Merge from git-svn r115 to master branch
Conflicts:
talk/libjingle.scons
talk/main.scons
talk/site_scons/talk.py
Change-Id: I173ec93b4b4a7aca68663dc756101325f280abf4
diff --git a/AUTHORS b/AUTHORS
index e491a9e..cbe3b77 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,4 @@
Google Inc.
+Eric Rescorla, RTFM Inc.
+Pali Rohar
+Robert Nagy
diff --git a/CHANGELOG b/CHANGELOG
index 1f64bb0..ecc343f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,62 @@
Libjingle
+0.6.12 - Feb 07, 2012
+ - PeerConnection client for windows.
+ - Bug fixes.
+
+0.6.11 - Jan 24, 2012
+ - Improved ipv6 support.
+ - Initial DTLS support.
+ - Initial BUNDLE support.
+ - Update Jingle protocol to multistream.
+ - WebRTC Bug fixes.
+
+0.6.10 - Jan 11, 2012
+ - Support fullscreen screencasting of secondary displays.
+ - Add IPv6 support for libjingle's STUN components.
+ - Enable SRTP in PeerConnection v1.
+ - Bug fixes.
+
+0.6.9 - Jan 09, 2012
+ - Enable SRTP in PeerConnection.
+ - Bug fixes.
+
+0.6.8 - Dec 22, 2011
+ - Add a lot of unit tests
+ - Add a lot of older files to base/ and xmpp/
+ - Add examples/pcp add examples/peerconnection
+ - Improve support for IPV6
+
+0.6.7 - Dec 21, 2011
+ - Release new PeerConnection implementation to app/webrtc.
+ - Bug fixes.
+
+0.6.6 - Dec 14, 2011
+ - Fix support for rtcp multiplexing (aka rtcp-mux).
+ - Add more support for FreeBSD and OpenBSD.
+ - Add more unit tests to session/phone.
+ - Add session/phone/mediarecorder.cc.
+ - Fixed httpportallocator tests.
+
+0.6.5 - Dec 8, 2011
+ - Add IPv6 support in SocketAddress.
+ - Change PeerConnectionFactory inteface.
+ - Bug fixes.
+
+0.6.4 - Nov 30, 2011
+ - Branch app/webrtc to app/webrtcv1.
+ - Add more base unit tests.
+ - Add xmllite unit tests.
+ - Refactoring and bug fixes
+
+0.6.3 - Oct 26, 2011
+ - Add media unit tests
+ - Improve OpenSSL support
+ - Add SSL unit tests
+ - Add DTLS support to SslStreamAdapter
+ - Add initial support for media processors
+ - Updated WebRTC voice and video engines
+
0.6.2 - Oct 7, 2011
- Increase the video rtp buffer.
- Disable sound system for chromium build.
diff --git a/README b/README
index f40d4ea..638d002 100644
--- a/README
+++ b/README
@@ -95,13 +95,22 @@
copy talk\third_party\srtp\config.hw talk\third_party\srtp\crypto\include\config.h
2.2 Build Libjingle under Linux or OS X
- * First, make sure the SCONS_DIR environment variable is set correctly.
+ * On Linux, you need to install libssl-dev, libasound2-dev and gtk+2.0.
+ * Some optional new features in OpenSSL are only available in OpenSSL v1.0
+ and above. To build with new OpenSSL features, you need to add the
+ "HAS_OPENSSL_1_0" to the cppdefine under the
+ "talk.Library(env, name = jingle..." section in the "libjingle.scons" file.
+ Then download the OpenSSL v1.0 from the following URL:
+ http://www.openssl.org/source/openssl-1.0.0e.tar.gz
+ Unzip the downloaded package to the "third_party/openssl", such that it creates
+ the directory "third_party/openssl/include/", etc.
+ * To build Libjingle, first make sure the SCONS_DIR environment variable
+ is set correctly.
* Second, run talk/third_party/expat-2.0.1/configure and
talk/third_party/srtp/configure.
* Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.sh. Run
$path_to_swtoolkit/hammer.sh --help for information on how to build for
different modes.
- * On Linux, you need to install libssl-dev, libasound2-dev and gtk+2.0.
2.3 Build Libjingle under Windows
* First, make sure the SCONS_DIR environment variable is set correctly and
diff --git a/talk/app/webrtc/audiotrackimpl.cc b/talk/app/webrtc/audiotrackimpl.cc
new file mode 100644
index 0000000..6fcef4a
--- /dev/null
+++ b/talk/app/webrtc/audiotrackimpl.cc
@@ -0,0 +1,71 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "talk/app/webrtc/audiotrackimpl.h"
+
+#include <string>
+
+namespace webrtc {
+
+static const char kAudioTrackKind[] = "audio";
+
+AudioTrack::AudioTrack(const std::string& label)
+ : MediaStreamTrack<LocalAudioTrackInterface>(label),
+ audio_device_(NULL) {
+}
+
+AudioTrack::AudioTrack(const std::string& label,
+ AudioDeviceModule* audio_device)
+ : MediaStreamTrack<LocalAudioTrackInterface>(label),
+ audio_device_(audio_device) {
+}
+
+ // Get the AudioDeviceModule associated with this track.
+AudioDeviceModule* AudioTrack::GetAudioDevice() {
+ return audio_device_.get();
+}
+
+ // Implement MediaStreamTrack
+std::string AudioTrack::kind() const {
+ return kAudioTrackKind;
+}
+
+talk_base::scoped_refptr<AudioTrack> AudioTrack::CreateRemote(
+ const std::string& label) {
+ talk_base::RefCountedObject<AudioTrack>* track =
+ new talk_base::RefCountedObject<AudioTrack>(label);
+ return track;
+}
+
+talk_base::scoped_refptr<AudioTrack> AudioTrack::CreateLocal(
+ const std::string& label,
+ AudioDeviceModule* audio_device) {
+ talk_base::RefCountedObject<AudioTrack>* track =
+ new talk_base::RefCountedObject<AudioTrack>(label, audio_device);
+ return track;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/audiotrackimpl.h b/talk/app/webrtc/audiotrackimpl.h
new file mode 100644
index 0000000..df3b3cb
--- /dev/null
+++ b/talk/app/webrtc/audiotrackimpl.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_AUDIOTRACKIMPL_H_
+#define TALK_APP_WEBRTC_AUDIOTRACKIMPL_H_
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediatrackimpl.h"
+#include "talk/app/webrtc/notifierimpl.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/audio_device/main/interface/audio_device.h"
+#else
+#include "third_party/webrtc/files/include/audio_device.h"
+#endif
+
+namespace webrtc {
+
+class AudioTrack : public MediaStreamTrack<LocalAudioTrackInterface> {
+ public:
+ // Creates a remote audio track.
+ static talk_base::scoped_refptr<AudioTrack> CreateRemote(
+ const std::string& label);
+ // Creates a local audio track.
+ static talk_base::scoped_refptr<AudioTrack> CreateLocal(
+ const std::string& label,
+ AudioDeviceModule* audio_device);
+
+ // Get the AudioDeviceModule associated with this track.
+ virtual AudioDeviceModule* GetAudioDevice();
+
+ // Implement MediaStreamTrack
+ virtual std::string kind() const;
+
+ protected:
+ explicit AudioTrack(const std::string& label);
+ AudioTrack(const std::string& label, AudioDeviceModule* audio_device);
+
+ private:
+ talk_base::scoped_refptr<AudioDeviceModule> audio_device_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_AUDIOTRACKIMPL_H_
diff --git a/talk/app/webrtc/fakeportallocatorfactory.h b/talk/app/webrtc/fakeportallocatorfactory.h
new file mode 100644
index 0000000..85885d4
--- /dev/null
+++ b/talk/app/webrtc/fakeportallocatorfactory.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file defines a fake port allocator factory used for testing.
+// This implementation creates instances of cricket::FakePortAllocator.
+
+#ifndef TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
+#define TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/p2p/client/fakeportallocator.h"
+
+namespace webrtc {
+
+class FakePortAllocatorFactory : public PortAllocatorFactoryInterface {
+ public:
+ static PortAllocatorFactoryInterface* Create() {
+ talk_base::RefCountedObject<FakePortAllocatorFactory>* allocator =
+ new talk_base::RefCountedObject<FakePortAllocatorFactory>();
+ return allocator;
+ }
+
+ virtual cricket::PortAllocator* CreatePortAllocator(
+ const std::vector<StunConfiguration>& stun_configurations,
+ const std::vector<TurnConfiguration>& turn_configurations) {
+ return new cricket::FakePortAllocator(talk_base::Thread::Current(), NULL);
+ }
+
+ protected:
+ FakePortAllocatorFactory() {}
+ ~FakePortAllocatorFactory() {}
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_FAKEPORTALLOCATORFACTORY_H_
diff --git a/talk/app/webrtc/mediastream.h b/talk/app/webrtc/mediastream.h
new file mode 100644
index 0000000..5db591a
--- /dev/null
+++ b/talk/app/webrtc/mediastream.h
@@ -0,0 +1,189 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains interfaces for MediaStream and MediaTrack. These
+// interfaces are used for implementing MediaStream and MediaTrack as defined
+// in http://dev.w3.org/2011/webrtc/editor/webrtc.html#stream-api. These
+// interfaces must be used only with PeerConnection. PeerConnectionManager
+// interface provides the factory methods to create MediaStream and MediaTracks.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAM_H_
+#define TALK_APP_WEBRTC_MEDIASTREAM_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace cricket {
+
+class VideoCapturer;
+class VideoRenderer;
+class MediaEngine;
+
+} // namespace cricket
+
+namespace webrtc {
+
+class AudioDeviceModule;
+
+// Generic observer interface.
+class ObserverInterface {
+ public:
+ virtual void OnChanged() = 0;
+
+ protected:
+ virtual ~ObserverInterface() {}
+};
+
+class NotifierInterface {
+ public:
+ virtual void RegisterObserver(ObserverInterface* observer) = 0;
+ virtual void UnregisterObserver(ObserverInterface* observer) = 0;
+
+ virtual ~NotifierInterface() {}
+};
+
+// Information about a track.
+class MediaStreamTrackInterface : public talk_base::RefCountInterface,
+ public NotifierInterface {
+ public:
+ enum TrackState {
+ kInitializing, // Track is beeing negotiated.
+ kLive = 1, // Track alive
+ kEnded = 2, // Track have ended
+ kFailed = 3, // Track negotiation failed.
+ };
+
+ virtual std::string kind() const = 0;
+ virtual std::string label() const = 0;
+ virtual bool enabled() const = 0;
+ virtual TrackState state() const = 0;
+ virtual bool set_enabled(bool enable) = 0;
+ // These methods should be called by implementation only.
+ virtual bool set_state(TrackState new_state) = 0;
+};
+
+// Reference counted wrapper for a VideoRenderer.
+class VideoRendererWrapperInterface : public talk_base::RefCountInterface {
+ public:
+ virtual cricket::VideoRenderer* renderer() = 0;
+
+ protected:
+ virtual ~VideoRendererWrapperInterface() {}
+};
+
+// Creates a reference counted object of type cricket::VideoRenderer.
+// webrtc::VideoRendererWrapperInterface take ownership of
+// cricket::VideoRenderer.
+talk_base::scoped_refptr<VideoRendererWrapperInterface> CreateVideoRenderer(
+ cricket::VideoRenderer* renderer);
+
+class VideoTrackInterface : public MediaStreamTrackInterface {
+ public:
+ // Set the video renderer for a local or remote stream.
+ // This call will start decoding the received video stream and render it.
+ // The VideoRendererInterface is stored as a scoped_refptr. This means that
+ // it is not allowed to call delete renderer after this API has been called.
+ virtual void SetRenderer(VideoRendererWrapperInterface* renderer) = 0;
+
+ // Get the VideoRenderer associated with this track.
+ virtual VideoRendererWrapperInterface* GetRenderer() = 0;
+
+ protected:
+ virtual ~VideoTrackInterface() {}
+};
+
+class LocalVideoTrackInterface : public VideoTrackInterface {
+ public:
+ // Get the VideoCapturer associated with the track.
+ virtual cricket::VideoCapturer* GetVideoCapture() = 0;
+
+ protected:
+ virtual ~LocalVideoTrackInterface() {}
+};
+
+class AudioTrackInterface : public MediaStreamTrackInterface {
+ public:
+ protected:
+ virtual ~AudioTrackInterface() {}
+};
+
+class LocalAudioTrackInterface : public AudioTrackInterface {
+ public:
+ // Get the AudioDeviceModule associated with this track.
+ virtual AudioDeviceModule* GetAudioDevice() = 0;
+ protected:
+ virtual ~LocalAudioTrackInterface() {}
+};
+
+// List of of tracks.
+template <class TrackType>
+class MediaStreamTrackListInterface : public talk_base::RefCountInterface {
+ public:
+ virtual size_t count() = 0;
+ virtual TrackType* at(size_t index) = 0;
+
+ protected:
+ virtual ~MediaStreamTrackListInterface() {}
+};
+
+typedef MediaStreamTrackListInterface<AudioTrackInterface> AudioTracks;
+typedef MediaStreamTrackListInterface<VideoTrackInterface> VideoTracks;
+
+class MediaStreamInterface : public talk_base::RefCountInterface,
+ public NotifierInterface {
+ public:
+ virtual std::string label() const = 0;
+ virtual AudioTracks* audio_tracks() = 0;
+ virtual VideoTracks* video_tracks() = 0;
+
+ enum ReadyState {
+ kInitializing,
+ kLive = 1, // Stream alive
+ kEnded = 2, // Stream have ended
+ };
+
+ virtual ReadyState ready_state() = 0;
+
+ // These methods should be called by implementation only.
+ virtual void set_ready_state(ReadyState state) = 0;
+
+ protected:
+ virtual ~MediaStreamInterface() {}
+};
+
+class LocalMediaStreamInterface : public MediaStreamInterface {
+ public:
+ virtual bool AddTrack(AudioTrackInterface* track) = 0;
+ virtual bool AddTrack(VideoTrackInterface* track) = 0;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAM_H_
diff --git a/talk/app/webrtc/mediastream_unittest.cc b/talk/app/webrtc/mediastream_unittest.cc
new file mode 100644
index 0000000..97d0c64
--- /dev/null
+++ b/talk/app/webrtc/mediastream_unittest.cc
@@ -0,0 +1,389 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+#include "testing/base/public/gmock.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoTrackLabel[] = "dummy_video_cam_1";
+static const char kAudioTrackLabel[] = "dummy_microphone_1";
+
+using talk_base::scoped_refptr;
+using ::testing::Exactly;
+
+namespace {
+
+class ReadyStateMessageData : public talk_base::MessageData {
+ public:
+ ReadyStateMessageData(
+ webrtc::MediaStreamInterface* stream,
+ webrtc::MediaStreamInterface::ReadyState new_state)
+ : stream_(stream),
+ ready_state_(new_state) {
+ }
+
+ scoped_refptr<webrtc::MediaStreamInterface> stream_;
+ webrtc::MediaStreamInterface::ReadyState ready_state_;
+};
+
+class TrackStateMessageData : public talk_base::MessageData {
+ public:
+ TrackStateMessageData(
+ webrtc::MediaStreamTrackInterface* track,
+ webrtc::MediaStreamTrackInterface::TrackState state)
+ : track_(track),
+ state_(state) {
+ }
+
+ scoped_refptr<webrtc::MediaStreamTrackInterface> track_;
+ webrtc::MediaStreamTrackInterface::TrackState state_;
+};
+
+} // namespace anonymous
+
+namespace webrtc {
+
+// Helper class to test Observer.
+class MockObserver : public ObserverInterface {
+ public:
+ explicit MockObserver(talk_base::Thread* signaling_thread)
+ : signaling_thread_(signaling_thread) {
+ }
+
+ MOCK_METHOD0(DoOnChanged, void());
+ virtual void OnChanged() {
+ ASSERT_TRUE(talk_base::Thread::Current() == signaling_thread_);
+ DoOnChanged();
+ }
+ private:
+ talk_base::Thread* signaling_thread_;
+};
+
+class MockMediaStream: public LocalMediaStreamInterface {
+ public:
+ MockMediaStream(const std::string& label, talk_base::Thread* signaling_thread)
+ : stream_impl_(MediaStream::Create(label)),
+ signaling_thread_(signaling_thread) {
+ }
+ virtual void RegisterObserver(webrtc::ObserverInterface* observer) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ stream_impl_->RegisterObserver(observer);
+ }
+ virtual void UnregisterObserver(webrtc::ObserverInterface* observer) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ stream_impl_->UnregisterObserver(observer);
+ }
+ virtual std::string label() const {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->label();
+ }
+ virtual AudioTracks* audio_tracks() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->audio_tracks();
+ }
+ virtual VideoTracks* video_tracks() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->video_tracks();
+ }
+ virtual ReadyState ready_state() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->ready_state();
+ }
+ virtual void set_ready_state(ReadyState state) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->set_ready_state(state);
+ }
+ virtual bool AddTrack(AudioTrackInterface* audio_track) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->AddTrack(audio_track);
+ }
+ virtual bool AddTrack(VideoTrackInterface* video_track) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return stream_impl_->AddTrack(video_track);
+ }
+
+ private:
+ scoped_refptr<MediaStream> stream_impl_;
+ talk_base::Thread* signaling_thread_;
+};
+
+template <class T>
+class MockMediaStreamTrack: public T {
+ public:
+ MockMediaStreamTrack(T* implementation,
+ talk_base::Thread* signaling_thread)
+ : track_impl_(implementation),
+ signaling_thread_(signaling_thread) {
+ }
+ virtual void RegisterObserver(webrtc::ObserverInterface* observer) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ track_impl_->RegisterObserver(observer);
+ }
+ virtual void UnregisterObserver(webrtc::ObserverInterface* observer) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ track_impl_->UnregisterObserver(observer);
+ }
+ virtual std::string kind() const {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->kind();
+ }
+ virtual std::string label() const {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->label();
+ }
+ virtual bool enabled() const {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->enabled();
+ }
+ virtual MediaStreamTrackInterface::TrackState state() const {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->state();
+ }
+ virtual bool set_enabled(bool enabled) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->set_enabled(enabled);
+ }
+ virtual bool set_state(webrtc::MediaStreamTrackInterface::TrackState state) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->set_state(state);
+ }
+
+ protected:
+ scoped_refptr<T> track_impl_;
+ talk_base::Thread* signaling_thread_;
+};
+
+class MockLocalVideoTrack
+ : public MockMediaStreamTrack<LocalVideoTrackInterface> {
+ public:
+ MockLocalVideoTrack(LocalVideoTrackInterface* implementation,
+ talk_base::Thread* signaling_thread)
+ : MockMediaStreamTrack<LocalVideoTrackInterface>(implementation,
+ signaling_thread) {
+ }
+ virtual void SetRenderer(webrtc::VideoRendererWrapperInterface* renderer) {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ track_impl_->SetRenderer(renderer);
+ }
+ virtual VideoRendererWrapperInterface* GetRenderer() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->GetRenderer();
+ }
+ virtual cricket::VideoCapturer* GetVideoCapture() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->GetVideoCapture();
+ }
+};
+
+class MockLocalAudioTrack
+ : public MockMediaStreamTrack<LocalAudioTrackInterface> {
+ public:
+ MockLocalAudioTrack(LocalAudioTrackInterface* implementation,
+ talk_base::Thread* signaling_thread)
+ : MockMediaStreamTrack<LocalAudioTrackInterface>(implementation,
+ signaling_thread) {
+ }
+
+ virtual AudioDeviceModule* GetAudioDevice() {
+ EXPECT_EQ(talk_base::Thread::Current(), signaling_thread_);
+ return track_impl_->GetAudioDevice();
+ }
+};
+
+class MediaStreamTest: public testing::Test,
+ public talk_base::MessageHandler {
+ protected:
+ virtual void SetUp() {
+ signaling_thread_ .reset(new talk_base::Thread());
+ ASSERT_TRUE(signaling_thread_->Start());
+
+ std::string label(kStreamLabel1);
+ // Create a stream proxy object that uses our mocked
+ // version of a LocalMediaStream.
+ scoped_refptr<MockMediaStream> mock_stream(
+ new talk_base::RefCountedObject<MockMediaStream>(label,
+ signaling_thread_.get()));
+ stream_ = MediaStreamProxy::Create(label, signaling_thread_.get(),
+ mock_stream);
+ ASSERT_TRUE(stream_.get() != NULL);
+ EXPECT_EQ(label, stream_->label());
+ EXPECT_EQ(MediaStreamInterface::kInitializing, stream_->ready_state());
+
+ // Create a video track proxy object that uses our mocked
+ // version of a LocalVideoTrack
+ scoped_refptr<VideoTrack> video_track_impl(
+ VideoTrack::CreateLocal(kVideoTrackLabel, NULL));
+ scoped_refptr<MockLocalVideoTrack> mock_videotrack(
+ new talk_base::RefCountedObject<MockLocalVideoTrack>(video_track_impl,
+ signaling_thread_.get()));
+ video_track_ = VideoTrackProxy::CreateLocal(mock_videotrack,
+ signaling_thread_.get());
+
+ ASSERT_TRUE(video_track_.get() != NULL);
+ EXPECT_EQ(MediaStreamTrackInterface::kInitializing, video_track_->state());
+
+ // Create an audio track proxy object that uses our mocked
+ // version of a LocalAudioTrack
+ scoped_refptr<AudioTrack> audio_track_impl(
+ AudioTrack::CreateLocal(kAudioTrackLabel, NULL));
+ scoped_refptr<MockLocalAudioTrack> mock_audiotrack(
+ new talk_base::RefCountedObject<MockLocalAudioTrack>(audio_track_impl,
+ signaling_thread_.get()));
+ audio_track_ = AudioTrackProxy::CreateLocal(mock_audiotrack,
+ signaling_thread_.get());
+
+ ASSERT_TRUE(audio_track_.get() != NULL);
+ EXPECT_EQ(MediaStreamTrackInterface::kInitializing, audio_track_->state());
+ }
+
+ enum {
+ MSG_SET_READYSTATE,
+ MSG_SET_TRACKSTATE,
+ };
+
+ // Set the ready state on the signaling thread.
+ // State can only be changed on the signaling thread.
+ void SetReadyState(MediaStreamInterface* stream,
+ MediaStreamInterface::ReadyState new_state) {
+ ReadyStateMessageData state(stream, new_state);
+ signaling_thread_->Send(this, MSG_SET_READYSTATE, &state);
+ }
+
+ // Set the track state on the signaling thread.
+ // State can only be changed on the signaling thread.
+ void SetTrackState(MediaStreamTrackInterface* track,
+ MediaStreamTrackInterface::TrackState new_state) {
+ TrackStateMessageData state(track, new_state);
+ signaling_thread_->Send(this, MSG_SET_TRACKSTATE, &state);
+ }
+
+ talk_base::scoped_ptr<talk_base::Thread> signaling_thread_;
+ scoped_refptr<LocalMediaStreamInterface> stream_;
+ scoped_refptr<LocalVideoTrackInterface> video_track_;
+ scoped_refptr<LocalAudioTrackInterface> audio_track_;
+
+ private:
+ // Implements talk_base::MessageHandler.
+ virtual void OnMessage(talk_base::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_SET_READYSTATE: {
+ ReadyStateMessageData* state =
+ static_cast<ReadyStateMessageData*>(msg->pdata);
+ state->stream_->set_ready_state(state->ready_state_);
+ break;
+ }
+ case MSG_SET_TRACKSTATE: {
+ TrackStateMessageData* state =
+ static_cast<TrackStateMessageData*>(msg->pdata);
+ state->track_->set_state(state->state_);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+};
+
+TEST_F(MediaStreamTest, CreateLocalStream) {
+ EXPECT_TRUE(stream_->AddTrack(video_track_));
+ EXPECT_TRUE(stream_->AddTrack(audio_track_));
+
+ ASSERT_EQ(1u, stream_->video_tracks()->count());
+ ASSERT_EQ(1u, stream_->audio_tracks()->count());
+
+ // Verify the video track.
+ scoped_refptr<webrtc::MediaStreamTrackInterface> track(
+ stream_->video_tracks()->at(0));
+ EXPECT_EQ(0, track->label().compare(kVideoTrackLabel));
+ EXPECT_TRUE(track->enabled());
+
+ // Verify the audio track.
+ track = stream_->audio_tracks()->at(0);
+ EXPECT_EQ(0, track->label().compare(kAudioTrackLabel));
+ EXPECT_TRUE(track->enabled());
+}
+
+TEST_F(MediaStreamTest, ChangeStreamState) {
+ MockObserver observer(signaling_thread_.get());
+ stream_->RegisterObserver(&observer);
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ SetReadyState(stream_, MediaStreamInterface::kLive);
+
+ EXPECT_EQ(MediaStreamInterface::kLive, stream_->ready_state());
+ // It should not be possible to add
+ // streams when the state has changed to live.
+ EXPECT_FALSE(stream_->AddTrack(audio_track_));
+ EXPECT_EQ(0u, stream_->audio_tracks()->count());
+}
+
+TEST_F(MediaStreamTest, ChangeVideoTrack) {
+ MockObserver observer(signaling_thread_.get());
+ video_track_->RegisterObserver(&observer);
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ video_track_->set_enabled(false);
+ EXPECT_FALSE(video_track_->state());
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ SetTrackState(video_track_, MediaStreamTrackInterface::kLive);
+ EXPECT_EQ(MediaStreamTrackInterface::kLive, video_track_->state());
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ scoped_refptr<VideoRendererWrapperInterface> renderer(
+ CreateVideoRenderer(NULL));
+ video_track_->SetRenderer(renderer.get());
+ EXPECT_TRUE(renderer.get() == video_track_->GetRenderer());
+}
+
+TEST_F(MediaStreamTest, ChangeAudioTrack) {
+ MockObserver observer(signaling_thread_.get());
+ audio_track_->RegisterObserver(&observer);
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ audio_track_->set_enabled(false);
+ EXPECT_FALSE(audio_track_->enabled());
+
+ EXPECT_CALL(observer, DoOnChanged())
+ .Times(Exactly(1));
+ SetTrackState(audio_track_, MediaStreamTrackInterface::kLive);
+ EXPECT_EQ(MediaStreamTrackInterface::kLive, audio_track_->state());
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.cc b/talk/app/webrtc/mediastreamhandler.cc
new file mode 100644
index 0000000..d120b80
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.cc
@@ -0,0 +1,258 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/mediastreamhandler.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace webrtc {
+
+VideoTrackHandler::VideoTrackHandler(VideoTrackInterface* track,
+ MediaProviderInterface* provider)
+ : provider_(provider),
+ video_track_(track),
+ state_(track->state()),
+ enabled_(track->enabled()),
+ renderer_(track->GetRenderer()) {
+ video_track_->RegisterObserver(this);
+}
+
+VideoTrackHandler::~VideoTrackHandler() {
+ video_track_->UnregisterObserver(this);
+}
+
+void VideoTrackHandler::OnChanged() {
+ if (state_ != video_track_->state()) {
+ state_ = video_track_->state();
+ OnStateChanged();
+ }
+ if (renderer_.get() != video_track_->GetRenderer()) {
+ renderer_ = video_track_->GetRenderer();
+ OnRendererChanged();
+ }
+ if (enabled_ != video_track_->enabled()) {
+ enabled_ = video_track_->enabled();
+ OnEnabledChanged();
+ }
+}
+
+LocalVideoTrackHandler::LocalVideoTrackHandler(
+ LocalVideoTrackInterface* track,
+ MediaProviderInterface* provider)
+ : VideoTrackHandler(track, provider),
+ local_video_track_(track) {
+}
+
+LocalVideoTrackHandler::~LocalVideoTrackHandler() {
+ // cricket::VideoRenderer and cricket::VideoCapturer is owned and deleted by
+ // the track. It must be removed from the media stream provider since it is
+ // possible that the tracks reference count is set to zero when
+ // local_video_track_ falls out of scope.
+ provider_->SetLocalRenderer(local_video_track_->label(), NULL);
+ provider_->SetCaptureDevice(local_video_track_->label(), NULL);
+}
+
+void LocalVideoTrackHandler::OnRendererChanged() {
+ VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+ if (renderer)
+ provider_->SetLocalRenderer(video_track_->label(), renderer->renderer());
+ else
+ provider_->SetLocalRenderer(video_track_->label(), NULL);
+}
+
+void LocalVideoTrackHandler::OnStateChanged() {
+ if (local_video_track_->state() == VideoTrackInterface::kLive) {
+ provider_->SetCaptureDevice(local_video_track_->label(),
+ local_video_track_->GetVideoCapture());
+ VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+ if (renderer)
+ provider_->SetLocalRenderer(video_track_->label(), renderer->renderer());
+ else
+ provider_->SetLocalRenderer(video_track_->label(), NULL);
+ }
+}
+
+void LocalVideoTrackHandler::OnEnabledChanged() {
+ // TODO What should happen when enabled is changed?
+}
+
+RemoteVideoTrackHandler::RemoteVideoTrackHandler(
+ VideoTrackInterface* track,
+ MediaProviderInterface* provider)
+ : VideoTrackHandler(track, provider),
+ remote_video_track_(track) {
+}
+
+RemoteVideoTrackHandler::~RemoteVideoTrackHandler() {
+ // Since cricket::VideoRenderer is not reference counted
+ // we need to remove the renderer before we are deleted.
+ provider_->SetRemoteRenderer(video_track_->label(), NULL);
+}
+
+
+void RemoteVideoTrackHandler::OnRendererChanged() {
+ VideoRendererWrapperInterface* renderer = video_track_->GetRenderer();
+ if (renderer)
+ provider_->SetRemoteRenderer(video_track_->label(), renderer->renderer());
+ else
+ provider_->SetRemoteRenderer(video_track_->label(), NULL);
+}
+
+void RemoteVideoTrackHandler::OnStateChanged() {
+}
+
+void RemoteVideoTrackHandler::OnEnabledChanged() {
+ // TODO: What should happen when enabled is changed?
+}
+
+MediaStreamHandler::MediaStreamHandler(MediaStreamInterface* stream,
+ MediaProviderInterface* provider)
+ : stream_(stream),
+ provider_(provider) {
+}
+
+MediaStreamHandler::~MediaStreamHandler() {
+ for (VideoTrackHandlers::iterator it = video_handlers_.begin();
+ it != video_handlers_.end(); ++it) {
+ delete *it;
+ }
+}
+
+MediaStreamInterface* MediaStreamHandler::stream() {
+ return stream_.get();
+}
+
+void MediaStreamHandler::OnChanged() {
+ // TODO: Implement state change and enabled changed.
+}
+
+
+LocalMediaStreamHandler::LocalMediaStreamHandler(
+ MediaStreamInterface* stream,
+ MediaProviderInterface* provider)
+ : MediaStreamHandler(stream, provider) {
+ VideoTracks* tracklist(stream->video_tracks());
+
+ for (size_t j = 0; j < tracklist->count(); ++j) {
+ LocalVideoTrackInterface* track =
+ static_cast<LocalVideoTrackInterface*>(tracklist->at(j));
+ VideoTrackHandler* handler(new LocalVideoTrackHandler(track, provider));
+ video_handlers_.push_back(handler);
+ }
+}
+
+RemoteMediaStreamHandler::RemoteMediaStreamHandler(
+ MediaStreamInterface* stream,
+ MediaProviderInterface* provider)
+ : MediaStreamHandler(stream, provider) {
+ VideoTracks* tracklist(stream->video_tracks());
+
+ for (size_t j = 0; j < tracklist->count(); ++j) {
+ VideoTrackInterface* track =
+ static_cast<VideoTrackInterface*>(tracklist->at(j));
+ VideoTrackHandler* handler(new RemoteVideoTrackHandler(track, provider));
+ video_handlers_.push_back(handler);
+ }
+}
+
+MediaStreamHandlers::MediaStreamHandlers(MediaProviderInterface* provider)
+ : provider_(provider) {
+}
+
+MediaStreamHandlers::~MediaStreamHandlers() {
+ for (StreamHandlerList::iterator it = remote_streams_handlers_.begin();
+ it != remote_streams_handlers_.end(); ++it) {
+ delete *it;
+ }
+ for (StreamHandlerList::iterator it = local_streams_handlers_.begin();
+ it != local_streams_handlers_.end(); ++it) {
+ delete *it;
+ }
+}
+
+void MediaStreamHandlers::AddRemoteStream(MediaStreamInterface* stream) {
+ RemoteMediaStreamHandler* handler = new RemoteMediaStreamHandler(stream,
+ provider_);
+ remote_streams_handlers_.push_back(handler);
+}
+
+void MediaStreamHandlers::RemoveRemoteStream(MediaStreamInterface* stream) {
+ StreamHandlerList::iterator it = remote_streams_handlers_.begin();
+ for (; it != remote_streams_handlers_.end(); ++it) {
+ if ((*it)->stream() == stream) {
+ delete *it;
+ break;
+ }
+ }
+ ASSERT(it != remote_streams_handlers_.end());
+ remote_streams_handlers_.erase(it);
+}
+
+void MediaStreamHandlers::CommitLocalStreams(
+ StreamCollectionInterface* streams) {
+ // Iterate the old list of local streams.
+ // If its not found in the new collection it have been removed.
+ // We can not erase from the old collection at the same time as we iterate.
+ // That is what the ugly while(1) fix.
+ while (1) {
+ StreamHandlerList::iterator it = local_streams_handlers_.begin();
+ for (; it != local_streams_handlers_.end(); ++it) {
+ if (streams->find((*it)->stream()->label()) == NULL) {
+ delete *it;
+ break;
+ }
+ }
+ if (it != local_streams_handlers_.end()) {
+ local_streams_handlers_.erase(it);
+ continue;
+ }
+ break;
+ }
+
+ // Iterate the new collection of local streams.
+ // If its not found in the old collection it have been added.
+ for (size_t j = 0; j < streams->count(); ++j) {
+ MediaStreamInterface* stream = streams->at(j);
+ StreamHandlerList::iterator it = local_streams_handlers_.begin();
+ for (; it != local_streams_handlers_.end(); ++it) {
+ if (stream == (*it)->stream())
+ break;
+ }
+ if (it == local_streams_handlers_.end()) {
+ LocalMediaStreamHandler* handler = new LocalMediaStreamHandler(
+ stream, provider_);
+ local_streams_handlers_.push_back(handler);
+ }
+ }
+};
+
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamhandler.h b/talk/app/webrtc/mediastreamhandler.h
new file mode 100644
index 0000000..550a2d4
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler.h
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains classes for listening on changes on MediaStreams and
+// MediaTracks and making sure appropriate action is taken.
+// Example: If a user sets a rendererer on a local video track the renderer is
+// connected to the appropriate camera.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+
+#include <list>
+#include <vector>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+
+// VideoTrackHandler listen to events on a VideoTrack instance and
+// executes the requested change.
+class VideoTrackHandler : public ObserverInterface {
+ public:
+ VideoTrackHandler(VideoTrackInterface* track,
+ MediaProviderInterface* provider);
+ virtual ~VideoTrackHandler();
+ virtual void OnChanged();
+
+ protected:
+ virtual void OnRendererChanged() = 0;
+ virtual void OnStateChanged() = 0;
+ virtual void OnEnabledChanged() = 0;
+
+ MediaProviderInterface* provider_;
+ VideoTrackInterface* video_track_;
+
+ private:
+ MediaStreamTrackInterface::TrackState state_;
+ bool enabled_;
+ talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer_;
+};
+
+class LocalVideoTrackHandler : public VideoTrackHandler {
+ public:
+ LocalVideoTrackHandler(LocalVideoTrackInterface* track,
+ MediaProviderInterface* provider);
+ virtual ~LocalVideoTrackHandler();
+
+ protected:
+ virtual void OnRendererChanged();
+ virtual void OnStateChanged();
+ virtual void OnEnabledChanged();
+
+ private:
+ talk_base::scoped_refptr<LocalVideoTrackInterface> local_video_track_;
+};
+
+class RemoteVideoTrackHandler : public VideoTrackHandler {
+ public:
+ RemoteVideoTrackHandler(VideoTrackInterface* track,
+ MediaProviderInterface* provider);
+ virtual ~RemoteVideoTrackHandler();
+
+ protected:
+ virtual void OnRendererChanged();
+ virtual void OnStateChanged();
+ virtual void OnEnabledChanged();
+
+ private:
+ talk_base::scoped_refptr<VideoTrackInterface> remote_video_track_;
+};
+
+class MediaStreamHandler : public ObserverInterface {
+ public:
+ MediaStreamHandler(MediaStreamInterface* stream,
+ MediaProviderInterface* provider);
+ ~MediaStreamHandler();
+ MediaStreamInterface* stream();
+ virtual void OnChanged();
+
+ protected:
+ talk_base::scoped_refptr<MediaStreamInterface> stream_;
+ MediaProviderInterface* provider_;
+ typedef std::vector<VideoTrackHandler*> VideoTrackHandlers;
+ VideoTrackHandlers video_handlers_;
+};
+
+class LocalMediaStreamHandler : public MediaStreamHandler {
+ public:
+ LocalMediaStreamHandler(MediaStreamInterface* stream,
+ MediaProviderInterface* provider);
+};
+
+class RemoteMediaStreamHandler : public MediaStreamHandler {
+ public:
+ RemoteMediaStreamHandler(MediaStreamInterface* stream,
+ MediaProviderInterface* provider);
+};
+
+class MediaStreamHandlers {
+ public:
+ explicit MediaStreamHandlers(MediaProviderInterface* provider);
+ ~MediaStreamHandlers();
+ void AddRemoteStream(MediaStreamInterface* stream);
+ void RemoveRemoteStream(MediaStreamInterface* stream);
+ void CommitLocalStreams(StreamCollectionInterface* streams);
+
+ private:
+ typedef std::list<MediaStreamHandler*> StreamHandlerList;
+ StreamHandlerList local_streams_handlers_;
+ StreamHandlerList remote_streams_handlers_;
+ MediaProviderInterface* provider_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAMHANDLER_H_
+
diff --git a/talk/app/webrtc/mediastreamhandler_unittest.cc b/talk/app/webrtc/mediastreamhandler_unittest.cc
new file mode 100644
index 0000000..cd702bd
--- /dev/null
+++ b/talk/app/webrtc/mediastreamhandler_unittest.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/app/webrtc/mediastreamhandler.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+#include "testing/base/public/gmock.h"
+
+using ::testing::Exactly;
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kVideoDeviceName[] = "dummy_video_cam_1";
+
+namespace webrtc {
+
+// Helper class to test MediaStreamHandler.
+class MockMediaProvier : public MediaProviderInterface {
+ public:
+ MOCK_METHOD1(SetCaptureDevice, bool(const std::string& name));
+ MOCK_METHOD1(SetLocalRenderer, void(const std::string& name));
+ MOCK_METHOD1(SetRemoteRenderer, void(const std::string& name));
+
+ virtual bool SetCaptureDevice(const std::string& name,
+ cricket::VideoCapturer* camera) {
+ return SetCaptureDevice(name);
+ }
+ virtual void SetLocalRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) {
+ SetLocalRenderer(name);
+ }
+
+ virtual void SetRemoteRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) {
+ SetRemoteRenderer(name);
+ }
+ ~MockMediaProvier() {}
+};
+
+TEST(MediaStreamHandlerTest, LocalStreams) {
+ // Create a local stream.
+ std::string label(kStreamLabel1);
+ talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+ MediaStream::Create(label));
+ talk_base::scoped_refptr<LocalVideoTrackInterface>
+ video_track(VideoTrack::CreateLocal(kVideoDeviceName, NULL));
+ EXPECT_TRUE(stream->AddTrack(video_track));
+ talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer(
+ CreateVideoRenderer(NULL));
+ video_track->SetRenderer(renderer);
+
+ MockMediaProvier provider;
+ MediaStreamHandlers handlers(&provider);
+
+ talk_base::scoped_refptr<StreamCollection> collection(
+ StreamCollection::Create());
+ collection->AddStream(stream);
+
+ EXPECT_CALL(provider, SetLocalRenderer(kVideoDeviceName))
+ .Times(Exactly(2)); // SetLocalRender will also be called from dtor of
+ // LocalVideoTrackHandler
+ EXPECT_CALL(provider, SetCaptureDevice(kVideoDeviceName))
+ .Times(Exactly(2)); // SetCaptureDevice will also be called from dtor of
+ // LocalVideoTrackHandler
+ handlers.CommitLocalStreams(collection);
+
+ video_track->set_state(MediaStreamTrackInterface::kLive);
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ collection->RemoveStream(stream);
+ handlers.CommitLocalStreams(collection);
+
+ video_track->set_state(MediaStreamTrackInterface::kEnded);
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+}
+
+TEST(MediaStreamHandlerTest, RemoteStreams) {
+ // Create a local stream. We use local stream in this test as well because
+ // they are easier to create.
+ // LocalMediaStreams inherit from MediaStreams.
+ std::string label(kStreamLabel1);
+ talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+ MediaStream::Create(label));
+ talk_base::scoped_refptr<LocalVideoTrackInterface>
+ video_track(VideoTrack::CreateLocal(kVideoDeviceName, NULL));
+ EXPECT_TRUE(stream->AddTrack(video_track));
+
+ MockMediaProvier provider;
+ MediaStreamHandlers handlers(&provider);
+
+ handlers.AddRemoteStream(stream);
+
+ EXPECT_CALL(provider, SetRemoteRenderer(kVideoDeviceName))
+ .Times(Exactly(3)); // SetRemoteRenderer is also called from dtor of
+ // RemoteVideoTrackHandler.
+
+ // Set the renderer once.
+ talk_base::scoped_refptr<VideoRendererWrapperInterface> renderer(
+ CreateVideoRenderer(NULL));
+ video_track->SetRenderer(renderer);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Change the already set renderer.
+ renderer = CreateVideoRenderer(NULL);
+ video_track->SetRenderer(renderer);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ handlers.RemoveRemoteStream(stream);
+
+ // Change the renderer after the stream have been removed from handler.
+ // This should not trigger a call to SetRemoteRenderer.
+ renderer = CreateVideoRenderer(NULL);
+ video_track->SetRenderer(renderer);
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamimpl.cc b/talk/app/webrtc/mediastreamimpl.cc
new file mode 100644
index 0000000..8920379
--- /dev/null
+++ b/talk/app/webrtc/mediastreamimpl.cc
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+talk_base::scoped_refptr<MediaStream> MediaStream::Create(
+ const std::string& label) {
+ talk_base::RefCountedObject<MediaStream>* stream =
+ new talk_base::RefCountedObject<MediaStream>(label);
+ return stream;
+}
+
+MediaStream::MediaStream(const std::string& label)
+ : label_(label),
+ ready_state_(MediaStreamInterface::kInitializing),
+ audio_track_list_(
+ new talk_base::RefCountedObject<
+ MediaStreamTrackList<AudioTrackInterface> >()),
+ video_track_list_(
+ new talk_base::RefCountedObject<
+ MediaStreamTrackList<VideoTrackInterface> >()) {
+}
+
+void MediaStream::set_ready_state(
+ MediaStreamInterface::ReadyState new_state) {
+ if (ready_state_ != new_state) {
+ ready_state_ = new_state;
+ Notifier<LocalMediaStreamInterface>::FireOnChanged();
+ }
+}
+
+bool MediaStream::AddTrack(AudioTrackInterface* track) {
+ if (ready_state() != kInitializing)
+ return false;
+ audio_track_list_->AddTrack(track);
+ return true;
+}
+
+bool MediaStream::AddTrack(VideoTrackInterface* track) {
+ if (ready_state() != kInitializing)
+ return false;
+ video_track_list_->AddTrack(track);
+ return true;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamimpl.h b/talk/app/webrtc/mediastreamimpl.h
new file mode 100644
index 0000000..44ede9b
--- /dev/null
+++ b/talk/app/webrtc/mediastreamimpl.h
@@ -0,0 +1,89 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains the implementation of MediaStreamInterface interface.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/notifierimpl.h"
+
+namespace webrtc {
+class AudioTrack;
+class VideoTrack;
+
+class MediaStream : public Notifier<LocalMediaStreamInterface> {
+ public:
+ template<class T>
+ class MediaStreamTrackList : public MediaStreamTrackListInterface<T> {
+ public:
+ void AddTrack(T* track) {
+ tracks_.push_back(track);
+ }
+ virtual size_t count() { return tracks_.size(); }
+ virtual T* at(size_t index) {
+ return tracks_.at(index);
+ }
+
+ private:
+ std::vector<talk_base::scoped_refptr<T> > tracks_;
+ };
+
+ static talk_base::scoped_refptr<MediaStream> Create(const std::string& label);
+
+ // Implement LocalMediaStreamInterface.
+ virtual bool AddTrack(AudioTrackInterface* track);
+ virtual bool AddTrack(VideoTrackInterface* track);
+ // Implement MediaStreamInterface.
+ virtual std::string label() const { return label_; }
+ virtual MediaStreamTrackListInterface<AudioTrackInterface>* audio_tracks() {
+ return audio_track_list_;
+ }
+ virtual MediaStreamTrackListInterface<VideoTrackInterface>* video_tracks() {
+ return video_track_list_;
+ }
+ virtual ReadyState ready_state() { return ready_state_; }
+ virtual void set_ready_state(ReadyState new_state);
+
+ protected:
+ explicit MediaStream(const std::string& label);
+
+ std::string label_;
+ MediaStreamInterface::ReadyState ready_state_;
+ talk_base::scoped_refptr<MediaStreamTrackList<AudioTrackInterface> >
+ audio_track_list_;
+ talk_base::scoped_refptr<MediaStreamTrackList<VideoTrackInterface> >
+ video_track_list_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAMIMPL_H_
diff --git a/talk/app/webrtc/mediastreamprovider.h b/talk/app/webrtc/mediastreamprovider.h
new file mode 100644
index 0000000..e99b115
--- /dev/null
+++ b/talk/app/webrtc/mediastreamprovider.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
+
+#include "talk/app/webrtc/mediastream.h"
+
+namespace cricket {
+
+class VideoCapturer;
+
+} // namespace cricket
+
+namespace webrtc {
+
+// Interface for setting media devices on a certain MediaTrack.
+// This interface is called by classes in mediastreamhandler.h to
+// set new devices.
+class MediaProviderInterface {
+ public:
+ virtual bool SetCaptureDevice(const std::string& name,
+ cricket::VideoCapturer* camera) = 0;
+ virtual void SetLocalRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) = 0;
+ virtual void SetRemoteRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) = 0;
+ protected:
+ virtual ~MediaProviderInterface() {}
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAMPROVIDER_H_
diff --git a/talk/app/webrtc/mediastreamproxy.cc b/talk/app/webrtc/mediastreamproxy.cc
new file mode 100644
index 0000000..7e74c86
--- /dev/null
+++ b/talk/app/webrtc/mediastreamproxy.cc
@@ -0,0 +1,318 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/base/refcount.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+namespace {
+
+enum {
+ MSG_SET_TRACKLIST_IMPLEMENTATION = 1,
+ MSG_REGISTER_OBSERVER,
+ MSG_UNREGISTER_OBSERVER,
+ MSG_LABEL,
+ MSG_ADD_AUDIO_TRACK,
+ MSG_ADD_VIDEO_TRACK,
+ MSG_READY_STATE,
+ MSG_COUNT,
+ MSG_AT
+};
+
+typedef talk_base::TypedMessageData<std::string*> LabelMessageData;
+typedef talk_base::TypedMessageData<size_t> SizeTMessageData;
+typedef talk_base::TypedMessageData<webrtc::ObserverInterface*>
+ ObserverMessageData;
+typedef talk_base::TypedMessageData<webrtc::MediaStreamInterface::ReadyState>
+ ReadyStateMessageData;
+
+template<typename T>
+class MediaStreamTrackMessageData : public talk_base::MessageData {
+ public:
+ explicit MediaStreamTrackMessageData(T* track)
+ : track_(track),
+ result_(false) {
+ }
+
+ talk_base::scoped_refptr<T> track_;
+ bool result_;
+};
+
+typedef MediaStreamTrackMessageData<webrtc::AudioTrackInterface>
+ AudioTrackMsgData;
+typedef MediaStreamTrackMessageData<webrtc::VideoTrackInterface>
+ VideoTrackMsgData;
+
+template <class TrackType>
+class MediaStreamTrackAtMessageData : public talk_base::MessageData {
+ public:
+ explicit MediaStreamTrackAtMessageData(size_t index)
+ : index_(index) {
+ }
+
+ size_t index_;
+ talk_base::scoped_refptr<TrackType> track_;
+};
+
+class MediaStreamTrackListsMessageData : public talk_base::MessageData {
+ public:
+ talk_base::scoped_refptr<webrtc::AudioTracks> audio_tracks_;
+ talk_base::scoped_refptr<webrtc::VideoTracks> video_tracks_;
+};
+
+} // namespace anonymous
+
+namespace webrtc {
+
+talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
+ const std::string& label,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<MediaStreamProxy>* stream =
+ new talk_base::RefCountedObject<MediaStreamProxy>(
+ label, signaling_thread,
+ reinterpret_cast<LocalMediaStreamInterface*>(NULL));
+ return stream;
+}
+
+talk_base::scoped_refptr<MediaStreamProxy> MediaStreamProxy::Create(
+ const std::string& label,
+ talk_base::Thread* signaling_thread,
+ LocalMediaStreamInterface* media_stream_impl) {
+ ASSERT(signaling_thread != NULL);
+ ASSERT(media_stream_impl != NULL);
+ talk_base::RefCountedObject<MediaStreamProxy>* stream =
+ new talk_base::RefCountedObject<MediaStreamProxy>(label, signaling_thread,
+ media_stream_impl);
+ return stream;
+}
+
+MediaStreamProxy::MediaStreamProxy(const std::string& label,
+ talk_base::Thread* signaling_thread,
+ LocalMediaStreamInterface* media_stream_impl)
+ : signaling_thread_(signaling_thread),
+ media_stream_impl_(media_stream_impl),
+ audio_tracks_(new talk_base::RefCountedObject<
+ MediaStreamTrackListProxy<AudioTrackInterface> >(
+ signaling_thread_)),
+ video_tracks_(new talk_base::RefCountedObject<
+ MediaStreamTrackListProxy<VideoTrackInterface> >(
+ signaling_thread_)) {
+ if (media_stream_impl_ == NULL) {
+ media_stream_impl_ = MediaStream::Create(label);
+ }
+
+ MediaStreamTrackListsMessageData tracklists;
+ Send(MSG_SET_TRACKLIST_IMPLEMENTATION, &tracklists);
+ audio_tracks_->SetImplementation(tracklists.audio_tracks_);
+ video_tracks_->SetImplementation(tracklists.video_tracks_);
+}
+
+std::string MediaStreamProxy::label() const {
+ if (!signaling_thread_->IsCurrent()) {
+ std::string label;
+ LabelMessageData msg(&label);
+ Send(MSG_LABEL, &msg);
+ return label;
+ }
+ return media_stream_impl_->label();
+}
+
+MediaStreamInterface::ReadyState MediaStreamProxy::ready_state() {
+ if (!signaling_thread_->IsCurrent()) {
+ ReadyStateMessageData msg(MediaStreamInterface::kInitializing);
+ Send(MSG_READY_STATE, &msg);
+ return msg.data();
+ }
+ return media_stream_impl_->ready_state();
+}
+
+void MediaStreamProxy::set_ready_state(
+ MediaStreamInterface::ReadyState new_state) {
+ if (!signaling_thread_->IsCurrent()) {
+ // State should only be allowed to be changed from the signaling thread.
+ ASSERT(!"Not Allowed!");
+ return;
+ }
+ media_stream_impl_->set_ready_state(new_state);
+}
+
+bool MediaStreamProxy::AddTrack(AudioTrackInterface* track) {
+ if (!signaling_thread_->IsCurrent()) {
+ AudioTrackMsgData msg(track);
+ Send(MSG_ADD_AUDIO_TRACK, &msg);
+ return msg.result_;
+ }
+ return media_stream_impl_->AddTrack(track);
+}
+
+bool MediaStreamProxy::AddTrack(VideoTrackInterface* track) {
+ if (!signaling_thread_->IsCurrent()) {
+ VideoTrackMsgData msg(track);
+ Send(MSG_ADD_VIDEO_TRACK, &msg);
+ return msg.result_;
+ }
+ return media_stream_impl_->AddTrack(track);
+}
+
+void MediaStreamProxy::RegisterObserver(ObserverInterface* observer) {
+ if (!signaling_thread_->IsCurrent()) {
+ ObserverMessageData msg(observer);
+ Send(MSG_REGISTER_OBSERVER, &msg);
+ return;
+ }
+ media_stream_impl_->RegisterObserver(observer);
+}
+
+void MediaStreamProxy::UnregisterObserver(ObserverInterface* observer) {
+ if (!signaling_thread_->IsCurrent()) {
+ ObserverMessageData msg(observer);
+ Send(MSG_UNREGISTER_OBSERVER, &msg);
+ return;
+ }
+ media_stream_impl_->UnregisterObserver(observer);
+}
+
+void MediaStreamProxy::Send(uint32 id, talk_base::MessageData* data) const {
+ signaling_thread_->Send(const_cast<MediaStreamProxy*>(this), id,
+ data);
+}
+
+// Implement MessageHandler
+void MediaStreamProxy::OnMessage(talk_base::Message* msg) {
+ talk_base::MessageData* data = msg->pdata;
+ switch (msg->message_id) {
+ case MSG_SET_TRACKLIST_IMPLEMENTATION: {
+ MediaStreamTrackListsMessageData* lists =
+ static_cast<MediaStreamTrackListsMessageData*>(data);
+ lists->audio_tracks_ = media_stream_impl_->audio_tracks();
+ lists->video_tracks_ = media_stream_impl_->video_tracks();
+ break;
+ }
+ case MSG_REGISTER_OBSERVER: {
+ ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+ media_stream_impl_->RegisterObserver(observer->data());
+ break;
+ }
+ case MSG_UNREGISTER_OBSERVER: {
+ ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+ media_stream_impl_->UnregisterObserver(observer->data());
+ break;
+ }
+ case MSG_LABEL: {
+ LabelMessageData * label = static_cast<LabelMessageData*>(data);
+ *(label->data()) = media_stream_impl_->label();
+ break;
+ }
+ case MSG_ADD_AUDIO_TRACK: {
+ AudioTrackMsgData * track =
+ static_cast<AudioTrackMsgData *>(data);
+ track->result_ = media_stream_impl_->AddTrack(track->track_.get());
+ break;
+ }
+ case MSG_ADD_VIDEO_TRACK: {
+ VideoTrackMsgData * track =
+ static_cast<VideoTrackMsgData *>(data);
+ track->result_ = media_stream_impl_->AddTrack(track->track_.get());
+ break;
+ }
+ case MSG_READY_STATE: {
+ ReadyStateMessageData* state = static_cast<ReadyStateMessageData*>(data);
+ state->data() = media_stream_impl_->ready_state();
+ break;
+ }
+ default:
+ ASSERT(!"Not Implemented!");
+ break;
+ }
+}
+
+template <class T>
+MediaStreamProxy::MediaStreamTrackListProxy<T>::MediaStreamTrackListProxy(
+ talk_base::Thread* signaling_thread)
+ : signaling_thread_(signaling_thread) {
+}
+
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::SetImplementation(
+ MediaStreamTrackListInterface<T>* track_list) {
+ track_list_ = track_list;
+}
+
+template <class T>
+size_t MediaStreamProxy::MediaStreamTrackListProxy<T>::count() {
+ if (!signaling_thread_->IsCurrent()) {
+ SizeTMessageData msg(0u);
+ Send(MSG_COUNT, &msg);
+ return msg.data();
+ }
+ return track_list_->count();
+}
+
+template <class T>
+T* MediaStreamProxy::MediaStreamTrackListProxy<T>::at(
+ size_t index) {
+ if (!signaling_thread_->IsCurrent()) {
+ MediaStreamTrackAtMessageData<T> msg(index);
+ Send(MSG_AT, &msg);
+ return msg.track_;
+ }
+ return track_list_->at(index);
+}
+
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::Send(
+ uint32 id, talk_base::MessageData* data) const {
+ signaling_thread_->Send(
+ const_cast<MediaStreamProxy::MediaStreamTrackListProxy<T>*>(
+ this), id, data);
+}
+
+// Implement MessageHandler
+template <class T>
+void MediaStreamProxy::MediaStreamTrackListProxy<T>::OnMessage(
+ talk_base::Message* msg) {
+ talk_base::MessageData* data = msg->pdata;
+ switch (msg->message_id) {
+ case MSG_COUNT: {
+ SizeTMessageData* count = static_cast<SizeTMessageData*>(data);
+ count->data() = track_list_->count();
+ break;
+ }
+ case MSG_AT: {
+ MediaStreamTrackAtMessageData<T>* track =
+ static_cast<MediaStreamTrackAtMessageData<T>*>(data);
+ track->track_ = track_list_->at(track->index_);
+ break;
+ }
+ default:
+ ASSERT(!"Not Implemented!");
+ break;
+ }
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamproxy.h b/talk/app/webrtc/mediastreamproxy.h
new file mode 100644
index 0000000..7d1068a
--- /dev/null
+++ b/talk/app/webrtc/mediastreamproxy.h
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/base/thread.h"
+
+namespace webrtc {
+using talk_base::scoped_refptr;
+
+// MediaStreamProxy is a proxy for the MediaStream interface. The purpose is
+// to make sure MediaStreamImpl is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote MediaStreams.
+class MediaStreamProxy : public LocalMediaStreamInterface,
+ public talk_base::MessageHandler {
+ public:
+ static scoped_refptr<MediaStreamProxy> Create(
+ const std::string& label,
+ talk_base::Thread* signaling_thread);
+
+ static scoped_refptr<MediaStreamProxy> Create(
+ const std::string& label,
+ talk_base::Thread* signaling_thread,
+ LocalMediaStreamInterface* media_stream_impl);
+
+ // Implement LocalStream.
+ virtual bool AddTrack(AudioTrackInterface* track);
+ virtual bool AddTrack(VideoTrackInterface* track);
+
+ // Implement MediaStream.
+ virtual std::string label() const;
+ virtual AudioTracks* audio_tracks() {
+ return audio_tracks_;
+ }
+ virtual VideoTracks* video_tracks() {
+ return video_tracks_;
+ }
+ virtual ReadyState ready_state();
+ virtual void set_ready_state(ReadyState new_state);
+
+ // Implement Notifier
+ virtual void RegisterObserver(ObserverInterface* observer);
+ virtual void UnregisterObserver(ObserverInterface* observer);
+
+ protected:
+ MediaStreamProxy(const std::string& label,
+ talk_base::Thread* signaling_thread,
+ LocalMediaStreamInterface* media_stream_impl);
+
+ template <class T>
+ class MediaStreamTrackListProxy : public MediaStreamTrackListInterface<T>,
+ public talk_base::MessageHandler {
+ public:
+ explicit MediaStreamTrackListProxy(talk_base::Thread* signaling_thread);
+
+ void SetImplementation(MediaStreamTrackListInterface<T>* track_list);
+ virtual size_t count();
+ virtual T* at(size_t index);
+
+ private:
+ void Send(uint32 id, talk_base::MessageData* data) const;
+ void OnMessage(talk_base::Message* msg);
+
+ talk_base::scoped_refptr<MediaStreamTrackListInterface<T> > track_list_;
+ mutable talk_base::Thread* signaling_thread_;
+ };
+ typedef MediaStreamTrackListProxy<AudioTrackInterface> AudioTrackListProxy;
+ typedef MediaStreamTrackListProxy<VideoTrackInterface> VideoTrackListProxy;
+
+ void Send(uint32 id, talk_base::MessageData* data) const;
+ // Implement MessageHandler.
+ virtual void OnMessage(talk_base::Message* msg);
+
+ mutable talk_base::Thread* signaling_thread_;
+ scoped_refptr<LocalMediaStreamInterface> media_stream_impl_;
+ scoped_refptr<AudioTrackListProxy> audio_tracks_;
+ scoped_refptr<VideoTrackListProxy> video_tracks_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAMPROXY_H_
diff --git a/talk/app/webrtc/mediastreamtrackproxy.cc b/talk/app/webrtc/mediastreamtrackproxy.cc
new file mode 100644
index 0000000..15b1d6d
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrackproxy.cc
@@ -0,0 +1,396 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+
+#include "talk/session/phone/videocapturer.h"
+
+namespace {
+
+enum {
+ MSG_REGISTER_OBSERVER = 1,
+ MSG_UNREGISTER_OBSERVER,
+ MSG_LABEL,
+ MSG_ENABLED,
+ MSG_SET_ENABLED,
+ MSG_STATE,
+ MSG_GET_AUDIODEVICE,
+ MSG_GET_VIDEODEVICE,
+ MSG_GET_VIDEORENDERER,
+ MSG_SET_VIDEORENDERER,
+};
+
+typedef talk_base::TypedMessageData<std::string*> LabelMessageData;
+typedef talk_base::TypedMessageData<webrtc::ObserverInterface*>
+ ObserverMessageData;
+typedef talk_base::TypedMessageData
+ <webrtc::MediaStreamTrackInterface::TrackState> TrackStateMessageData;
+typedef talk_base::TypedMessageData<bool> EnableMessageData;
+
+
+class AudioDeviceMessageData : public talk_base::MessageData {
+ public:
+ talk_base::scoped_refptr<webrtc::AudioDeviceModule> audio_device_;
+};
+
+class VideoDeviceMessageData : public talk_base::MessageData {
+ public:
+ cricket::VideoCapturer* video_device_;
+};
+
+class VideoRendererMessageData : public talk_base::MessageData {
+ public:
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ video_renderer_;
+};
+
+} // namespace anonymous
+
+namespace webrtc {
+
+template <class T>
+MediaStreamTrackProxy<T>::MediaStreamTrackProxy(
+ talk_base::Thread* signaling_thread)
+ : signaling_thread_(signaling_thread) {
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::Init(MediaStreamTrackInterface* track) {
+ track_ = track;
+}
+
+template <class T>
+std::string MediaStreamTrackProxy<T>::kind() const {
+ return track_->kind();
+}
+
+template <class T>
+std::string MediaStreamTrackProxy<T>::label() const {
+ if (!signaling_thread_->IsCurrent()) {
+ std::string label;
+ LabelMessageData msg(&label);
+ Send(MSG_LABEL, &msg);
+ return label;
+ }
+ return track_->label();
+}
+
+template <class T>
+MediaStreamTrackInterface::TrackState MediaStreamTrackProxy<T>::state() const {
+ if (!signaling_thread_->IsCurrent()) {
+ TrackStateMessageData msg(MediaStreamTrackInterface::kInitializing);
+ Send(MSG_STATE, &msg);
+ return msg.data();
+ }
+ return track_->state();
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::enabled() const {
+ if (!signaling_thread_->IsCurrent()) {
+ EnableMessageData msg(false);
+ Send(MSG_ENABLED, &msg);
+ return msg.data();
+ }
+ return track_->enabled();
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::set_enabled(bool enable) {
+ if (!signaling_thread_->IsCurrent()) {
+ EnableMessageData msg(enable);
+ Send(MSG_SET_ENABLED, &msg);
+ return msg.data();
+ }
+ return track_->set_enabled(enable);
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::set_state(
+ MediaStreamTrackInterface::TrackState new_state) {
+ if (!signaling_thread_->IsCurrent()) {
+ // State should only be allowed to be changed from the signaling thread.
+ ASSERT(!"Not Allowed!");
+ return false;
+ }
+ return track_->set_state(new_state);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::RegisterObserver(ObserverInterface* observer) {
+ if (!signaling_thread_->IsCurrent()) {
+ ObserverMessageData msg(observer);
+ Send(MSG_REGISTER_OBSERVER, &msg);
+ return;
+ }
+ track_->RegisterObserver(observer);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::UnregisterObserver(ObserverInterface* observer) {
+ if (!signaling_thread_->IsCurrent()) {
+ ObserverMessageData msg(observer);
+ Send(MSG_UNREGISTER_OBSERVER, &msg);
+ return;
+ }
+ track_->UnregisterObserver(observer);
+}
+
+template <class T>
+void MediaStreamTrackProxy<T>::Send(uint32 id,
+ talk_base::MessageData* data) const {
+ signaling_thread_->Send(const_cast<MediaStreamTrackProxy<T>*>(this), id,
+ data);
+}
+
+template <class T>
+bool MediaStreamTrackProxy<T>::HandleMessage(talk_base::Message* msg) {
+ talk_base::MessageData* data = msg->pdata;
+ switch (msg->message_id) {
+ case MSG_REGISTER_OBSERVER: {
+ ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+ track_->RegisterObserver(observer->data());
+ return true;
+ break;
+ }
+ case MSG_UNREGISTER_OBSERVER: {
+ ObserverMessageData* observer = static_cast<ObserverMessageData*>(data);
+ track_->UnregisterObserver(observer->data());
+ return true;
+ break;
+ }
+ case MSG_LABEL: {
+ LabelMessageData* label = static_cast<LabelMessageData*>(data);
+ *(label->data()) = track_->label();
+ return true;
+ }
+ case MSG_SET_ENABLED: {
+ EnableMessageData* enabled = static_cast<EnableMessageData*>(data);
+ enabled->data() = track_->set_enabled(enabled->data());
+ return true;
+ break;
+ }
+ case MSG_ENABLED: {
+ EnableMessageData* enabled = static_cast<EnableMessageData*>(data);
+ enabled->data() = track_->enabled();
+ return true;
+ break;
+ }
+ case MSG_STATE: {
+ TrackStateMessageData* state = static_cast<TrackStateMessageData*>(data);
+ state->data() = track_->state();
+ return true;
+ break;
+ }
+ default:
+ return false;
+ }
+}
+
+AudioTrackProxy::AudioTrackProxy(const std::string& label,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+ audio_track_(AudioTrack::CreateRemote(label)) {
+ Init(audio_track_);
+}
+
+AudioTrackProxy::AudioTrackProxy(const std::string& label,
+ AudioDeviceModule* audio_device,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+ audio_track_(AudioTrack::CreateLocal(label, audio_device)) {
+ Init(audio_track_);
+}
+
+AudioTrackProxy::AudioTrackProxy(LocalAudioTrackInterface* implementation,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalAudioTrackInterface>(signaling_thread),
+ audio_track_(implementation) {
+ Init(audio_track_);
+}
+
+talk_base::scoped_refptr<AudioTrackInterface> AudioTrackProxy::CreateRemote(
+ const std::string& label,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<AudioTrackProxy>* track =
+ new talk_base::RefCountedObject<AudioTrackProxy>(label, signaling_thread);
+ return track;
+}
+
+talk_base::scoped_refptr<LocalAudioTrackInterface> AudioTrackProxy::CreateLocal(
+ const std::string& label,
+ AudioDeviceModule* audio_device,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<AudioTrackProxy>* track =
+ new talk_base::RefCountedObject<AudioTrackProxy>(label,
+ audio_device,
+ signaling_thread);
+ return track;
+}
+
+talk_base::scoped_refptr<LocalAudioTrackInterface> AudioTrackProxy::CreateLocal(
+ LocalAudioTrackInterface* implementation,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<AudioTrackProxy>* track =
+ new talk_base::RefCountedObject<AudioTrackProxy>(implementation,
+ signaling_thread);
+ return track;
+}
+
+AudioDeviceModule* AudioTrackProxy::GetAudioDevice() {
+ if (!signaling_thread_->IsCurrent()) {
+ AudioDeviceMessageData msg;
+ Send(MSG_GET_AUDIODEVICE, &msg);
+ return msg.audio_device_;
+ }
+ return audio_track_->GetAudioDevice();
+}
+
+void AudioTrackProxy::OnMessage(talk_base::Message* msg) {
+ if (!MediaStreamTrackProxy<LocalAudioTrackInterface>::HandleMessage(msg)) {
+ if (msg->message_id == MSG_GET_AUDIODEVICE) {
+ AudioDeviceMessageData* audio_device =
+ static_cast<AudioDeviceMessageData*>(msg->pdata);
+ audio_device->audio_device_ = audio_track_->GetAudioDevice();
+ } else {
+ ASSERT(!"Not Implemented!");
+ }
+ }
+}
+
+VideoTrackProxy::VideoTrackProxy(const std::string& label,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+ video_track_(VideoTrack::CreateRemote(label)) {
+ Init(video_track_);
+}
+
+VideoTrackProxy::VideoTrackProxy(const std::string& label,
+ cricket::VideoCapturer* video_device,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+ video_track_(VideoTrack::CreateLocal(label, video_device)) {
+ Init(video_track_);
+}
+
+VideoTrackProxy::VideoTrackProxy(LocalVideoTrackInterface* implementation,
+ talk_base::Thread* signaling_thread)
+ : MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
+ video_track_(implementation) {
+ Init(video_track_);
+}
+
+talk_base::scoped_refptr<VideoTrackInterface> VideoTrackProxy::CreateRemote(
+ const std::string& label,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<VideoTrackProxy>* track =
+ new talk_base::RefCountedObject<VideoTrackProxy>(label, signaling_thread);
+ return track;
+}
+
+talk_base::scoped_refptr<LocalVideoTrackInterface> VideoTrackProxy::CreateLocal(
+ const std::string& label,
+ cricket::VideoCapturer* video_device,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<VideoTrackProxy>* track =
+ new talk_base::RefCountedObject<VideoTrackProxy>(label, video_device,
+ signaling_thread);
+ return track;
+}
+
+talk_base::scoped_refptr<LocalVideoTrackInterface> VideoTrackProxy::CreateLocal(
+ LocalVideoTrackInterface* implementation,
+ talk_base::Thread* signaling_thread) {
+ ASSERT(signaling_thread != NULL);
+ talk_base::RefCountedObject<VideoTrackProxy>* track =
+ new talk_base::RefCountedObject<VideoTrackProxy>(implementation,
+ signaling_thread);
+ return track;
+}
+
+cricket::VideoCapturer* VideoTrackProxy::GetVideoCapture() {
+ if (!signaling_thread_->IsCurrent()) {
+ VideoDeviceMessageData msg;
+ Send(MSG_GET_VIDEODEVICE, &msg);
+ return msg.video_device_;
+ }
+ return video_track_->GetVideoCapture();
+}
+
+void VideoTrackProxy::SetRenderer(VideoRendererWrapperInterface* renderer) {
+ if (!signaling_thread_->IsCurrent()) {
+ VideoRendererMessageData msg;
+ msg.video_renderer_ = renderer;
+ Send(MSG_SET_VIDEORENDERER, &msg);
+ return;
+ }
+ return video_track_->SetRenderer(renderer);
+}
+
+VideoRendererWrapperInterface* VideoTrackProxy::GetRenderer() {
+ if (!signaling_thread_->IsCurrent()) {
+ VideoRendererMessageData msg;
+ Send(MSG_GET_VIDEORENDERER, &msg);
+ return msg.video_renderer_;
+ }
+ return video_track_->GetRenderer();
+}
+
+void VideoTrackProxy::OnMessage(talk_base::Message* msg) {
+ if (!MediaStreamTrackProxy<LocalVideoTrackInterface>::HandleMessage(msg)) {
+ switch (msg->message_id) {
+ case MSG_GET_VIDEODEVICE: {
+ VideoDeviceMessageData* video_device =
+ static_cast<VideoDeviceMessageData*>(msg->pdata);
+ video_device->video_device_ = video_track_->GetVideoCapture();
+ break;
+ }
+ case MSG_GET_VIDEORENDERER: {
+ VideoRendererMessageData* video_renderer =
+ static_cast<VideoRendererMessageData*>(msg->pdata);
+ video_renderer->video_renderer_ = video_track_->GetRenderer();
+ break;
+ }
+ case MSG_SET_VIDEORENDERER: {
+ VideoRendererMessageData* video_renderer =
+ static_cast<VideoRendererMessageData*>(msg->pdata);
+ video_track_->SetRenderer(video_renderer->video_renderer_.get());
+ break;
+ }
+ default:
+ ASSERT(!"Not Implemented!");
+ break;
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamtrackproxy.h b/talk/app/webrtc/mediastreamtrackproxy.h
new file mode 100644
index 0000000..590d6ab
--- /dev/null
+++ b/talk/app/webrtc/mediastreamtrackproxy.h
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file includes proxy classes for tracks. The purpose is
+// to make sure tracks are only accessed from the signaling thread.
+
+#ifndef TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
+#define TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/audiotrackimpl.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/base/thread.h"
+
+namespace cricket {
+
+class VideoCapturer;
+
+} // namespace cricket
+
+namespace webrtc {
+
+template <class T>
+class MediaStreamTrackProxy : public T,
+ talk_base::MessageHandler {
+ public:
+ void Init(MediaStreamTrackInterface* track);
+ // Implement MediaStreamTrack.
+
+ virtual std::string kind() const;
+ virtual std::string label() const;
+ virtual bool enabled() const;
+ virtual MediaStreamTrackInterface::TrackState state() const;
+ virtual bool set_enabled(bool enable);
+ virtual bool set_state(MediaStreamTrackInterface::TrackState new_state);
+
+ // Implement Notifier
+ virtual void RegisterObserver(ObserverInterface* observer);
+ virtual void UnregisterObserver(ObserverInterface* observer);
+
+ protected:
+ explicit MediaStreamTrackProxy(talk_base::Thread* signaling_thread);
+
+ void Send(uint32 id, talk_base::MessageData* data) const;
+ // Returns true if the message is handled.
+ bool HandleMessage(talk_base::Message* msg);
+
+ mutable talk_base::Thread* signaling_thread_;
+ MediaStreamTrackInterface* track_;
+};
+
+// AudioTrackProxy is a proxy for the AudioTrackInterface. The purpose is
+// to make sure AudioTrack is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote audio tracks.
+class AudioTrackProxy : public MediaStreamTrackProxy<LocalAudioTrackInterface> {
+ public:
+ static talk_base::scoped_refptr<AudioTrackInterface> CreateRemote(
+ const std::string& label,
+ talk_base::Thread* signaling_thread);
+ static talk_base::scoped_refptr<LocalAudioTrackInterface> CreateLocal(
+ const std::string& label,
+ AudioDeviceModule* audio_device,
+ talk_base::Thread* signaling_thread);
+ static talk_base::scoped_refptr<LocalAudioTrackInterface> CreateLocal(
+ LocalAudioTrackInterface* implementation,
+ talk_base::Thread* signaling_thread);
+
+ virtual AudioDeviceModule* GetAudioDevice();
+
+ protected:
+ AudioTrackProxy(const std::string& label,
+ talk_base::Thread* signaling_thread);
+ AudioTrackProxy(const std::string& label,
+ AudioDeviceModule* audio_device,
+ talk_base::Thread* signaling_thread);
+ AudioTrackProxy(LocalAudioTrackInterface* implementation,
+ talk_base::Thread* signaling_thread);
+ // Implement MessageHandler
+ virtual void OnMessage(talk_base::Message* msg);
+
+ talk_base::scoped_refptr<LocalAudioTrackInterface> audio_track_;
+};
+
+// VideoTrackProxy is a proxy for the VideoTrackInterface and
+// LocalVideoTrackInterface. The purpose is
+// to make sure VideoTrack is only accessed from the signaling thread.
+// It can be used as a proxy for both local and remote video tracks.
+class VideoTrackProxy : public MediaStreamTrackProxy<LocalVideoTrackInterface> {
+ public:
+ static talk_base::scoped_refptr<VideoTrackInterface> CreateRemote(
+ const std::string& label,
+ talk_base::Thread* signaling_thread);
+ static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
+ const std::string& label,
+ cricket::VideoCapturer* video_device,
+ talk_base::Thread* signaling_thread);
+ static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
+ LocalVideoTrackInterface* implementation,
+ talk_base::Thread* signaling_thread);
+
+ virtual cricket::VideoCapturer* GetVideoCapture();
+ virtual void SetRenderer(VideoRendererWrapperInterface* renderer);
+ VideoRendererWrapperInterface* GetRenderer();
+
+ protected:
+ VideoTrackProxy(const std::string& label,
+ talk_base::Thread* signaling_thread);
+ VideoTrackProxy(const std::string& label,
+ cricket::VideoCapturer* video_device,
+ talk_base::Thread* signaling_thread);
+ VideoTrackProxy(LocalVideoTrackInterface* implementation,
+ talk_base::Thread* signaling_thread);
+
+ // Implement MessageHandler
+ virtual void OnMessage(talk_base::Message* msg);
+
+ talk_base::scoped_refptr<LocalVideoTrackInterface> video_track_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIASTREAMTRACKPROXY_H_
diff --git a/talk/app/webrtc/mediatrackimpl.h b/talk/app/webrtc/mediatrackimpl.h
new file mode 100644
index 0000000..e60801d
--- /dev/null
+++ b/talk/app/webrtc/mediatrackimpl.h
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_MEDIATRACKIMPL_H_
+#define TALK_APP_WEBRTC_MEDIATRACKIMPL_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/notifierimpl.h"
+
+namespace webrtc {
+
+// MediaTrack implements the interface common to AudioTrackInterface and
+// VideoTrackInterface.
+template <typename T>
+class MediaStreamTrack : public Notifier<T> {
+ public:
+ typedef typename T::TrackState TypedTrackState;
+
+ virtual std::string label() const { return label_; }
+ virtual MediaStreamTrackInterface::TrackState state() const {
+ return state_;
+ }
+ virtual bool enabled() const { return enabled_; }
+ virtual bool set_enabled(bool enable) {
+ bool fire_on_change = (enable != enabled_);
+ enabled_ = enable;
+ if (fire_on_change) {
+ Notifier<T>::FireOnChanged();
+ }
+ return fire_on_change;
+ }
+ virtual bool set_state(MediaStreamTrackInterface::TrackState new_state) {
+ bool fire_on_change = (state_ != new_state);
+ state_ = new_state;
+ if (fire_on_change)
+ Notifier<T>::FireOnChanged();
+ return true;
+ }
+
+ protected:
+ explicit MediaStreamTrack(const std::string& label)
+ : enabled_(true),
+ label_(label),
+ state_(MediaStreamTrackInterface::kInitializing) {
+ }
+
+ private:
+ bool enabled_;
+ std::string label_;
+ MediaStreamTrackInterface::TrackState state_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_MEDIATRACKIMPL_H_
diff --git a/talk/app/webrtc/notifierimpl.h b/talk/app/webrtc/notifierimpl.h
new file mode 100644
index 0000000..bd7c183
--- /dev/null
+++ b/talk/app/webrtc/notifierimpl.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_NOTIFIERIMPL_H_
+#define TALK_APP_WEBRTC_NOTIFIERIMPL_H_
+
+#include <list>
+
+#include "talk/base/common.h"
+#include "talk/app/webrtc/mediastream.h"
+
+namespace webrtc {
+
+// Implement a template version of a notifier.
+template <class T>
+class Notifier : public T {
+ public:
+ Notifier() {
+ }
+
+ virtual void RegisterObserver(ObserverInterface* observer) {
+ ASSERT(observer != NULL);
+ observers_.push_back(observer);
+ }
+
+ virtual void UnregisterObserver(ObserverInterface* observer) {
+ for (std::list<ObserverInterface*>::iterator it = observers_.begin();
+ it != observers_.end(); it++) {
+ if (*it == observer) {
+ observers_.erase(it);
+ break;
+ }
+ }
+ }
+
+ void FireOnChanged() {
+ for (std::list<ObserverInterface*>::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ (*it)-> OnChanged();
+ }
+ }
+
+ protected:
+ std::list<ObserverInterface*> observers_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_NOTIFIERIMPL_H_
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 62c2504..24a1cc4 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -25,107 +25,253 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+// This file contains the PeerConnection interface as defined in
+// http://dev.w3.org/2011/webrtc/editor/webrtc.html#peer-to-peer-connections.
+// Applications must use this interface to implement peerconnection.
+// PeerConnectionFactory class provides factory methods to create
+// peerconnection, mediastream and media tracks objects.
+//
+// The Following steps are needed to setup a typical call.
+// 1. Create a PeerConnectionFactoryInterface. Check constructors for more
+// information about input parameters.
+// 2. Create a PeerConnection object. Provide a configuration string which
+// points either to stun or turn server to generate ICE candidates and provide
+// an object that implements the PeerConnectionObserver interface.
+// Now PeerConnection will startcollecting ICE candidates.
+// 3. Create local MediaStream and MediaTracks using the PeerConnectionFactory
+// and add it to PeerConnection by calling AddStream.
+// 4. Once all mediastreams are added to peerconnection, call
+// CommitStreamChanges. Now PeerConnection starts generating an offer based on
+// the local mediastreams.
+// 5. When PeerConnection have generated the ICE candidates it will call the
+// observer OnSignalingMessage callback with the initial offer.
+// 6. When an Answer from peer received it must be supplied to the
+// PeerConnection by calling ProcessSignalingMessage.
+// At this point PeerConnection knows remote capabilities and ICE candidates.
+// Media will start flowing to the remote peer.
+
+// The Receiver of a call can decide to accept or reject the call.
+// This decision will be taken by the application not peerconnection.
+// If application decides to accept the call
+// 1. Create PeerConnectionFactoryInterface if it doesn't exist.
+// 2. Create new PeerConnection
+// 3. Provide the remote offer to the new PeerConnection object by calling
+// ProcessSignalingMessage.
+// 4. PeerConnection will call the observer function OnAddStream with remote
+// MediaStream and tracks information.
+// 5. PeerConnection will call the observer function OnSignalingMessage with
+// local ICE candidates in a answer message.
+// 6. Application can add it's own MediaStreams by calling AddStream.
+// When all streams have been added the application must call
+// CommitStreamChanges. Streams can be added at any time after the
+// PeerConnection object have been created.
+
#ifndef TALK_APP_WEBRTC_PEERCONNECTION_H_
#define TALK_APP_WEBRTC_PEERCONNECTION_H_
-// TODO - Add a factory class or some kind of PeerConnection manager
-// to support multiple PeerConnection object instantiation. This class will
-// create ChannelManager object and pass it to PeerConnection object. Otherwise
-// each PeerConnection object will have its own ChannelManager hence MediaEngine
-// and VoiceEngine/VideoEngine.
-
#include <string>
+#include <vector>
-namespace cricket {
-class VideoRenderer;
-}
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/base/socketaddress.h"
namespace talk_base {
class Thread;
}
-namespace webrtc {
+namespace cricket {
+class PortAllocator;
+}
+namespace webrtc {
+class VideoCaptureModule;
+
+// MediaStream container interface.
+class StreamCollectionInterface : public talk_base::RefCountInterface {
+ public:
+ virtual size_t count() = 0;
+ virtual MediaStreamInterface* at(size_t index) = 0;
+ virtual MediaStreamInterface* find(const std::string& label) = 0;
+ protected:
+ // Dtor protected as objects shouldn't be deleted via this interface.
+ ~StreamCollectionInterface() {}
+};
+
+// PeerConnection callback interface. Application should implement these
+// methods.
class PeerConnectionObserver {
public:
- // serialized signaling message
+ enum StateType {
+ kReadyState,
+ kIceState,
+ kSdpState,
+ };
+
+ virtual void OnError() = 0;
+
+ virtual void OnMessage(const std::string& msg) = 0;
+
+ // Serialized signaling message
virtual void OnSignalingMessage(const std::string& msg) = 0;
- // Triggered when a remote peer accepts a media connection.
- virtual void OnAddStream(const std::string& stream_id, bool video) = 0;
+ // Triggered when ReadyState, SdpState or IceState have changed.
+ virtual void OnStateChange(StateType state_changed) = 0;
- // Triggered when a remote peer closes a media stream.
- virtual void OnRemoveStream(const std::string& stream_id, bool video) = 0;
+ // Triggered when media is received on a new stream from remote peer.
+ virtual void OnAddStream(MediaStreamInterface* stream) = 0;
+
+ // Triggered when a remote peer close a stream.
+ virtual void OnRemoveStream(MediaStreamInterface* stream) = 0;
protected:
// Dtor protected as objects shouldn't be deleted via this interface.
~PeerConnectionObserver() {}
};
-class PeerConnection {
+
+class PeerConnectionInterface : public talk_base::RefCountInterface {
public:
enum ReadyState {
- NEW = 0,
- NEGOTIATING,
- ACTIVE,
- CLOSED,
+ kNew,
+ kNegotiating,
+ kActive,
+ kClosing,
+ kClosed,
};
- virtual ~PeerConnection() {}
+ enum SdpState {
+ kSdpNew,
+ kSdpIdle,
+ kSdpWaiting,
+ };
- // Register a listener
- virtual void RegisterObserver(PeerConnectionObserver* observer) = 0;
+ // Process a signaling message using the ROAP protocol.
+ virtual void ProcessSignalingMessage(const std::string& msg) = 0;
- // SignalingMessage in json format
- virtual bool SignalingMessage(const std::string& msg) = 0;
+ // Sends the msg over a data stream.
+ virtual bool Send(const std::string& msg) = 0;
- // Asynchronously adds a local stream device to the peer
- // connection.
- virtual bool AddStream(const std::string& stream_id, bool video) = 0;
+ // Accessor methods to active local streams.
+ virtual talk_base::scoped_refptr<StreamCollectionInterface>
+ local_streams() = 0;
- // Asynchronously removes a local stream device from the peer
- // connection. The operation is complete when
- // PeerConnectionObserver::OnRemoveStream is called.
- virtual bool RemoveStream(const std::string& stream_id) = 0;
+ // Accessor methods to remote streams.
+ virtual talk_base::scoped_refptr<StreamCollectionInterface>
+ remote_streams() = 0;
- // Info the peerconnection that it is time to return the signaling
- // information. The operation is complete when
- // PeerConnectionObserver::OnSignalingMessage is called.
- virtual bool Connect() = 0;
+ // Add a new local stream.
+ // This function does not trigger any changes to the stream until
+ // CommitStreamChanges is called.
+ virtual void AddStream(LocalMediaStreamInterface* stream) = 0;
- // Remove all the streams and tear down the session.
- // After the Close() is called, the OnSignalingMessage will be invoked
- // asynchronously. And before OnSignalingMessage is called,
- // OnRemoveStream will be called for each stream that was active.
- // TODO: Add an event such as onclose, or onreadystatechanged
- // when the readystate reaches the closed state (no more streams in the
- // peerconnection object.
- virtual bool Close() = 0;
+ // Remove a local stream and stop sending it.
+ // This function does not trigger any changes to the stream until
+ // CommitStreamChanges is called.
+ virtual void RemoveStream(LocalMediaStreamInterface* stream) = 0;
- // Set the audio input & output devices based on the given device name.
- // An empty device name means to use the default audio device.
- virtual bool SetAudioDevice(const std::string& wave_in_device,
- const std::string& wave_out_device,
- int opts) = 0;
+ // Commit Stream changes. This will start sending media on new streams
+ // and stop sending media on removed streams.
+ virtual void CommitStreamChanges() = 0;
- // Set the video renderer for the camera preview.
- virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer) = 0;
+ // Close the current session. This will trigger a Shutdown message
+ // being sent and the readiness state change to Closing.
+ // After calling this function no changes can be made to the sending streams.
+ virtual void Close() = 0;
- // Set the video renderer for the specified stream.
- virtual bool SetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer) = 0;
+ // Returns the current ReadyState.
+ virtual ReadyState ready_state() = 0;
- // Set video capture device
- // For Chromium the cam_device should use the capture session id.
- // For standalone app, cam_device is the camera name. It will try to
- // set the default capture device when cam_device is "".
- virtual bool SetVideoCapture(const std::string& cam_device) = 0;
+ // Returns the current SdpState.
+ virtual SdpState sdp_state() = 0;
- // Returns the state of the PeerConnection object. See the ReadyState
- // enum for valid values.
- virtual ReadyState GetReadyState() = 0;
+ protected:
+ // Dtor protected as objects shouldn't be deleted via this interface.
+ ~PeerConnectionInterface() {}
};
+// Helper function to create a new instance of cricket::VideoCapturer
+// from VideoCaptureModule.
+// TODO: This function should be removed once chrome implement video
+// capture as the cricket::VideoCapturer.
+cricket::VideoCapturer* CreateVideoCapturer(VideoCaptureModule* vcm);
+
+// Factory class used for creating cricket::PortAllocator that is used
+// for ICE negotiation.
+class PortAllocatorFactoryInterface : public talk_base::RefCountInterface {
+ public:
+ struct StunConfiguration {
+ StunConfiguration(const std::string& address, int port)
+ : server(address, port) {}
+ // STUN server address and port.
+ talk_base::SocketAddress server;
+ };
+
+ struct TurnConfiguration {
+ TurnConfiguration(const std::string& address,
+ int port,
+ const std::string& username,
+ const std::string& password)
+ : server(address, port),
+ username(username),
+ password(password) {}
+ talk_base::SocketAddress server;
+ std::string username;
+ std::string password;
+ };
+
+ virtual cricket::PortAllocator* CreatePortAllocator(
+ const std::vector<StunConfiguration>& stun_servers,
+ const std::vector<TurnConfiguration>& turn_configurations) = 0;
+
+ protected:
+ PortAllocatorFactoryInterface() {}
+ ~PortAllocatorFactoryInterface() {}
+};
+
+// PeerConnectionFactoryInterface is the factory interface use for creating
+// PeerConnection, MediaStream and media tracks.
+// PeerConnectionFactoryInterface will create required libjingle threads,
+// socket and network manager factory classes for networking.
+// If application decides to provide its own implementation of these classes
+// it should use alternate create method which accepts a threads and a
+// PortAllocatorFactoryInterface as input.
+class PeerConnectionFactoryInterface : public talk_base::RefCountInterface {
+ public:
+ virtual talk_base::scoped_refptr<PeerConnectionInterface>
+ CreatePeerConnection(const std::string& config,
+ PeerConnectionObserver* observer) = 0;
+
+ virtual talk_base::scoped_refptr<LocalMediaStreamInterface>
+ CreateLocalMediaStream(const std::string& label) = 0;
+
+ virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
+ CreateLocalVideoTrack(const std::string& label,
+ cricket::VideoCapturer* video_device) = 0;
+
+ virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
+ CreateLocalAudioTrack(const std::string& label,
+ AudioDeviceModule* audio_device) = 0;
+
+ protected:
+ // Dtor and ctor protected as objects shouldn't be created or deleted via
+ // this interface.
+ PeerConnectionFactoryInterface() {}
+ ~PeerConnectionFactoryInterface() {} // NOLINT
+};
+
+// Create a new instance of PeerConnectionFactoryInterface.
+talk_base::scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory();
+
+// Create a new instance of PeerConnectionFactoryInterface.
+// Ownership of the arguments are not transfered to this object and must
+// remain in scope for the lifetime of the PeerConnectionFactoryInterface.
+talk_base::scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(talk_base::Thread* worker_thread,
+ talk_base::Thread* signaling_thread,
+ PortAllocatorFactoryInterface* factory,
+ AudioDeviceModule* default_adm);
+
} // namespace webrtc
#endif // TALK_APP_WEBRTC_PEERCONNECTION_H_
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
new file mode 100644
index 0000000..2caa7c6
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -0,0 +1,351 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include <list>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/test/fakevideocapturemodule.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/fakevideorenderer.h"
+#include "talk/session/phone/videorenderer.h"
+
+void GetAllVideoTracks(webrtc::MediaStreamInterface* media_stream,
+ std::list<webrtc::VideoTrackInterface*>* video_tracks) {
+ webrtc::VideoTracks* track_list = media_stream->video_tracks();
+ for (size_t i = 0; i < track_list->count(); ++i) {
+ webrtc::VideoTrackInterface* track = track_list->at(i);
+ video_tracks->push_back(track);
+ }
+}
+
+class SignalingMessageReceiver {
+ public:
+ virtual void ReceiveMessage(const std::string& msg) = 0;
+
+ virtual int num_rendered_frames() = 0;
+
+ // Makes it possible for the remote side to decide when to start capturing.
+ // This makes it possible to wait with capturing until a renderer has been
+ // added.
+ virtual void StartCapturing() = 0;
+
+ protected:
+ SignalingMessageReceiver() {}
+ virtual ~SignalingMessageReceiver() {}
+};
+
+class PeerConnectionP2PTestClient
+ : public webrtc::PeerConnectionObserver,
+ public SignalingMessageReceiver {
+ public:
+ static PeerConnectionP2PTestClient* CreateClient(int id) {
+ PeerConnectionP2PTestClient* client = new PeerConnectionP2PTestClient(id);
+ if (!client->Init()) {
+ delete client;
+ return NULL;
+ }
+ return client;
+ }
+
+ ~PeerConnectionP2PTestClient() {
+ }
+
+ void StartSession() {
+ if (video_track_.get() != NULL) {
+ // Tracks have already been set up.
+ return;
+ }
+ // TODO: the default audio device module is used regardless of
+ // the second parameter to the CreateLocalAudioTrack(..) call. Maybe remove
+ // the second parameter from the API altogether?
+ talk_base::scoped_refptr<webrtc::LocalAudioTrackInterface> audio_track(
+ peer_connection_factory_->CreateLocalAudioTrack("audio_track", NULL));
+
+ CreateLocalVideoTrack();
+
+ talk_base::scoped_refptr<webrtc::LocalMediaStreamInterface> stream =
+ peer_connection_factory_->CreateLocalMediaStream("stream_label");
+
+ stream->AddTrack(audio_track);
+ stream->AddTrack(video_track_);
+
+ peer_connection_->AddStream(stream);
+ peer_connection_->CommitStreamChanges();
+ }
+
+ void StartCapturing() {
+ if (fake_video_capture_module_ != NULL) {
+ fake_video_capture_module_->StartCapturing();
+ }
+ }
+
+ bool SessionActive() {
+ return peer_connection_->ready_state() ==
+ webrtc::PeerConnectionInterface::kActive;
+ }
+
+ void StopSession() {
+ if (fake_video_capture_module_ != NULL) {
+ fake_video_capture_module_->StopCapturing();
+ }
+ // TODO: investigate why calling Close() triggers a crash when
+ // deleting the PeerConnection.
+ // peer_connection_->Close();
+ }
+
+ void set_signaling_message_receiver(
+ SignalingMessageReceiver* signaling_message_receiver) {
+ signaling_message_receiver_ = signaling_message_receiver;
+ }
+
+ bool FramesReceivedCheck(int number_of_frames) {
+ if (number_of_frames > signaling_message_receiver_->num_rendered_frames()) {
+ return false;
+ }
+ else {
+ EXPECT_LT(number_of_frames, fake_video_capture_module_->sent_frames());
+ }
+ return true;
+ }
+
+ // SignalingMessageReceiver callback.
+ virtual void ReceiveMessage(const std::string& msg) {
+ peer_connection_->ProcessSignalingMessage(msg);
+ }
+
+ virtual int num_rendered_frames() {
+ if (fake_video_renderer_ == NULL) {
+ return -1;
+ }
+ return fake_video_renderer_->num_rendered_frames();
+ }
+
+ // PeerConnectionObserver callbacks.
+ virtual void OnError() {}
+ virtual void OnMessage(const std::string&) {}
+ virtual void OnSignalingMessage(const std::string& msg) {
+ if (signaling_message_receiver_ == NULL) {
+ // Remote party may be deleted.
+ return;
+ }
+ signaling_message_receiver_->ReceiveMessage(msg);
+ }
+ virtual void OnStateChange(StateType /*state_changed*/) {}
+ virtual void OnAddStream(webrtc::MediaStreamInterface* media_stream) {
+ std::list<webrtc::VideoTrackInterface*> video_tracks;
+ GetAllVideoTracks(media_stream, &video_tracks);
+ int track_id = 0;
+ // Currently only one video track is supported.
+ // TODO: enable multiple video tracks.
+ EXPECT_EQ(1u, video_tracks.size());
+ for (std::list<webrtc::VideoTrackInterface*>::iterator iter =
+ video_tracks.begin();
+ iter != video_tracks.end();
+ ++iter) {
+ fake_video_renderer_ = new cricket::FakeVideoRenderer();
+ video_renderer_wrapper_ = webrtc::CreateVideoRenderer(
+ fake_video_renderer_);
+ (*iter)->SetRenderer(video_renderer_wrapper_);
+ track_id++;
+ }
+ // The video renderer has been added. Tell the far end to start capturing
+ // frames. That way the number of captured frames should be equal to number
+ // of rendered frames.
+ if (signaling_message_receiver_ != NULL) {
+ signaling_message_receiver_->StartCapturing();
+ return;
+ }
+ }
+ virtual void OnRemoveStream(webrtc::MediaStreamInterface* /*media_stream*/) {
+ }
+
+ private:
+ explicit PeerConnectionP2PTestClient(int id)
+ : id_(id),
+ fake_video_capture_module_(NULL),
+ fake_video_renderer_(NULL),
+ signaling_message_receiver_(NULL) {
+ }
+
+ bool Init() {
+ EXPECT_TRUE(peer_connection_.get() == NULL);
+ EXPECT_TRUE(peer_connection_factory_.get() == NULL);
+ peer_connection_factory_ = webrtc::CreatePeerConnectionFactory();
+ if (peer_connection_factory_.get() == NULL) {
+ return false;
+ }
+
+ const std::string server_configuration = "STUN stun.l.google.com:19302";
+ peer_connection_ = peer_connection_factory_->CreatePeerConnection(
+ server_configuration, this);
+ return peer_connection_.get() != NULL;
+ }
+
+ void GenerateRecordingFileName(int track, std::string* file_name) {
+ if (file_name == NULL) {
+ return;
+ }
+ std::stringstream file_name_stream;
+ file_name_stream << "p2p_test_client_" << id_ << "_videotrack_" << track <<
+ ".yuv";
+ file_name->clear();
+ *file_name = file_name_stream.str();
+ }
+
+ void CreateLocalVideoTrack() {
+ fake_video_capture_module_ = FakeVideoCaptureModule::Create(
+ talk_base::Thread::Current());
+ // TODO: Use FakeVideoCapturer instead of FakeVideoCaptureModule.
+ video_track_ = peer_connection_factory_->CreateLocalVideoTrack(
+ "video_track", CreateVideoCapturer(fake_video_capture_module_));
+ }
+
+ int id_;
+ talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+ talk_base::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+ peer_connection_factory_;
+
+ // Owns and ensures that fake_video_capture_module_ is available as long as
+ // this class exists. It also ensures destruction of the memory associated
+ // with it when this class is deleted.
+ talk_base::scoped_refptr<webrtc::LocalVideoTrackInterface> video_track_;
+ // Needed to keep track of number of frames send.
+ FakeVideoCaptureModule* fake_video_capture_module_;
+ // Ensures that fake_video_renderer_ is available as long as this class
+ // exists. It also ensures destruction of the memory associated with it when
+ // this class is deleted.
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ video_renderer_wrapper_;
+ // Needed to keep track of number of frames received.
+ cricket::FakeVideoRenderer* fake_video_renderer_;
+
+ // For remote peer communication.
+ SignalingMessageReceiver* signaling_message_receiver_;
+};
+
+class P2PTestConductor : public testing::Test {
+ public:
+ virtual void SetUp() {
+ EXPECT_TRUE(Init());
+ }
+ // Return true if session no longer is pending. I.e. if the session is active
+ // or failed.
+ bool ActivationNotPending() {
+ if (!IsInitialized()) {
+ return true;
+ }
+ return SessionActive();
+ }
+ bool SessionActive() {
+ return initiating_client_->SessionActive() &&
+ receiving_client_->SessionActive();
+ }
+ // Return true if the number of frames provided have been received or it is
+ // known that that will never occur (e.g. no frames will be sent or
+ // captured).
+ bool FramesNotPending(int frames_to_receive) {
+ if (!IsInitialized()) {
+ return true;
+ }
+ return FramesReceivedCheck(frames_to_receive);
+ }
+ bool FramesReceivedCheck(int frames_received) {
+ return initiating_client_->FramesReceivedCheck(frames_received) &&
+ receiving_client_->FramesReceivedCheck(frames_received);
+ }
+ ~P2PTestConductor() {
+ if (initiating_client_.get() != NULL) {
+ initiating_client_->set_signaling_message_receiver(NULL);
+ }
+ if (receiving_client_.get() != NULL) {
+ receiving_client_->set_signaling_message_receiver(NULL);
+ }
+ }
+
+ bool StartSession() {
+ if (!IsInitialized()) {
+ return false;
+ }
+ initiating_client_->StartSession();
+ receiving_client_->StartSession();
+ return true;
+ }
+
+ bool StopSession() {
+ if (!IsInitialized()) {
+ return false;
+ }
+ initiating_client_->StopSession();
+ receiving_client_->StopSession();
+ return true;
+ }
+
+ private:
+ bool Init() {
+ initiating_client_.reset(PeerConnectionP2PTestClient::CreateClient(0));
+ receiving_client_.reset(PeerConnectionP2PTestClient::CreateClient(1));
+ if ((initiating_client_.get() == NULL) ||
+ (receiving_client_.get() == NULL)) {
+ return false;
+ }
+ initiating_client_->set_signaling_message_receiver(receiving_client_.get());
+ receiving_client_->set_signaling_message_receiver(initiating_client_.get());
+ return true;
+ }
+ bool IsInitialized() const {
+ return (initiating_client_.get() != NULL) &&
+ (receiving_client_.get() != NULL);
+ }
+
+ talk_base::scoped_ptr<PeerConnectionP2PTestClient> initiating_client_;
+ talk_base::scoped_ptr<PeerConnectionP2PTestClient> receiving_client_;
+};
+
+// This test sets up a call between two parties. Both parties send static frames
+// to each other. Once the test is finished the number of sent frames is
+// compared to the number of received frames.
+TEST_F(P2PTestConductor, LocalP2PTest) {
+ EXPECT_TRUE(StartSession());
+ const int kMaxWaitForActivationMs = 5000;
+ EXPECT_TRUE_WAIT(ActivationNotPending(), kMaxWaitForActivationMs);
+ EXPECT_TRUE(SessionActive());
+
+ // TODO - These are failing on Windows dbg paricular on pulse.
+ // removing check now.
+#if 0
+ const int kEndFrameCount = 10;
+ const int kMaxWaitForFramesMs = 5000;
+ EXPECT_TRUE_WAIT(FramesNotPending(kEndFrameCount), kMaxWaitForFramesMs);
+ EXPECT_TRUE(FramesReceivedCheck(kEndFrameCount));
+#endif
+ EXPECT_TRUE(StopSession());
+}
diff --git a/talk/app/webrtc/peerconnectionfactory_unittest.cc b/talk/app/webrtc/peerconnectionfactory_unittest.cc
new file mode 100644
index 0000000..5056e17
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory_unittest.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnectionfactoryimpl.h"
+#include "talk/app/webrtc/fakeportallocatorfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/webrtccommon.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+static const char kStunConfiguration[] = "STUN stun.l.google.com:19302";
+
+namespace webrtc {
+
+class NullPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+ virtual void OnError() {}
+ virtual void OnMessage(const std::string& msg) {}
+ virtual void OnSignalingMessage(const std::string& msg) {}
+ virtual void OnStateChange(StateType state_changed) {}
+ virtual void OnAddStream(MediaStreamInterface* stream) {}
+ virtual void OnRemoveStream(MediaStreamInterface* stream) {}
+};
+
+TEST(PeerConnectionFactory, CreatePCUsingInternalModules) {
+ talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory(
+ CreatePeerConnectionFactory());
+ ASSERT_TRUE(factory.get() != NULL);
+
+ NullPeerConnectionObserver observer;
+ talk_base::scoped_refptr<PeerConnectionInterface> pc(
+ factory->CreatePeerConnection(kStunConfiguration, &observer));
+
+ EXPECT_TRUE(pc.get() != NULL);
+}
+
+TEST(PeerConnectionFactory, CreatePCUsingExternalModules) {
+ talk_base::scoped_refptr<PortAllocatorFactoryInterface> allocator_factory(
+ FakePortAllocatorFactory::Create());
+
+ talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory =
+ CreatePeerConnectionFactory(talk_base::Thread::Current(),
+ talk_base::Thread::Current(),
+ allocator_factory.get(),
+ NULL);
+ ASSERT_TRUE(factory.get() != NULL);
+
+ NullPeerConnectionObserver observer;
+ talk_base::scoped_refptr<PeerConnectionInterface> pc(
+ factory->CreatePeerConnection(kStunConfiguration, &observer));
+ EXPECT_TRUE(pc.get() != NULL);
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.cc b/talk/app/webrtc/peerconnectionfactoryimpl.cc
new file mode 100644
index 0000000..e74e16e
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.cc
@@ -0,0 +1,252 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/peerconnectionfactoryimpl.h"
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtc/portallocatorfactory.h"
+#include "talk/session/phone/dummydevicemanager.h"
+#include "talk/session/phone/webrtcmediaengine.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/audio_device/main/interface/audio_device.h"
+#else
+#include "third_party/webrtc/files/include/audio_device.h"
+#endif
+
+using talk_base::scoped_refptr;
+
+namespace {
+
+typedef talk_base::TypedMessageData<bool> InitMessageData;
+
+struct CreatePeerConnectionParams : public talk_base::MessageData {
+ CreatePeerConnectionParams(const std::string& configuration,
+ webrtc::PeerConnectionObserver* observer)
+ : configuration(configuration), observer(observer) {
+ }
+ scoped_refptr<webrtc::PeerConnectionInterface> peerconnection;
+ const std::string& configuration;
+ webrtc::PeerConnectionObserver* observer;
+};
+
+enum {
+ MSG_INIT_FACTORY = 1,
+ MSG_TERMINATE_FACTORY = 2,
+ MSG_CREATE_PEERCONNECTION = 3,
+};
+
+} // namespace
+
+namespace webrtc {
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory() {
+ talk_base::RefCountedObject<PeerConnectionFactory>* pc_factory =
+ new talk_base::RefCountedObject<PeerConnectionFactory>();
+
+ if (!pc_factory->Initialize()) {
+ delete pc_factory;
+ pc_factory = NULL;
+ }
+ return pc_factory;
+}
+
+scoped_refptr<PeerConnectionFactoryInterface>
+CreatePeerConnectionFactory(talk_base::Thread* worker_thread,
+ talk_base::Thread* signaling_thread,
+ PortAllocatorFactoryInterface* factory,
+ AudioDeviceModule* default_adm) {
+ talk_base::RefCountedObject<PeerConnectionFactory>* pc_factory =
+ new talk_base::RefCountedObject<PeerConnectionFactory>(
+ worker_thread, signaling_thread, factory, default_adm);
+ if (!pc_factory->Initialize()) {
+ delete pc_factory;
+ pc_factory = NULL;
+ }
+ return pc_factory;
+}
+
+PeerConnectionFactory::PeerConnectionFactory()
+ : owns_ptrs_(true),
+ signaling_thread_(new talk_base::Thread),
+ worker_thread_(new talk_base::Thread) {
+ bool result = signaling_thread_->Start();
+ ASSERT(result);
+ result = worker_thread_->Start();
+ ASSERT(result);
+}
+
+PeerConnectionFactory::PeerConnectionFactory(
+ talk_base::Thread* worker_thread,
+ talk_base::Thread* signaling_thread,
+ PortAllocatorFactoryInterface* port_allocator_factory,
+ AudioDeviceModule* default_adm)
+ : owns_ptrs_(false),
+ signaling_thread_(signaling_thread),
+ worker_thread_(worker_thread),
+ allocator_factory_(port_allocator_factory),
+ default_adm_(default_adm) {
+ ASSERT(worker_thread != NULL);
+ ASSERT(signaling_thread != NULL);
+ ASSERT(allocator_factory_.get() != NULL);
+ // TODO: Currently there is no way creating an external adm in
+ // libjingle source tree. So we can 't currently assert if this is NULL.
+ // ASSERT(default_adm != NULL);
+}
+
+PeerConnectionFactory::~PeerConnectionFactory() {
+ signaling_thread_->Clear(this);
+ signaling_thread_->Send(this, MSG_TERMINATE_FACTORY);
+ if (owns_ptrs_) {
+ delete signaling_thread_;
+ delete worker_thread_;
+ }
+}
+
+bool PeerConnectionFactory::Initialize() {
+ InitMessageData result(false);
+ signaling_thread_->Send(this, MSG_INIT_FACTORY, &result);
+ return result.data();
+}
+
+void PeerConnectionFactory::OnMessage(talk_base::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_INIT_FACTORY: {
+ InitMessageData* pdata = static_cast<InitMessageData*> (msg->pdata);
+ pdata->data() = Initialize_s();
+ break;
+ }
+ case MSG_TERMINATE_FACTORY: {
+ Terminate_s();
+ break;
+ }
+ case MSG_CREATE_PEERCONNECTION: {
+ CreatePeerConnectionParams* pdata =
+ static_cast<CreatePeerConnectionParams*> (msg->pdata);
+ pdata->peerconnection = CreatePeerConnection_s(pdata->configuration,
+ pdata->observer);
+ break;
+ }
+ }
+}
+
+bool PeerConnectionFactory::Initialize_s() {
+ if (owns_ptrs_) {
+ allocator_factory_ = PortAllocatorFactory::Create(worker_thread_);
+ if (allocator_factory_.get() == NULL)
+ return false;
+ }
+
+ cricket::DummyDeviceManager* device_manager(
+ new cricket::DummyDeviceManager());
+ // TODO: Need to make sure only one VoE is created inside
+ // WebRtcMediaEngine.
+ cricket::WebRtcMediaEngine* webrtc_media_engine(
+ new cricket::WebRtcMediaEngine(default_adm_.get(),
+ NULL, // No secondary adm.
+ NULL)); // No vcm available.
+
+ channel_manager_.reset(new cricket::ChannelManager(
+ webrtc_media_engine, device_manager, worker_thread_));
+ if (!channel_manager_->Init()) {
+ return false;
+ }
+ return true;
+}
+
+// Terminate what we created on the signaling thread.
+void PeerConnectionFactory::Terminate_s() {
+ channel_manager_.reset(NULL);
+ if (owns_ptrs_) {
+ allocator_factory_ = NULL;
+ }
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection(
+ const std::string& configuration,
+ PeerConnectionObserver* observer) {
+ CreatePeerConnectionParams params(configuration, observer);
+ signaling_thread_->Send(this, MSG_CREATE_PEERCONNECTION, ¶ms);
+ return params.peerconnection;
+}
+
+scoped_refptr<PeerConnectionInterface>
+PeerConnectionFactory::CreatePeerConnection_s(
+ const std::string& configuration,
+ PeerConnectionObserver* observer) {
+ talk_base::RefCountedObject<PeerConnection>* pc(
+ new talk_base::RefCountedObject<PeerConnection>(this));
+ if (!pc->Initialize(configuration, observer)) {
+ delete pc;
+ pc = NULL;
+ }
+ return pc;
+}
+
+scoped_refptr<LocalMediaStreamInterface>
+PeerConnectionFactory::CreateLocalMediaStream(
+ const std::string& label) {
+ return MediaStreamProxy::Create(label, signaling_thread_);
+}
+
+scoped_refptr<LocalVideoTrackInterface>
+PeerConnectionFactory::CreateLocalVideoTrack(
+ const std::string& label,
+ cricket::VideoCapturer* video_device) {
+ return VideoTrackProxy::CreateLocal(label, video_device,
+ signaling_thread_);
+}
+
+scoped_refptr<LocalAudioTrackInterface>
+PeerConnectionFactory::CreateLocalAudioTrack(
+ const std::string& label,
+ AudioDeviceModule* audio_device) {
+ return AudioTrackProxy::CreateLocal(label, audio_device,
+ signaling_thread_);
+}
+
+cricket::ChannelManager* PeerConnectionFactory::channel_manager() {
+ return channel_manager_.get();
+}
+
+talk_base::Thread* PeerConnectionFactory::signaling_thread() {
+ return signaling_thread_;
+}
+
+talk_base::Thread* PeerConnectionFactory::worker_thread() {
+ return worker_thread_;
+}
+
+PortAllocatorFactoryInterface* PeerConnectionFactory::port_allocator_factory() {
+ return allocator_factory_.get();
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.h b/talk/app/webrtc/peerconnectionfactoryimpl.h
new file mode 100644
index 0000000..ba22d47
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
+
+#include <string>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
+
+namespace webrtc {
+
+class PeerConnectionFactory : public PeerConnectionFactoryInterface,
+ public talk_base::MessageHandler {
+ public:
+ talk_base::scoped_refptr<PeerConnectionInterface> CreatePeerConnection(
+ const std::string& config,
+ PeerConnectionObserver* observer);
+ bool Initialize();
+
+ virtual talk_base::scoped_refptr<LocalMediaStreamInterface>
+ CreateLocalMediaStream(const std::string& label);
+
+ virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
+ CreateLocalVideoTrack(const std::string& label,
+ cricket::VideoCapturer* video_device);
+
+ virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
+ CreateLocalAudioTrack(const std::string& label,
+ AudioDeviceModule* audio_device);
+
+ virtual cricket::ChannelManager* channel_manager();
+ virtual talk_base::Thread* signaling_thread();
+ virtual talk_base::Thread* worker_thread();
+ virtual PortAllocatorFactoryInterface* port_allocator_factory();
+
+ protected:
+ PeerConnectionFactory();
+ PeerConnectionFactory(
+ talk_base::Thread* worker_thread,
+ talk_base::Thread* signaling_thread,
+ PortAllocatorFactoryInterface* port_allocator_factory,
+ AudioDeviceModule* default_adm);
+ virtual ~PeerConnectionFactory();
+
+
+ private:
+ bool Initialize_s();
+ void Terminate_s();
+ talk_base::scoped_refptr<PeerConnectionInterface> CreatePeerConnection_s(
+ const std::string& configuration,
+ PeerConnectionObserver* observer);
+ // Implements talk_base::MessageHandler.
+ void OnMessage(talk_base::Message* msg);
+
+ bool owns_ptrs_;
+ talk_base::Thread* signaling_thread_;
+ talk_base::Thread* worker_thread_;
+ talk_base::scoped_refptr<PortAllocatorFactoryInterface> allocator_factory_;
+ // External Audio device used for audio playback.
+ talk_base::scoped_refptr<AudioDeviceModule> default_adm_;
+ talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PEERCONNECTIONFACTORYIMPL_H_
diff --git a/talk/app/webrtc/peerconnectionimpl.cc b/talk/app/webrtc/peerconnectionimpl.cc
index 28e4685..7f990f1 100644
--- a/talk/app/webrtc/peerconnectionimpl.cc
+++ b/talk/app/webrtc/peerconnectionimpl.cc
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -27,199 +27,366 @@
#include "talk/app/webrtc/peerconnectionimpl.h"
-#include "talk/app/webrtc/webrtcjson.h"
-#include "talk/app/webrtc/webrtcsession.h"
-#include "talk/base/basicpacketsocketfactory.h"
-#include "talk/base/helpers.h"
+#include <vector>
+
+#include "talk/app/webrtc/mediastreamhandler.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
-#include "talk/p2p/base/session.h"
-#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+namespace {
+
+// The number of the tokens in the config string.
+static const size_t kConfigTokens = 2;
+static const size_t kServiceCount = 5;
+// The default stun port.
+static const int kDefaultPort = 3478;
+
+// NOTE: Must be in the same order as the ServiceType enum.
+static const char* kValidServiceTypes[kServiceCount] = {
+ "STUN", "STUNS", "TURN", "TURNS", "INVALID" };
+
+enum ServiceType {
+ STUN, // Indicates a STUN server.
+ STUNS, // Indicates a STUN server used with a TLS session.
+ TURN, // Indicates a TURN server
+ TURNS, // Indicates a TURN server used with a TLS session.
+ INVALID, // Unknown.
+};
+
+enum {
+ MSG_COMMITSTREAMCHANGES = 1,
+ MSG_PROCESSSIGNALINGMESSAGE = 2,
+ MSG_RETURNREMOTEMEDIASTREAMS = 3,
+ MSG_CLOSE = 4,
+ MSG_READYSTATE = 5,
+ MSG_SDPSTATE = 6,
+ MSG_TERMINATE = 7
+};
+
+typedef webrtc::PortAllocatorFactoryInterface::StunConfiguration
+ StunConfiguration;
+typedef webrtc::PortAllocatorFactoryInterface::TurnConfiguration
+ TurnConfiguration;
+
+bool static ParseConfigString(const std::string& config,
+ std::vector<StunConfiguration>* stun_config,
+ std::vector<TurnConfiguration>* turn_config) {
+ std::vector<std::string> tokens;
+ talk_base::tokenize(config, ' ', &tokens);
+
+ if (tokens.size() != kConfigTokens) {
+ LOG(WARNING) << "Invalid config string";
+ return false;
+ }
+
+ ServiceType service_type = INVALID;
+
+ const std::string& type = tokens[0];
+ for (size_t i = 0; i < kServiceCount; ++i) {
+ if (type.compare(kValidServiceTypes[i]) == 0) {
+ service_type = static_cast<ServiceType>(i);
+ break;
+ }
+ }
+
+ if (service_type == INVALID) {
+ LOG(WARNING) << "Invalid service type: " << type;
+ return false;
+ }
+ std::string service_address = tokens[1];
+
+ int port;
+ tokens.clear();
+ talk_base::tokenize(service_address, ':', &tokens);
+ if (tokens.size() != kConfigTokens) {
+ port = kDefaultPort;
+ } else {
+ port = talk_base::FromString<int>(tokens[1]);
+ if (port <= 0 || port > 0xffff) {
+ LOG(WARNING) << "Invalid port: " << tokens[1];
+ return false;
+ }
+ }
+
+ // TODO: Currently the specification does not tell us how to parse
+ // multiple addresses, username and password from the configuration string.
+ switch (service_type) {
+ case STUN:
+ stun_config->push_back(StunConfiguration(service_address, port));
+ break;
+ case TURN:
+ turn_config->push_back(TurnConfiguration(service_address, port, "", ""));
+ break;
+ case TURNS:
+ case STUNS:
+ case INVALID:
+ default:
+ LOG(WARNING) << "Configuration not supported";
+ return false;
+ }
+ return true;
+}
+
+struct SignalingParams : public talk_base::MessageData {
+ SignalingParams(const std::string& msg,
+ webrtc::StreamCollectionInterface* local_streams)
+ : msg(msg),
+ local_streams(local_streams) {}
+ const std::string msg;
+ talk_base::scoped_refptr<webrtc::StreamCollectionInterface> local_streams;
+};
+
+struct StreamCollectionParams : public talk_base::MessageData {
+ explicit StreamCollectionParams(webrtc::StreamCollectionInterface* streams)
+ : streams(streams) {}
+ talk_base::scoped_refptr<webrtc::StreamCollectionInterface> streams;
+};
+
+struct ReadyStateMessage : public talk_base::MessageData {
+ ReadyStateMessage() : state(webrtc::PeerConnectionInterface::kNew) {}
+ webrtc::PeerConnectionInterface::ReadyState state;
+};
+
+struct SdpStateMessage : public talk_base::MessageData {
+ SdpStateMessage() : state(webrtc::PeerConnectionInterface::kSdpNew) {}
+ webrtc::PeerConnectionInterface::SdpState state;
+};
+
+} // namespace
namespace webrtc {
-
-PeerConnectionImpl::PeerConnectionImpl(
- cricket::PortAllocator* port_allocator,
- cricket::ChannelManager* channel_manager,
- talk_base::Thread* signaling_thread)
- : port_allocator_(port_allocator),
- channel_manager_(channel_manager),
- signaling_thread_(signaling_thread),
- event_callback_(NULL),
- session_(NULL) {
+cricket::VideoCapturer* CreateVideoCapturer(VideoCaptureModule* vcm) {
+ cricket::WebRtcVideoCapturer* video_capturer =
+ new cricket::WebRtcVideoCapturer;
+ if (!video_capturer->Init(vcm)) {
+ delete video_capturer;
+ video_capturer = NULL;
+ }
+ return video_capturer;
}
-PeerConnectionImpl::~PeerConnectionImpl() {
+PeerConnection::PeerConnection(PeerConnectionFactory* factory)
+ : factory_(factory),
+ observer_(NULL),
+ ready_state_(kNew),
+ sdp_state_(kSdpNew),
+ local_media_streams_(StreamCollection::Create()) {
}
-bool PeerConnectionImpl::Init() {
- std::string sid;
- talk_base::CreateRandomString(8, &sid);
- const bool incoming = false;
- // default outgoing direction
- session_.reset(CreateMediaSession(sid, incoming));
- if (session_.get() == NULL) {
- ASSERT(false && "failed to initialize a session");
+PeerConnection::~PeerConnection() {
+ signaling_thread()->Clear(this);
+ signaling_thread()->Send(this, MSG_TERMINATE);
+}
+
+// Clean up what needs to be cleaned up on the signaling thread.
+void PeerConnection::Terminate_s() {
+ stream_handler_.reset();
+ signaling_.reset();
+ session_.reset();
+ port_allocator_.reset();
+}
+
+bool PeerConnection::Initialize(const std::string& configuration,
+ PeerConnectionObserver* observer) {
+ ASSERT(observer != NULL);
+ if (!observer)
return false;
- }
- return true;
+ observer_ = observer;
+ std::vector<PortAllocatorFactoryInterface::StunConfiguration> stun_config;
+ std::vector<PortAllocatorFactoryInterface::TurnConfiguration> turn_config;
+
+ ParseConfigString(configuration, &stun_config, &turn_config);
+
+ port_allocator_.reset(factory_->port_allocator_factory()->CreatePortAllocator(
+ stun_config, turn_config));
+
+ session_.reset(new WebRtcSession(factory_->channel_manager(),
+ factory_->signaling_thread(),
+ factory_->worker_thread(),
+ port_allocator_.get()));
+ signaling_.reset(new PeerConnectionSignaling(factory_->signaling_thread(),
+ session_.get()));
+ stream_handler_.reset(new MediaStreamHandlers(session_.get()));
+
+ signaling_->SignalNewPeerConnectionMessage.connect(
+ this, &PeerConnection::OnNewPeerConnectionMessage);
+ signaling_->SignalRemoteStreamAdded.connect(
+ this, &PeerConnection::OnRemoteStreamAdded);
+ signaling_->SignalRemoteStreamRemoved.connect(
+ this, &PeerConnection::OnRemoteStreamRemoved);
+ signaling_->SignalStateChange.connect(
+ this, &PeerConnection::OnSignalingStateChange);
+ // Register with WebRtcSession
+ session_->RegisterObserver(signaling_.get());
+
+ // Initialize the WebRtcSession. It creates transport channels etc.
+ const bool result = session_->Initialize();
+ if (result)
+ ChangeReadyState(PeerConnectionInterface::kNegotiating);
+ return result;
}
-void PeerConnectionImpl::RegisterObserver(PeerConnectionObserver* observer) {
- // This assert is to catch cases where two observer pointers are registered.
- // We only support one and if another is to be used, the current one must be
- // cleared first.
- ASSERT(observer == NULL || event_callback_ == NULL);
- event_callback_ = observer;
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::local_streams() {
+ return local_media_streams_;
}
-bool PeerConnectionImpl::SignalingMessage(
- const std::string& signaling_message) {
- // Deserialize signaling message
- cricket::SessionDescription* incoming_sdp = NULL;
- std::vector<cricket::Candidate> candidates;
- if (!ParseJsonSignalingMessage(signaling_message,
- &incoming_sdp, &candidates)) {
- return false;
- }
-
- bool ret = false;
- if (GetReadyState() == NEW) {
- // set direction to incoming, as message received first
- session_->set_incoming(true);
- ret = session_->OnInitiateMessage(incoming_sdp, candidates);
- } else {
- ret = session_->OnRemoteDescription(incoming_sdp, candidates);
- }
- return ret;
+talk_base::scoped_refptr<StreamCollectionInterface>
+PeerConnection::remote_streams() {
+ StreamCollectionParams msg(NULL);
+ signaling_thread()->Send(this, MSG_RETURNREMOTEMEDIASTREAMS, &msg);
+ return msg.streams;
}
-WebRtcSession* PeerConnectionImpl::CreateMediaSession(
- const std::string& id, bool incoming) {
- ASSERT(port_allocator_ != NULL);
- WebRtcSession* session = new WebRtcSession(id, incoming,
- port_allocator_, channel_manager_, signaling_thread_);
-
- if (session->Initiate()) {
- session->SignalAddStream.connect(
- this,
- &PeerConnectionImpl::OnAddStream);
- session->SignalRemoveStream.connect(
- this,
- &PeerConnectionImpl::OnRemoveStream);
- session->SignalLocalDescription.connect(
- this,
- &PeerConnectionImpl::OnLocalDescription);
- session->SignalFailedCall.connect(
- this,
- &PeerConnectionImpl::OnFailedCall);
- } else {
- delete session;
- session = NULL;
- }
- return session;
+void PeerConnection::ProcessSignalingMessage(const std::string& msg) {
+ SignalingParams* parameter(new SignalingParams(
+ msg, StreamCollection::Create(local_media_streams_)));
+ signaling_thread()->Post(this, MSG_PROCESSSIGNALINGMESSAGE, parameter);
}
-bool PeerConnectionImpl::AddStream(const std::string& stream_id, bool video) {
- bool ret = false;
- if (session_->HasStream(stream_id)) {
- ASSERT(false && "A stream with this name already exists");
- } else {
- if (!video) {
- ret = !session_->HasAudioChannel() &&
- session_->CreateVoiceChannel(stream_id);
- } else {
- ret = !session_->HasVideoChannel() &&
- session_->CreateVideoChannel(stream_id);
+void PeerConnection::AddStream(LocalMediaStreamInterface* local_stream) {
+ local_media_streams_->AddStream(local_stream);
+}
+
+void PeerConnection::RemoveStream(LocalMediaStreamInterface* remove_stream) {
+ local_media_streams_->RemoveStream(remove_stream);
+}
+
+void PeerConnection::CommitStreamChanges() {
+ StreamCollectionParams* msg(new StreamCollectionParams(
+ StreamCollection::Create(local_media_streams_)));
+ signaling_thread()->Post(this, MSG_COMMITSTREAMCHANGES, msg);
+}
+
+void PeerConnection::Close() {
+ signaling_thread()->Send(this, MSG_CLOSE);
+}
+
+PeerConnectionInterface::ReadyState PeerConnection::ready_state() {
+ ReadyStateMessage msg;
+ signaling_thread()->Send(this, MSG_READYSTATE, &msg);
+ return msg.state;
+}
+
+PeerConnectionInterface::SdpState PeerConnection::sdp_state() {
+ SdpStateMessage msg;
+ signaling_thread()->Send(this, MSG_SDPSTATE, &msg);
+ return msg.state;
+}
+
+void PeerConnection::OnMessage(talk_base::Message* msg) {
+ talk_base::MessageData* data = msg->pdata;
+ switch (msg->message_id) {
+ case MSG_COMMITSTREAMCHANGES: {
+ if (ready_state_ != PeerConnectionInterface::kClosed ||
+ ready_state_ != PeerConnectionInterface::kClosing) {
+ StreamCollectionParams* param(
+ static_cast<StreamCollectionParams*> (data));
+ signaling_->CreateOffer(param->streams);
+ stream_handler_->CommitLocalStreams(param->streams);
+ }
+ delete data; // Because it is Posted.
+ break;
}
- }
- return ret;
-}
-
-bool PeerConnectionImpl::RemoveStream(const std::string& stream_id) {
- return session_->RemoveStream(stream_id);
-}
-
-void PeerConnectionImpl::OnLocalDescription(
- const cricket::SessionDescription* desc,
- const std::vector<cricket::Candidate>& candidates) {
- if (!desc) {
- LOG(WARNING) << "no local SDP ";
- return;
- }
-
- std::string message;
- if (GetJsonSignalingMessage(desc, candidates, &message)) {
- if (event_callback_) {
- event_callback_->OnSignalingMessage(message);
+ case MSG_PROCESSSIGNALINGMESSAGE: {
+ if (ready_state_ != PeerConnectionInterface::kClosed) {
+ SignalingParams* params(static_cast<SignalingParams*> (data));
+ signaling_->ProcessSignalingMessage(params->msg, params->local_streams);
+ }
+ delete data; // Because it is Posted.
+ break;
}
+ case MSG_RETURNREMOTEMEDIASTREAMS: {
+ StreamCollectionParams* param(
+ static_cast<StreamCollectionParams*> (data));
+ param->streams = StreamCollection::Create(signaling_->remote_streams());
+ break;
+ }
+ case MSG_CLOSE: {
+ if (ready_state_ != PeerConnectionInterface::kClosed) {
+ ChangeReadyState(PeerConnectionInterface::kClosing);
+ signaling_->SendShutDown();
+ }
+ break;
+ }
+ case MSG_READYSTATE: {
+ ReadyStateMessage* msg(static_cast<ReadyStateMessage*> (data));
+ msg->state = ready_state_;
+ break;
+ }
+ case MSG_SDPSTATE: {
+ SdpStateMessage* msg(static_cast<SdpStateMessage*> (data));
+ msg->state = sdp_state_;
+ break;
+ }
+ case MSG_TERMINATE: {
+ Terminate_s();
+ break;
+ }
+ default:
+ ASSERT(!"NOT IMPLEMENTED");
+ break;
}
}
-void PeerConnectionImpl::OnFailedCall() {
- // TODO: implement.
+void PeerConnection::OnNewPeerConnectionMessage(const std::string& message) {
+ observer_->OnSignalingMessage(message);
}
-bool PeerConnectionImpl::SetAudioDevice(const std::string& wave_in_device,
- const std::string& wave_out_device,
- int opts) {
- return channel_manager_->SetAudioOptions(wave_in_device,
- wave_out_device,
- opts);
+void PeerConnection::OnRemoteStreamAdded(MediaStreamInterface* remote_stream) {
+ stream_handler_->AddRemoteStream(remote_stream);
+ observer_->OnAddStream(remote_stream);
}
-bool PeerConnectionImpl::SetLocalVideoRenderer(
- cricket::VideoRenderer* renderer) {
- return channel_manager_->SetLocalRenderer(renderer);
+void PeerConnection::OnRemoteStreamRemoved(
+ MediaStreamInterface* remote_stream) {
+ stream_handler_->RemoveRemoteStream(remote_stream);
+ observer_->OnRemoveStream(remote_stream);
}
-bool PeerConnectionImpl::SetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer) {
- return session_->SetVideoRenderer(stream_id, renderer);
-}
-
-bool PeerConnectionImpl::SetVideoCapture(const std::string& cam_device) {
- return channel_manager_->SetVideoOptions(cam_device);
-}
-
-bool PeerConnectionImpl::Connect() {
- return session_->Connect();
-}
-
-// TODO - Close is not used anymore, should be removed.
-bool PeerConnectionImpl::Close() {
- session_->RemoveAllStreams();
- return true;
-}
-
-void PeerConnectionImpl::OnAddStream(const std::string& stream_id,
- bool video) {
- if (event_callback_) {
- event_callback_->OnAddStream(stream_id, video);
+void PeerConnection::OnSignalingStateChange(
+ PeerConnectionSignaling::State state) {
+ switch (state) {
+ case PeerConnectionSignaling::kInitializing:
+ break;
+ case PeerConnectionSignaling::kIdle:
+ if (ready_state_ == PeerConnectionInterface::kNegotiating)
+ ChangeReadyState(PeerConnectionInterface::kActive);
+ ChangeSdpState(PeerConnectionInterface::kSdpIdle);
+ break;
+ case PeerConnectionSignaling::kWaitingForAnswer:
+ ChangeSdpState(PeerConnectionInterface::kSdpWaiting);
+ break;
+ case PeerConnectionSignaling::kWaitingForOK:
+ ChangeSdpState(PeerConnectionInterface::kSdpWaiting);
+ break;
+ case PeerConnectionSignaling::kShutingDown:
+ ChangeReadyState(PeerConnectionInterface::kClosing);
+ break;
+ case PeerConnectionSignaling::kShutdownComplete:
+ ChangeReadyState(PeerConnectionInterface::kClosed);
+ signaling_thread()->Post(this, MSG_TERMINATE);
+ break;
+ default:
+ ASSERT(!"NOT IMPLEMENTED");
+ break;
}
}
-void PeerConnectionImpl::OnRemoveStream(const std::string& stream_id,
- bool video) {
- if (event_callback_) {
- event_callback_->OnRemoveStream(stream_id, video);
- }
+void PeerConnection::ChangeReadyState(
+ PeerConnectionInterface::ReadyState ready_state) {
+ ready_state_ = ready_state;
+ observer_->OnStateChange(PeerConnectionObserver::kReadyState);
}
-PeerConnectionImpl::ReadyState PeerConnectionImpl::GetReadyState() {
- ReadyState ready_state;
- cricket::BaseSession::State state = session_->state();
- if (state == cricket::BaseSession::STATE_INIT) {
- ready_state = NEW;
- } else if (state == cricket::BaseSession::STATE_INPROGRESS) {
- ready_state = ACTIVE;
- } else if (state == cricket::BaseSession::STATE_DEINIT) {
- ready_state = CLOSED;
- } else {
- ready_state = NEGOTIATING;
- }
- return ready_state;
+void PeerConnection::ChangeSdpState(
+ PeerConnectionInterface::SdpState sdp_state) {
+ sdp_state_ = sdp_state;
+ observer_->OnStateChange(PeerConnectionObserver::kSdpState);
}
} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionimpl.h b/talk/app/webrtc/peerconnectionimpl.h
index 6ff2f25..2686cf2 100644
--- a/talk/app/webrtc/peerconnectionimpl.h
+++ b/talk/app/webrtc/peerconnectionimpl.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -28,68 +28,83 @@
#ifndef TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
#define TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
+#include <map>
#include <string>
-#include <vector>
#include "talk/app/webrtc/peerconnection.h"
-#include "talk/base/sigslot.h"
+#include "talk/app/webrtc/peerconnectionfactoryimpl.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/app/webrtc/webrtcsession.h"
#include "talk/base/scoped_ptr.h"
-#include "talk/base/thread.h"
-#include "talk/session/phone/channelmanager.h"
-
-namespace cricket {
-class ChannelManager;
-class PortAllocator;
-class SessionDescription;
-}
+#include "talk/p2p/client/httpportallocator.h"
namespace webrtc {
-class WebRtcSession;
+class MediaStreamHandlers;
-class PeerConnectionImpl : public PeerConnection,
- public sigslot::has_slots<> {
+// PeerConnectionImpl implements the PeerConnection interface.
+// It uses PeerConnectionSignaling and WebRtcSession to implement
+// the PeerConnection functionality.
+class PeerConnection : public PeerConnectionInterface,
+ public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
public:
- PeerConnectionImpl(cricket::PortAllocator* port_allocator,
- cricket::ChannelManager* channel_manager,
- talk_base::Thread* signaling_thread);
- virtual ~PeerConnectionImpl();
+ explicit PeerConnection(PeerConnectionFactory* factory);
- // PeerConnection interfaces
- virtual void RegisterObserver(PeerConnectionObserver* observer);
- virtual bool SignalingMessage(const std::string& msg);
- virtual bool AddStream(const std::string& stream_id, bool video);
- virtual bool RemoveStream(const std::string& stream_id);
- virtual bool Connect();
- virtual bool Close();
- virtual bool SetAudioDevice(const std::string& wave_in_device,
- const std::string& wave_out_device, int opts);
- virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer);
- virtual bool SetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer);
- virtual bool SetVideoCapture(const std::string& cam_device);
- virtual ReadyState GetReadyState();
+ bool Initialize(const std::string& configuration,
+ PeerConnectionObserver* observer);
- cricket::ChannelManager* channel_manager() {
- return channel_manager_;
+ virtual ~PeerConnection();
+
+ virtual void ProcessSignalingMessage(const std::string& msg);
+ virtual bool Send(const std::string& msg) {
+ // TODO: implement
+ ASSERT(false);
+ return false;
}
-
- // Callbacks from PeerConnectionImplCallbacks
- void OnAddStream(const std::string& stream_id, bool video);
- void OnRemoveStream(const std::string& stream_id, bool video);
- void OnLocalDescription(
- const cricket::SessionDescription* desc,
- const std::vector<cricket::Candidate>& candidates);
- void OnFailedCall();
- bool Init();
+ virtual talk_base::scoped_refptr<StreamCollectionInterface> local_streams();
+ virtual talk_base::scoped_refptr<StreamCollectionInterface> remote_streams();
+ virtual void AddStream(LocalMediaStreamInterface* stream);
+ virtual void RemoveStream(LocalMediaStreamInterface* stream);
+ virtual void CommitStreamChanges();
+ virtual void Close();
+ virtual ReadyState ready_state();
+ virtual SdpState sdp_state();
private:
- WebRtcSession* CreateMediaSession(const std::string& id, bool incoming);
+ // Implement talk_base::MessageHandler.
+ void OnMessage(talk_base::Message* msg);
- cricket::PortAllocator* port_allocator_;
- cricket::ChannelManager* channel_manager_;
- talk_base::Thread* signaling_thread_;
- PeerConnectionObserver* event_callback_;
+ // Signals from PeerConnectionSignaling.
+ void OnNewPeerConnectionMessage(const std::string& message);
+ void OnRemoteStreamAdded(MediaStreamInterface* remote_stream);
+ void OnRemoteStreamRemoved(MediaStreamInterface* remote_stream);
+ void OnSignalingStateChange(PeerConnectionSignaling::State state);
+
+ void ChangeReadyState(PeerConnectionInterface::ReadyState ready_state);
+ void ChangeSdpState(PeerConnectionInterface::SdpState sdp_state);
+ void Terminate_s();
+
+ talk_base::Thread* signaling_thread() {
+ return factory_->signaling_thread();
+ }
+
+ // Storing the factory as a scoped reference pointer ensures that the memory
+ // in the PeerConnectionFactoryImpl remains available as long as the
+ // PeerConnection is running. It is passed to PeerConnection as a raw pointer.
+ // However, since the reference counting is done in the
+ // PeerConnectionFactoryInteface all instances created using the raw pointer
+ // will refer to the same reference count.
+ talk_base::scoped_refptr<PeerConnectionFactory> factory_;
+ PeerConnectionObserver* observer_;
+ ReadyState ready_state_;
+ SdpState sdp_state_;
+ talk_base::scoped_refptr<StreamCollection> local_media_streams_;
+
+ talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
talk_base::scoped_ptr<WebRtcSession> session_;
+ talk_base::scoped_ptr<PeerConnectionSignaling> signaling_;
+ talk_base::scoped_ptr<MediaStreamHandlers> stream_handler_;
};
} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionimpl_unittest.cc b/talk/app/webrtc/peerconnectionimpl_unittest.cc
new file mode 100644
index 0000000..5363af6
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionimpl_unittest.cc
@@ -0,0 +1,287 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/fakeportallocatorfactory.h"
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/gunit.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStunConfiguration[] = "STUN stun.l.google.com:19302";
+static const char kInvalidConfiguration[] = "a13151913541234:19302";
+static const uint32 kTimeout = 5000U;
+
+using talk_base::scoped_ptr;
+using talk_base::scoped_refptr;
+using webrtc::FakePortAllocatorFactory;
+using webrtc::LocalMediaStreamInterface;
+using webrtc::LocalVideoTrackInterface;
+using webrtc::MediaStreamInterface;
+using webrtc::PeerConnectionInterface;
+using webrtc::PeerConnectionObserver;
+using webrtc::PortAllocatorFactoryInterface;
+using webrtc::RoapMessageBase;
+using webrtc::RoapOffer;
+
+
+// Create ROAP message for shutdown.
+static std::string CreateShutdownMessage() {
+ webrtc::RoapShutdown shutdown("dummy_session", "", "", 1);
+ return shutdown.Serialize();
+}
+
+// Create a ROAP answer message.
+// The session description in the answer is set to the same as in the offer.
+static std::string CreateAnswerMessage(const RoapMessageBase& msg) {
+ webrtc::RoapOffer offer(msg);
+ EXPECT_TRUE(offer.Parse());
+ cricket::SessionDescription* sdp_offer =
+ offer.ReleaseSessionDescription();
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp_offer);
+ if (audio_content) {
+ const cricket::AudioContentDescription* desc =
+ static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+ cricket::CryptoParamsVec& cryptos =
+ const_cast<cricket::CryptoParamsVec&>(desc->cryptos());
+ cryptos.erase(cryptos.begin()++);
+ }
+
+ webrtc::RoapAnswer answer(offer.offer_session_id(), "dummy_session",
+ offer.session_token(), offer.response_token(),
+ offer.seq(), sdp_offer, offer.candidates());
+ return answer.Serialize();
+}
+
+// Create ROAP message to answer ok to a ROAP shutdown or ROAP answer message.
+static std::string CreateOkMessage(const RoapMessageBase& msg) {
+ webrtc::RoapOk ok(msg.offer_session_id(), "dummy_session",
+ msg.session_token(), msg.response_token(), msg.seq());
+ return ok.Serialize();
+}
+
+class MockPeerConnectionObserver : public PeerConnectionObserver {
+ public:
+ void SetPeerConnectionInterface(PeerConnectionInterface* pc) {
+ pc_ = pc;
+ state_ = pc_->ready_state();
+ sdp_state_ = pc_->sdp_state();
+ }
+ virtual void OnError() {}
+ virtual void OnMessage(const std::string& msg) {}
+ virtual void OnSignalingMessage(const std::string& msg) {
+ EXPECT_TRUE(last_message_.Parse(msg));
+ }
+ virtual void OnStateChange(StateType state_changed) {
+ if (pc_.get() == NULL)
+ return;
+ switch (state_changed) {
+ case kReadyState:
+ state_ = pc_->ready_state();
+ break;
+ case kSdpState:
+ sdp_state_ = pc_->sdp_state();
+ break;
+ case kIceState:
+ ADD_FAILURE();
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+ }
+ virtual void OnAddStream(MediaStreamInterface* stream) {
+ last_added_stream_ = stream;
+ }
+ virtual void OnRemoveStream(MediaStreamInterface* stream) {
+ last_removed_stream_ = stream;
+ }
+
+ // Returns the label of the last added stream.
+ // Empty string if no stream have been added.
+ std::string GetLastAddedStreamLabel() {
+ if (last_added_stream_.get())
+ return last_added_stream_->label();
+ return "";
+ }
+ std::string GetLastRemovedStreamLabel() {
+ if (last_removed_stream_.get())
+ return last_removed_stream_->label();
+ return "";
+ }
+
+ talk_base::scoped_refptr<PeerConnectionInterface> pc_;
+ RoapMessageBase last_message_;
+ PeerConnectionInterface::ReadyState state_;
+ PeerConnectionInterface::SdpState sdp_state_;
+
+ private:
+ scoped_refptr<MediaStreamInterface> last_added_stream_;
+ scoped_refptr<MediaStreamInterface> last_removed_stream_;
+};
+
+class PeerConnectionImplTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ port_allocator_factory_ = FakePortAllocatorFactory::Create();
+
+ pc_factory_ = webrtc::CreatePeerConnectionFactory(
+ talk_base::Thread::Current(), talk_base::Thread::Current(),
+ port_allocator_factory_.get(), NULL);
+ ASSERT_TRUE(pc_factory_.get() != NULL);
+ }
+
+ void CreatePeerConnection() {
+ pc_ = pc_factory_->CreatePeerConnection(kStunConfiguration, &observer_);
+ ASSERT_TRUE(pc_.get() != NULL);
+ observer_.SetPeerConnectionInterface(pc_.get());
+ EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
+ }
+
+ void CreatePeerConnectionWithInvalidConfiguration() {
+ pc_ = pc_factory_->CreatePeerConnection(kInvalidConfiguration, &observer_);
+ ASSERT_TRUE(pc_.get() != NULL);
+ observer_.SetPeerConnectionInterface(pc_.get());
+ EXPECT_EQ(PeerConnectionInterface::kNegotiating, observer_.state_);
+ }
+
+ void AddStream(const std::string& label) {
+ // Create a local stream.
+ scoped_refptr<LocalMediaStreamInterface> stream(
+ pc_factory_->CreateLocalMediaStream(label));
+ scoped_refptr<LocalVideoTrackInterface> video_track(
+ pc_factory_->CreateLocalVideoTrack(label, NULL));
+ stream->AddTrack(video_track.get());
+ pc_->AddStream(stream);
+ pc_->CommitStreamChanges();
+
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+ kTimeout);
+ // Wait for the ICE agent to find the candidates and send an offer.
+ EXPECT_EQ_WAIT(RoapMessageBase::kOffer, observer_.last_message_.type(),
+ kTimeout);
+ }
+
+ scoped_refptr<PortAllocatorFactoryInterface> port_allocator_factory_;
+ scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory_;
+ scoped_refptr<PeerConnectionInterface> pc_;
+ MockPeerConnectionObserver observer_;
+};
+
+TEST_F(PeerConnectionImplTest, CreatePeerConnectionWithInvalidConfiguration) {
+ CreatePeerConnectionWithInvalidConfiguration();
+ AddStream(kStreamLabel1);
+}
+
+TEST_F(PeerConnectionImplTest, AddStream) {
+ CreatePeerConnection();
+ AddStream(kStreamLabel1);
+ ASSERT_EQ(1u, pc_->local_streams()->count());
+ EXPECT_EQ(kStreamLabel1, pc_->local_streams()->at(0)->label());
+
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kNegotiating, observer_.state_,
+ kTimeout);
+ pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kActive, observer_.state_, kTimeout);
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+ kTimeout);
+ // Since we answer with the same session description as we offer we can
+ // check if OnAddStream have been called.
+ EXPECT_EQ(kStreamLabel1, observer_.GetLastAddedStreamLabel());
+ ASSERT_EQ(1u, pc_->remote_streams()->count());
+ EXPECT_EQ(kStreamLabel1, pc_->remote_streams()->at(0)->label());
+}
+
+TEST_F(PeerConnectionImplTest, DISABLED_UpdateStream) {
+ CreatePeerConnection();
+ AddStream(kStreamLabel1);
+ WAIT(PeerConnectionInterface::kNegotiating == observer_.state_, kTimeout);
+ pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+ WAIT(PeerConnectionInterface::kActive == observer_.state_, kTimeout);
+ WAIT(PeerConnectionInterface::kSdpIdle == observer_.sdp_state_, kTimeout);
+
+ AddStream(kStreamLabel2);
+ ASSERT_EQ(2u, pc_->local_streams()->count());
+ EXPECT_EQ(kStreamLabel2, pc_->local_streams()->at(1)->label());
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+ kTimeout);
+ EXPECT_EQ(PeerConnectionInterface::kActive, observer_.state_);
+ pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+ kTimeout);
+ // Since we answer with the same session description as we offer we can
+ // check if OnAddStream have been called.
+ EXPECT_EQ(kStreamLabel2, observer_.GetLastAddedStreamLabel());
+ ASSERT_EQ(2u, pc_->remote_streams()->count());
+ EXPECT_EQ(kStreamLabel2, pc_->remote_streams()->at(1)->label());
+
+ pc_->RemoveStream(static_cast<LocalMediaStreamInterface*>(
+ pc_->local_streams()->at(1)));
+ pc_->CommitStreamChanges();
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpWaiting, observer_.sdp_state_,
+ kTimeout);
+ pc_->ProcessSignalingMessage(CreateAnswerMessage(observer_.last_message_));
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kSdpIdle, observer_.sdp_state_,
+ kTimeout);
+ EXPECT_EQ(kStreamLabel2, observer_.GetLastRemovedStreamLabel());
+ EXPECT_EQ(1u, pc_->local_streams()->count());
+}
+
+TEST_F(PeerConnectionImplTest, SendClose) {
+ CreatePeerConnection();
+ pc_->Close();
+ EXPECT_EQ(RoapMessageBase::kShutdown, observer_.last_message_.type());
+ EXPECT_EQ(PeerConnectionInterface::kClosing, observer_.state_);
+ pc_->ProcessSignalingMessage(CreateOkMessage(observer_.last_message_));
+ EXPECT_EQ_WAIT(PeerConnectionInterface::kClosed, observer_.state_, kTimeout);
+}
+
+TEST_F(PeerConnectionImplTest, ReceiveClose) {
+ CreatePeerConnection();
+ pc_->ProcessSignalingMessage(CreateShutdownMessage());
+ EXPECT_EQ_WAIT(RoapMessageBase::kOk, observer_.last_message_.type(),
+ kTimeout);
+ EXPECT_EQ(PeerConnectionInterface::kClosed, observer_.state_);
+}
+
+TEST_F(PeerConnectionImplTest, ReceiveCloseWhileExpectingAnswer) {
+ CreatePeerConnection();
+ AddStream(kStreamLabel1);
+
+ // Receive the shutdown message.
+ pc_->ProcessSignalingMessage(CreateShutdownMessage());
+ EXPECT_EQ_WAIT(RoapMessageBase::kOk, observer_.last_message_.type(),
+ kTimeout);
+ EXPECT_EQ(PeerConnectionInterface::kClosed, observer_.state_);
+}
diff --git a/talk/app/webrtc/peerconnectionsignaling.cc b/talk/app/webrtc/peerconnectionsignaling.cc
new file mode 100644
index 0000000..a3bfb63
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling.cc
@@ -0,0 +1,596 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+
+#include <utility>
+
+#include "talk/app/webrtc/mediastreamproxy.h"
+#include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/session/phone/channelmanager.h"
+
+using talk_base::scoped_refptr;
+
+namespace webrtc {
+
+enum {
+ MSG_SEND_QUEUED_OFFER = 1,
+ MSG_GENERATE_ANSWER = 2,
+};
+
+// Verifies that a SessionDescription contains as least one valid media content
+// and a valid codec.
+static bool VerifyAnswer(const cricket::SessionDescription* answer_desc) {
+ // We need to verify that at least one media content with
+ // a codec is available.
+ const cricket::ContentInfo* audio_content =
+ GetFirstAudioContent(answer_desc);
+ if (audio_content) {
+ const cricket::AudioContentDescription* audio_desc =
+ static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+ if (audio_desc->codecs().size() > 0) {
+ return true;
+ }
+ }
+ const cricket::ContentInfo* video_content =
+ GetFirstVideoContent(answer_desc);
+ if (video_content) {
+ const cricket::VideoContentDescription* video_desc =
+ static_cast<const cricket::VideoContentDescription*>(
+ video_content->description);
+ if (video_desc->codecs().size() > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Fills a MediaSessionOptions struct with the MediaTracks we want to sent given
+// the local MediaStreams. |local_streams| can be null if there are no tracks to
+// send.
+static void InitMediaSessionOptions(
+ cricket::MediaSessionOptions* options,
+ StreamCollectionInterface* local_streams) {
+ if (!VERIFY(options != NULL))
+ return;
+ // In order to be able to receive video,
+ // the has_video should always be true even if there are no video tracks.
+ options->has_video = true;
+ if (local_streams == NULL)
+ return;
+
+ for (size_t i = 0; i < local_streams->count(); ++i) {
+ MediaStreamInterface* stream = local_streams->at(i);
+
+ scoped_refptr<AudioTracks> audio_tracks(stream->audio_tracks());
+ // For each audio track in the stream, add it to the MediaSessionOptions.
+ for (size_t j = 0; j < audio_tracks->count(); ++j) {
+ scoped_refptr<MediaStreamTrackInterface> track(audio_tracks->at(j));
+ options->AddStream(cricket::MEDIA_TYPE_AUDIO, track->label(),
+ stream->label());
+ }
+
+ scoped_refptr<VideoTracks> video_tracks(stream->video_tracks());
+ // For each video track in the stream, add it to the MediaSessionOptions.
+ for (size_t j = 0; j < video_tracks->count(); ++j) {
+ scoped_refptr<MediaStreamTrackInterface> track(video_tracks->at(j));
+ options->AddStream(cricket::MEDIA_TYPE_VIDEO, track->label(),
+ stream->label());
+ }
+ }
+}
+
+PeerConnectionSignaling::PeerConnectionSignaling(
+ talk_base::Thread* signaling_thread,
+ SessionDescriptionProvider* provider)
+ : signaling_thread_(signaling_thread),
+ provider_(provider),
+ state_(kInitializing),
+ received_pre_offer_(false),
+ remote_streams_(StreamCollection::Create()),
+ local_streams_(StreamCollection::Create()) {
+}
+
+PeerConnectionSignaling::~PeerConnectionSignaling() {}
+
+void PeerConnectionSignaling::OnCandidatesReady(
+ const cricket::Candidates& candidates) {
+ if (!VERIFY(state_ == kInitializing))
+ return;
+ // Store the candidates.
+ candidates_ = candidates;
+ // If we have a queued remote offer we need to handle this first.
+ if (received_pre_offer_) {
+ received_pre_offer_ = false;
+ ChangeState(kWaitingForOK);
+ signaling_thread_->Post(this, MSG_GENERATE_ANSWER);
+ } else if (!queued_local_streams_.empty()) {
+ // Else if we have local queued offers.
+ ChangeState(kWaitingForAnswer);
+ signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+ } else {
+ ChangeState(kIdle);
+ }
+}
+
+void PeerConnectionSignaling::ChangeState(State new_state) {
+ state_ = new_state;
+ SignalStateChange(state_);
+}
+
+void PeerConnectionSignaling::ProcessSignalingMessage(
+ const std::string& message,
+ StreamCollectionInterface* local_streams) {
+ ASSERT(talk_base::Thread::Current() == signaling_thread_);
+
+ RoapSession::ParseResult result = roap_session_.Parse(message);
+
+ // Signal an error message and return if a message is received after shutdown
+ // or it is not an ok message that is received during shutdown.
+ // No other messages from the remote peer can be processed in these states.
+ if (state_ == kShutdownComplete ||
+ (state_ == kShutingDown && result != RoapSession::kOk)) {
+ SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(kNoMatch));
+ return;
+ }
+
+ switch (result) {
+ case RoapSession::kOffer: {
+ queued_local_streams_.clear();
+ queued_local_streams_.push_back(local_streams);
+
+ if (state_ == kWaitingForAnswer) {
+ // Message received out of order or Glare occurred and the decision was
+ // to use the incoming offer.
+ LOG(LS_INFO) << "Received offer while waiting for answer.";
+ // Be nice and handle this offer instead of the pending offer.
+ signaling_thread_->Clear(this, MSG_SEND_QUEUED_OFFER);
+ }
+
+ // Provide the remote session description and the remote candidates from
+ // the parsed ROAP message to the |provider_|.
+ // The session description ownership is transferred from |roap_session_|
+ // to |provider_|.
+ provider_->SetRemoteDescription(roap_session_.ReleaseRemoteDescription(),
+ cricket::CA_OFFER);
+ provider_->SetRemoteCandidates(roap_session_.RemoteCandidates());
+ // Update the known remote MediaStreams.
+ UpdateRemoteStreams(provider_->remote_description());
+
+ // If we are still Initializing we need to wait until we have our local
+ // candidates before we can handle the offer. Queue it and handle it when
+ // the state changes.
+ if (state_ == kInitializing) {
+ received_pre_offer_ = true;
+ break;
+ }
+
+ // Post a task to generate the answer.
+ signaling_thread_->Post(this, MSG_GENERATE_ANSWER);
+ ChangeState(kWaitingForOK);
+ break;
+ }
+ case RoapSession::kAnswerMoreComing: {
+ // We ignore this message for now and wait for the complete result.
+ LOG(LS_INFO) << "Received answer more coming.";
+ break;
+ }
+ case RoapSession::kAnswer: {
+ if (state_ != kWaitingForAnswer) {
+ LOG(LS_WARNING) << "Received an unexpected answer.";
+ return;
+ }
+
+ talk_base::scoped_ptr<cricket::SessionDescription> remote_desc(
+ roap_session_.ReleaseRemoteDescription());
+
+ // Pop the first item of queued StreamCollections containing local
+ // MediaStreams that just have been negotiated.
+ scoped_refptr<StreamCollectionInterface> streams(
+ queued_local_streams_.front());
+ queued_local_streams_.pop_front();
+ // Update the state of the local MediaStreams.
+ UpdateSendingLocalStreams(remote_desc.get(), streams);
+
+ // Hand the ownership of the local session description to |provider_|.
+ provider_->SetLocalDescription(local_desc_.release(), cricket::CA_OFFER);
+
+ // Provide the remote session description and the remote candidates from
+ // the parsed ROAP message to the |provider_|.
+ // The session description ownership is transferred from |roap_session_|
+ // to |provider_|.
+ provider_->SetRemoteDescription(remote_desc.release(),
+ cricket::CA_ANSWER);
+ provider_->SetRemoteCandidates(roap_session_.RemoteCandidates());
+ // Update the list of known remote MediaStreams.
+ UpdateRemoteStreams(provider_->remote_description());
+
+ // Let the remote peer know we have received the answer.
+ SignalNewPeerConnectionMessage(roap_session_.CreateOk());
+ // Check if we have more offers waiting in the queue.
+ if (!queued_local_streams_.empty()) {
+ // Send the next offer.
+ signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+ } else {
+ ChangeState(kIdle);
+ }
+ break;
+ }
+ case RoapSession::kOk: {
+ if (state_ == kWaitingForOK) {
+ scoped_refptr<StreamCollectionInterface> streams(
+ queued_local_streams_.front());
+ queued_local_streams_.pop_front();
+ // Update the state of the local streams.
+ UpdateSendingLocalStreams(local_desc_.get(), streams);
+
+ // Hand over the ownership of the local description to the provider.
+ provider_->SetLocalDescription(local_desc_.release(),
+ cricket::CA_ANSWER);
+ ChangeState(kIdle);
+ // Check if we have an updated offer waiting in the queue.
+ if (!queued_local_streams_.empty())
+ signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+ } else if (state_ == kShutingDown) {
+ ChangeState(kShutdownComplete);
+ }
+ break;
+ }
+ case RoapSession::kConflict: {
+ SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+ kConflict));
+ break;
+ }
+ case RoapSession::kDoubleConflict: {
+ SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+ kDoubleConflict));
+
+ // Recreate the offer with new sequence values etc.
+ ChangeState(kWaitingForAnswer);
+ signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+ break;
+ }
+ case RoapSession::kError: {
+ if (roap_session_.RemoteError() != kConflict &&
+ roap_session_.RemoteError() != kDoubleConflict) {
+ SignalErrorMessageReceived(roap_session_.RemoteError());
+ // An error have occurred that we can't do anything about.
+ // Reset the state and wait for user action.
+ signaling_thread_->Clear(this);
+ queued_local_streams_.clear();
+ ChangeState(kIdle);
+ }
+ break;
+ }
+ case RoapSession::kShutDown: {
+ DoShutDown();
+ SignalNewPeerConnectionMessage(roap_session_.CreateOk());
+ ChangeState(kShutdownComplete);
+ break;
+ }
+ case RoapSession::kInvalidMessage: {
+ SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(
+ kNoMatch));
+ return;
+ }
+ }
+}
+
+void PeerConnectionSignaling::CreateOffer(
+ StreamCollectionInterface* local_streams) {
+ if (!VERIFY(talk_base::Thread::Current() == signaling_thread_ &&
+ state_ != kShutingDown && state_ != kShutdownComplete)) {
+ return;
+ }
+
+ queued_local_streams_.push_back(local_streams);
+ if (state_ == kIdle) {
+ // Check if we can send a new offer.
+ // Only one offer is allowed at the time.
+ ChangeState(kWaitingForAnswer);
+ signaling_thread_->Post(this, MSG_SEND_QUEUED_OFFER);
+ }
+}
+
+void PeerConnectionSignaling::SendShutDown() {
+ DoShutDown();
+ SignalNewPeerConnectionMessage(roap_session_.CreateShutDown());
+}
+
+// Implement talk_base::MessageHandler.
+void PeerConnectionSignaling::OnMessage(talk_base::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_SEND_QUEUED_OFFER:
+ CreateOffer_s();
+ break;
+ case MSG_GENERATE_ANSWER:
+ CreateAnswer_s();
+ break;
+ default:
+ ASSERT(!"Invalid value in switch statement.");
+ break;
+ }
+}
+
+void PeerConnectionSignaling::CreateOffer_s() {
+ ASSERT(!queued_local_streams_.empty());
+ scoped_refptr<StreamCollectionInterface> local_streams(
+ queued_local_streams_.front());
+ cricket::MediaSessionOptions options;
+ InitMediaSessionOptions(&options, local_streams);
+
+ local_desc_.reset(provider_->CreateOffer(options));
+ SignalNewPeerConnectionMessage(roap_session_.CreateOffer(local_desc_.get(),
+ candidates_));
+}
+
+void PeerConnectionSignaling::DoShutDown() {
+ ChangeState(kShutingDown);
+ signaling_thread_->Clear(this); // Don't send queued offers or answers.
+ queued_local_streams_.clear();
+ cricket::MediaSessionOptions options;
+ InitMediaSessionOptions(&options, NULL);
+
+ // Create new empty session descriptions without StreamParams.
+ // By applying these descriptions we don't send or receive any streams.
+ const SessionDescription* local_desc = provider_->CreateOffer(options);
+ SessionDescription* remote_desc = provider_->CreateAnswer(local_desc,
+ options);
+
+ provider_->SetRemoteDescription(remote_desc, cricket::CA_OFFER);
+ provider_->SetLocalDescription(local_desc, cricket::CA_ANSWER);
+
+ UpdateRemoteStreams(NULL);
+}
+
+void PeerConnectionSignaling::CreateAnswer_s() {
+ scoped_refptr<StreamCollectionInterface> streams(
+ queued_local_streams_.back());
+ // Clean up all queued collections of local streams except the last one.
+ // The last one is kept until the ok message is received for this answer and
+ // is needed for updating the state of the local streams.
+ queued_local_streams_.erase(queued_local_streams_.begin(),
+ --queued_local_streams_.end());
+
+ // Create a MediaSessionOptions object with the sources we want to send.
+ cricket::MediaSessionOptions options;
+ InitMediaSessionOptions(&options, streams);
+ // Create an local session description based on this.
+ local_desc_.reset(provider_->CreateAnswer(provider_->remote_description(),
+ options));
+ if (!VerifyAnswer(local_desc_.get())) {
+ SignalNewPeerConnectionMessage(roap_session_.CreateErrorMessage(kRefused));
+ return;
+ }
+
+ SignalNewPeerConnectionMessage(roap_session_.CreateAnswer(local_desc_.get(),
+ candidates_));
+}
+
+// Updates or Creates remote MediaStream objects given a
+// remote SessionDesription.
+// If the remote SessionDesription contain new remote MediaStreams
+// SignalRemoteStreamAdded is triggered. If a remote MediaStream is missing from
+// the remote SessionDescription SignalRemoteStreamRemoved is triggered.
+void PeerConnectionSignaling::UpdateRemoteStreams(
+ const cricket::SessionDescription* remote_desc) {
+ talk_base::scoped_refptr<StreamCollection> current_streams(
+ StreamCollection::Create());
+
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(remote_desc);
+ if (audio_content) {
+ const cricket::AudioContentDescription* audio_desc =
+ static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+
+ for (cricket::StreamParamsVec::const_iterator it =
+ audio_desc->streams().begin();
+ it != audio_desc->streams().end(); ++it) {
+ MediaStreamInterface* old_stream = remote_streams_->find(it->sync_label);
+ scoped_refptr<MediaStreamProxy> new_stream(static_cast<MediaStreamProxy*>(
+ current_streams->find(it->sync_label)));
+
+ if (old_stream == NULL) {
+ if (new_stream == NULL) {
+ // New stream
+ new_stream = MediaStreamProxy::Create(it->sync_label,
+ signaling_thread_);
+ current_streams->AddStream(new_stream);
+ }
+ scoped_refptr<AudioTrackInterface> track(
+ AudioTrackProxy::CreateRemote(it->name, signaling_thread_));
+ track->set_state(MediaStreamTrackInterface::kLive);
+ new_stream->AddTrack(track);
+ } else {
+ current_streams->AddStream(old_stream);
+ }
+ }
+ }
+
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(remote_desc);
+ if (video_content) {
+ const cricket::VideoContentDescription* video_desc =
+ static_cast<const cricket::VideoContentDescription*>(
+ video_content->description);
+
+ for (cricket::StreamParamsVec::const_iterator it =
+ video_desc->streams().begin();
+ it != video_desc->streams().end(); ++it) {
+ MediaStreamInterface* old_stream = remote_streams_->find(it->sync_label);
+ scoped_refptr<MediaStreamProxy> new_stream(static_cast<MediaStreamProxy*>(
+ current_streams->find(it->sync_label)));
+ if (old_stream == NULL) {
+ if (new_stream == NULL) {
+ // New stream
+ new_stream = MediaStreamProxy::Create(it->sync_label,
+ signaling_thread_);
+ current_streams->AddStream(new_stream);
+ }
+ scoped_refptr<VideoTrackInterface> track(
+ VideoTrackProxy::CreateRemote(it->name, signaling_thread_));
+ new_stream->AddTrack(track);
+ track->set_state(MediaStreamTrackInterface::kLive);
+ } else {
+ current_streams->AddStream(old_stream);
+ }
+ }
+ }
+
+ // Iterate current_streams to find all new streams.
+ // Change the state of the new stream and SignalRemoteStreamAdded.
+ for (size_t i = 0; i < current_streams->count(); ++i) {
+ MediaStreamInterface* new_stream = current_streams->at(i);
+ MediaStreamInterface* old_stream = remote_streams_->find(
+ new_stream->label());
+ if (old_stream != NULL) continue;
+
+ new_stream->set_ready_state(MediaStreamInterface::kLive);
+ SignalRemoteStreamAdded(new_stream);
+ }
+
+ // Iterate the old list of remote streams.
+ // If a stream is not found in the new list it have been removed.
+ // Change the state of the removed stream and SignalRemoteStreamRemoved.
+ for (size_t i = 0; i < remote_streams_->count(); ++i) {
+ MediaStreamInterface* old_stream = remote_streams_->at(i);
+ MediaStreamInterface* new_stream = current_streams->find(
+ old_stream->label());
+ if (new_stream != NULL) continue;
+
+ old_stream->set_ready_state(MediaStreamInterface::kEnded);
+ scoped_refptr<AudioTracks> audio_tracklist(old_stream->audio_tracks());
+ for (size_t j = 0; j < audio_tracklist->count(); ++j) {
+ audio_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+ }
+ scoped_refptr<VideoTracks> video_tracklist(old_stream->video_tracks());
+ for (size_t j = 0; j < video_tracklist->count(); ++j) {
+ video_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+ }
+ SignalRemoteStreamRemoved(old_stream);
+ }
+ // Prepare for next offer.
+ remote_streams_ = current_streams;
+}
+
+// Update the state of all local streams we have just negotiated. If the
+// negotiation succeeded the state is changed to kLive, if the negotiation
+// failed the state is changed to kEnded.
+void PeerConnectionSignaling::UpdateSendingLocalStreams(
+ const cricket::SessionDescription* answer_desc,
+ StreamCollectionInterface* negotiated_streams) {
+ talk_base::scoped_refptr<StreamCollection> current_local_streams(
+ StreamCollection::Create());
+
+ for (size_t i = 0; i < negotiated_streams->count(); ++i) {
+ scoped_refptr<MediaStreamInterface> stream(negotiated_streams->at(i));
+ scoped_refptr<AudioTracks> audiotracklist(stream->audio_tracks());
+ scoped_refptr<VideoTracks> videotracklist(stream->video_tracks());
+
+ bool stream_ok = false; // A stream is ok if at least one track succeed.
+ // Update tracks based on its type.
+ for (size_t j = 0; j < audiotracklist->count(); ++j) {
+ scoped_refptr<MediaStreamTrackInterface> track(audiotracklist->at(j));
+ const cricket::ContentInfo* audio_content =
+ GetFirstAudioContent(answer_desc);
+ if (!audio_content) { // The remote does not accept audio.
+ track->set_state(MediaStreamTrackInterface::kFailed);
+ continue;
+ }
+
+ const cricket::AudioContentDescription* audio_desc =
+ static_cast<const cricket::AudioContentDescription*>(
+ audio_content->description);
+ if (audio_desc->codecs().size() <= 0) {
+ // No common codec.
+ track->set_state(MediaStreamTrackInterface::kFailed);
+ }
+ track->set_state(MediaStreamTrackInterface::kLive);
+ stream_ok = true;
+ }
+
+ for (size_t j = 0; j < videotracklist->count(); ++j) {
+ scoped_refptr<MediaStreamTrackInterface> track(videotracklist->at(j));
+ const cricket::ContentInfo* video_content =
+ GetFirstVideoContent(answer_desc);
+ if (!video_content) { // The remote does not accept video.
+ track->set_state(MediaStreamTrackInterface::kFailed);
+ continue;
+ }
+
+ const cricket::VideoContentDescription* video_desc =
+ static_cast<const cricket::VideoContentDescription*>(
+ video_content->description);
+ if (video_desc->codecs().size() <= 0) {
+ // No common codec.
+ track->set_state(MediaStreamTrackInterface::kFailed);
+ }
+ track->set_state(MediaStreamTrackInterface::kLive);
+ stream_ok = true;
+ }
+
+ if (stream_ok) {
+ // We have successfully negotiated to send this stream.
+ // Change the stream and store it as successfully negotiated.
+ stream->set_ready_state(MediaStreamInterface::kLive);
+ current_local_streams->AddStream(stream);
+ } else {
+ stream->set_ready_state(MediaStreamInterface::kEnded);
+ }
+ }
+
+ // Iterate the old list of local streams.
+ // If a stream is not found in the new list it have been removed.
+ // Change the state of the removed stream and all its tracks to kEnded.
+ for (size_t i = 0; i < local_streams_->count(); ++i) {
+ MediaStreamInterface* old_stream = local_streams_->at(i);
+ MediaStreamInterface* new_streams =
+ negotiated_streams->find(old_stream->label());
+
+ if (new_streams != NULL) continue;
+
+ old_stream->set_ready_state(MediaStreamInterface::kEnded);
+ scoped_refptr<AudioTracks> audio_tracklist(old_stream->audio_tracks());
+ for (size_t j = 0; j < audio_tracklist->count(); ++j) {
+ audio_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+ }
+ scoped_refptr<VideoTracks> video_tracklist(old_stream->video_tracks());
+ for (size_t j = 0; j < video_tracklist->count(); ++j) {
+ video_tracklist->at(j)->set_state(MediaStreamTrackInterface::kEnded);
+ }
+ }
+
+ // Update the local_streams_ for next update.
+ local_streams_ = current_local_streams;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionsignaling.h b/talk/app/webrtc/peerconnectionsignaling.h
new file mode 100644
index 0000000..9e195ee
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling.h
@@ -0,0 +1,262 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains classes used for handling signaling between
+// two PeerConnections.
+
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/app/webrtc/roapsession.h"
+#include "talk/app/webrtc/webrtcsessionobserver.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace cricket {
+class ChannelManager;
+class Candidate;
+typedef std::vector<Candidate> Candidates;
+}
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+class SessionDescriptionProvider;
+class StreamCollection;
+class StreamCollectionInterface;
+class MediaStreamInterface;
+
+// PeerConnectionSignaling is a class responsible for handling signaling
+// between two PeerConnection objects. It creates remote MediaStream objects
+// when the remote peer signals it wants to send a new MediaStream. It changes
+// the state of local MediaStreams and tracks when a remote peer is ready to
+// receive media.
+//
+// PeerConnectionSignaling is Thread-compatible and all non-const methods are
+// expected to be called on the signaling thread.
+//
+// Note that before PeerConnectionSignaling can process an answer or create an
+// offer OnCandidatesReady has to be called. The last request to create an offer
+// or process an answer will be processed after OnCandidatesReady has been
+// called.
+//
+// Call CreateOffer to negotiate new local streams to send.
+// Call ProcessSignalingMessage when a new message has been received from the
+// remote peer. This might result in one or more signals being triggered to
+// indicate changes in the offer from the the remote peer or a detected error.
+// PeerConnectionSignaling creates Offers and Answers asynchronous on the
+// signaling thread.
+//
+// Example usage: Creating an offer with one audio track.
+//
+// class ProviderImpl : public SessionDescriptionProvider {
+// ...
+// };
+//
+// void OnSignalingMessage(const std::string& smessage) { ... }
+//
+// ProviderImpl impl;
+// PeerConnectionSignaling pc(talk_base::Thread::Current(), &impl);
+//
+// // Connect the function OnSignalingMessage to the signal
+// // SignalNewPeerConnectionMessage.
+// pc.SignalNewPeerConnectionMessage.connect(&OnSignalingMessage);
+//
+// // Initialize PeerConnectionSignaling by providing the candidates for
+// // this session.
+// pc.OnCandidatesReady(candidates);
+// // Create an offer with one stream with one audio track.
+// AudioTrack audio;
+// MediaStream local_stream1;
+// local_stream1.AddTrack(&audio);
+// StreamCollection local_streams;
+// local_streams.AddStream(&local_stream1)
+// pc.CreateOffer(&local_streams);
+// // When the offer has been created, OnsignalingMessage is called
+// // with the offer in a string. Provide this offer to the remote
+// // PeerConnection. The remote PeerConnection will then respond with an answer
+// // string. Provide this answer string to PeerConnectionSignaling.
+// pc.ProcessSignalingMessage(remote_message, &local_streams);
+
+
+class PeerConnectionSignaling : public WebRtcSessionObserver,
+ public talk_base::MessageHandler {
+ public:
+ enum State {
+ // Awaiting the local candidates.
+ kInitializing,
+ // Ready to sent new offer or receive a new offer.
+ kIdle,
+ // An offer has been sent and expect an answer.
+ kWaitingForAnswer,
+ // An answer have been sent and expect an ok message.
+ kWaitingForOK,
+ // SendShutdown has been called. No more messages are processed.
+ kShutingDown,
+ // Shutdown message have been received or remote peer have answered ok
+ // to a sent shutdown message.
+ kShutdownComplete,
+ };
+
+ // Constructs a PeerConnectionSignaling instance.
+ // signaling_thread - the thread where all signals will be triggered from.
+ // Also all calls to to methods are expected to be called on this thread.
+ // provider - Implementation of the SessionDescriptionProvider interface.
+ // This interface provides methods for returning local offer and answer
+ // session descriptions as well as functions for receiving events about
+ // negotiation completion and received remote session descriptions.
+ PeerConnectionSignaling(talk_base::Thread* signaling_thread,
+ SessionDescriptionProvider* provider);
+ virtual ~PeerConnectionSignaling();
+
+ // Process a received offer/answer from the remote peer.
+ // local_streams must be the collection of streams the peerconnection
+ // currently would like to send.
+ void ProcessSignalingMessage(const std::string& message,
+ StreamCollectionInterface* local_streams);
+
+ // Creates an offer containing all tracks in local_streams.
+ // When the offer is ready it is signaled by SignalNewPeerConnectionMessage.
+ // When the remote peer is ready to receive media on a stream , the state of
+ // the local streams will change to kLive.
+ void CreateOffer(StreamCollectionInterface* local_streams);
+
+ // Creates a ShutDown message to be sent to the remote peer.
+ // When the message is ready it is signaled by SignalNewPeerConnectionMessage.
+ // After calling this no more offers or answers to offers can be created.
+ void SendShutDown();
+
+ // Implements WebRtcSessionObserver interface.
+ // OnCandidatesReady is called when local candidates have been collected.
+ // This tell PeerConnectionSignaling that it is ready to respond to offers
+ // and create offer messages.
+ virtual void OnCandidatesReady(const cricket::Candidates& candidates);
+
+ // Returns all current remote MediaStreams.
+ StreamCollection* remote_streams() { return remote_streams_.get(); }
+
+ // Returns the current state.
+ State GetState() const { return state_; }
+
+ // A new ROAP message is ready to be sent. The listener to this signal is
+ // supposed to deliver this message to the remote peer.
+ sigslot::signal1<const std::string&> SignalNewPeerConnectionMessage;
+
+ // A new remote stream has been discovered.
+ sigslot::signal1<MediaStreamInterface*> SignalRemoteStreamAdded;
+
+ // A remote stream is no longer available.
+ sigslot::signal1<MediaStreamInterface*> SignalRemoteStreamRemoved;
+
+ // The signaling state have changed.
+ sigslot::signal1<State> SignalStateChange;
+
+ // Remote PeerConnection sent an error message.
+ sigslot::signal1<RoapErrorCode> SignalErrorMessageReceived;
+
+ private:
+ typedef std::list<talk_base::scoped_refptr<StreamCollectionInterface> >
+ StreamCollectionList;
+
+ // Implements talk_base::MessageHandler.
+ virtual void OnMessage(talk_base::Message* msg);
+
+ // Change the State and triggers the SignalStateChange signal.
+ void ChangeState(State new_state);
+
+ // Creates an offer on the signaling_thread_.
+ // This is either initiated by CreateOffer or OnCandidatesReady.
+ void CreateOffer_s();
+
+ // Creates an answer on the signaling thread.
+ // This is either initiated by ProcessSignalingMessage when a remote offer
+ // have been received or OnCandidatesReady.
+ void CreateAnswer_s();
+
+ // Notifies the provider_ and the active remote media streams
+ // about the shutdown.
+ // This is either initiated by ProcessSignalingMessage when a remote shutdown
+ // message have been received or by a call to SendShutDown.
+ void DoShutDown();
+
+ // Creates and destroys remote media streams based on remote_desc.
+ void UpdateRemoteStreams(const cricket::SessionDescription* remote_desc);
+
+ // Updates the state of local streams based on the answer_desc and the streams
+ // that have been negotiated in negotiated_streams.
+ void UpdateSendingLocalStreams(
+ const cricket::SessionDescription* answer_desc,
+ StreamCollectionInterface* negotiated_streams);
+
+ talk_base::Thread* signaling_thread_;
+ SessionDescriptionProvider* provider_;
+ State state_;
+
+ // Flag indicating PeerConnectionSignaling was called with an offer while
+ // PeerConnectionSignaling is in kInitializing state.
+ bool received_pre_offer_;
+
+ // LocalStreams queued for later use if ProcessSignalingMessage or CreateOffer
+ // is called while PeerConnectionSignaling is in kInitializing state or
+ // CreateOffer is called while PeerConnectionSignaling is currently sending
+ // an offer.
+ StreamCollectionList queued_local_streams_;
+
+ // Currently known remote MediaStreams.
+ talk_base::scoped_refptr<StreamCollection> remote_streams_;
+
+ // The local session description of the local MediaStreams that is being
+ // negotiated.
+ talk_base::scoped_ptr<const cricket::SessionDescription> local_desc_;
+
+ // Local MediaStreams being negotiated.
+ talk_base::scoped_refptr<StreamCollection> local_streams_;
+
+ // The set of local transport candidates used in negotiation.
+ // This is set by OnCandidatesReady.
+ cricket::Candidates candidates_;
+
+ // roap_session_ holds the ROAP-specific session state and is used for
+ // creating a parsing ROAP messages.
+ RoapSession roap_session_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeerConnectionSignaling);
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PEERCONNECTIONSIGNALING_H_
diff --git a/talk/app/webrtc/peerconnectionsignaling_unittest.cc b/talk/app/webrtc/peerconnectionsignaling_unittest.cc
new file mode 100644
index 0000000..8980128
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionsignaling_unittest.cc
@@ -0,0 +1,592 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "talk/app/webrtc/audiotrackimpl.h"
+#include "talk/app/webrtc/mediastreamimpl.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/streamcollectionimpl.h"
+#include "talk/app/webrtc/videotrackimpl.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const char kVideoTrackLabel1[] = "local_video_1";
+
+namespace webrtc {
+
+typedef std::map<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
+ MediaStreamMap;
+typedef std::pair<std::string, talk_base::scoped_refptr<MediaStreamInterface> >
+ RemotePair;
+
+// MockSignalingObserver implements functions for listening all signals from a
+// PeerConnectionSignaling instance.
+// The method AnswerPeer can be used to forward messages from one
+// PeerConnectionSignaling instance to another.
+class MockSignalingObserver : public sigslot::has_slots<> {
+ public:
+ MockSignalingObserver()
+ : last_error_(-1), // Initialize last_error_ to unused error code.
+ state_(PeerConnectionSignaling::kInitializing),
+ remote_peer_(NULL) {
+ }
+
+ virtual ~MockSignalingObserver() {}
+
+ // New remote stream have been discovered.
+ virtual void OnRemoteStreamAdded(MediaStreamInterface* remote_stream) {
+ EXPECT_EQ(MediaStreamInterface::kLive, remote_stream->ready_state());
+ remote_media_streams_.insert(RemotePair(remote_stream->label(),
+ remote_stream));
+ }
+
+ // Remote stream is no longer available.
+ virtual void OnRemoteStreamRemoved(MediaStreamInterface* remote_stream) {
+ EXPECT_TRUE(remote_media_streams_.find(remote_stream->label()) !=
+ remote_media_streams_.end());
+ EXPECT_EQ(MediaStreamInterface::kEnded, remote_stream->ready_state());
+ remote_media_streams_.erase(remote_stream->label());
+ }
+
+ virtual void OnStateChange(PeerConnectionSignaling::State state) {
+ state_ = state;
+ }
+
+ virtual void OnErrorReceived(RoapErrorCode error) {
+ last_error_ = error;
+ }
+
+ void OnSignalingMessage(const std::string& smessage) {
+ last_message_ = smessage;
+ if (remote_peer_) {
+ remote_peer_->ProcessSignalingMessage(smessage, remote_local_collection_);
+ }
+ }
+
+ // Tell this object to answer the remote_peer.
+ // remote_local_collection is the local collection the remote peer want to
+ // send in an answer.
+ void AnswerPeer(PeerConnectionSignaling* remote_peer,
+ StreamCollection* remote_local_collection) {
+ remote_peer_ = remote_peer;
+ remote_local_collection_ = remote_local_collection;
+ }
+
+ void CancelAnswerPeer() {
+ remote_peer_ = NULL;
+ remote_local_collection_.release();
+ }
+
+ MediaStreamInterface* RemoteStream(const std::string& label) {
+ MediaStreamMap::iterator it = remote_media_streams_.find(label);
+ if (it != remote_media_streams_.end())
+ return it->second;
+ return NULL;
+ }
+
+ std::string last_message_;
+ int last_error_;
+ PeerConnectionSignaling::State state_;
+
+ private:
+ MediaStreamMap remote_media_streams_;
+ talk_base::scoped_refptr<StreamCollection> remote_local_collection_;
+ PeerConnectionSignaling* remote_peer_;
+};
+
+// Mock implementation of SessionDescriptionProvider.
+// PeerConnectionSignaling uses this object to create session descriptions.
+class MockSessionDescriptionProvider : public SessionDescriptionProvider {
+ public:
+ explicit MockSessionDescriptionProvider(
+ cricket::ChannelManager* channel_manager)
+ : update_session_description_counter_(0),
+ session_description_factory_(
+ new cricket::MediaSessionDescriptionFactory(channel_manager)),
+ offer_set_(false) {
+ }
+ virtual cricket::SessionDescription* CreateOffer(
+ const cricket::MediaSessionOptions& options) {
+ return session_description_factory_->CreateOffer(options,
+ local_desc_.get());
+ }
+ virtual cricket::SessionDescription* CreateAnswer(
+ const cricket::SessionDescription*offer,
+ const cricket::MediaSessionOptions& options) {
+ return session_description_factory_->CreateAnswer(offer, options,
+ local_desc_.get());
+ }
+ virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+ cricket::ContentAction type) {
+ local_desc_.reset(desc);
+ UpdateNegotiationState(type);
+ }
+ virtual void SetRemoteDescription(
+ cricket::SessionDescription* remote_offer,
+ cricket::ContentAction type) {
+ remote_desc_.reset(remote_offer);
+ UpdateNegotiationState(type);
+ }
+ virtual const cricket::SessionDescription* local_description() const {
+ return local_desc_.get();
+ }
+ virtual const cricket::SessionDescription* remote_description() const {
+ return remote_desc_.get();
+ }
+ virtual void SetRemoteCandidates(
+ const std::vector<cricket::Candidate>& remote_candidates) {
+ }
+
+ // |update_session_description_counter_| is the number of successful
+ // negotiations / re-negotiations.
+ size_t update_session_description_counter_;
+
+ protected:
+ void UpdateNegotiationState(cricket::ContentAction type) {
+ if (type == cricket::CA_ANSWER && offer_set_) {
+ // We have received and offer and now we receive an answer.
+ // Negotiation is done. Update the counter to indicate this.
+ ++update_session_description_counter_;
+ offer_set_ = false;
+ } else {
+ // Received an offer when expecting an answer.
+ EXPECT_FALSE(offer_set_);
+ offer_set_ = true;
+ }
+ }
+
+ talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory>
+ session_description_factory_;
+ talk_base::scoped_ptr<const cricket::SessionDescription> local_desc_;
+ talk_base::scoped_ptr<const cricket::SessionDescription> remote_desc_;
+ bool offer_set_;
+};
+
+// PeerConnectionSignalingTest create two PeerConnectionSignaling instances
+// and connects the signals to two MockSignalingObservers.
+// This is used in tests to test the signaling between to peers.
+class PeerConnectionSignalingTest: public testing::Test {
+ protected:
+ virtual void SetUp() {
+ channel_manager_.reset(new cricket::ChannelManager(
+ talk_base::Thread::Current()));
+ EXPECT_TRUE(channel_manager_->Init());
+ provider1_.reset(new MockSessionDescriptionProvider(
+ channel_manager_.get()));
+ provider2_.reset(new MockSessionDescriptionProvider(
+ channel_manager_.get()));
+
+ signaling1_.reset(new PeerConnectionSignaling(
+ talk_base::Thread::Current(), provider1_.get()));
+ observer1_.reset(new MockSignalingObserver());
+ signaling1_->SignalNewPeerConnectionMessage.connect(
+ observer1_.get(), &MockSignalingObserver::OnSignalingMessage);
+ signaling1_->SignalRemoteStreamAdded.connect(
+ observer1_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
+ signaling1_->SignalRemoteStreamRemoved.connect(
+ observer1_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
+ signaling1_->SignalErrorMessageReceived.connect(
+ observer1_.get(), &MockSignalingObserver::OnErrorReceived);
+ signaling1_->SignalStateChange.connect(
+ observer1_.get(), &MockSignalingObserver::OnStateChange);
+
+ signaling2_.reset(new PeerConnectionSignaling(
+ talk_base::Thread::Current(), provider2_.get()));
+ observer2_.reset(new MockSignalingObserver());
+ signaling2_->SignalNewPeerConnectionMessage.connect(
+ observer2_.get(), &MockSignalingObserver::OnSignalingMessage);
+ signaling2_->SignalRemoteStreamAdded.connect(
+ observer2_.get(), &MockSignalingObserver::OnRemoteStreamAdded);
+ signaling2_->SignalRemoteStreamRemoved.connect(
+ observer2_.get(), &MockSignalingObserver::OnRemoteStreamRemoved);
+ signaling2_->SignalErrorMessageReceived.connect(
+ observer2_.get(), &MockSignalingObserver::OnErrorReceived);
+ signaling2_->SignalStateChange.connect(
+ observer2_.get(), &MockSignalingObserver::OnStateChange);
+ }
+
+ // Create a collection of streams be sent on signaling1__
+ talk_base::scoped_refptr<StreamCollection> CreateLocalCollection1() {
+ std::string label(kStreamLabel1);
+ talk_base::scoped_refptr<LocalMediaStreamInterface> stream1(
+ MediaStream::Create(label));
+
+ // Add a local audio track.
+ talk_base::scoped_refptr<LocalAudioTrackInterface>
+ audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
+ stream1->AddTrack(audio_track);
+
+ // Add a local video track.
+ talk_base::scoped_refptr<LocalVideoTrackInterface>
+ video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
+ stream1->AddTrack(video_track);
+
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ StreamCollection::Create());
+ local_collection1->AddStream(stream1);
+
+ return local_collection1;
+ }
+
+ talk_base::scoped_refptr<StreamCollection> CreateLocalCollection2() {
+ std::string label(kStreamLabel2);
+ talk_base::scoped_refptr<LocalMediaStreamInterface> stream2(
+ MediaStream::Create(label));
+
+ // Add a local audio track.
+ talk_base::scoped_refptr<LocalAudioTrackInterface>
+ audio_track(AudioTrack::CreateLocal(kAudioTrackLabel2, NULL));
+ stream2->AddTrack(audio_track);
+
+ talk_base::scoped_refptr<StreamCollection> local_collection2(
+ StreamCollection::Create());
+ local_collection2->AddStream(stream2);
+
+ return local_collection2;
+ }
+
+ void VerifyStreamStates(StreamCollection* collection,
+ MediaStreamInterface::ReadyState state,
+ MediaStreamTrackInterface::TrackState track_state) {
+ for (size_t i = 0; i < collection->count(); ++i) {
+ MediaStreamInterface* stream = collection->at(i);
+ EXPECT_EQ(state, stream->ready_state());
+ for (size_t j = 0; j < stream->audio_tracks()->count(); ++j) {
+ AudioTrackInterface* audio = stream->audio_tracks()->at(j);
+ EXPECT_EQ(track_state, audio->state());
+ }
+ for (size_t j = 0; j < stream->video_tracks()->count(); ++j) {
+ VideoTrackInterface* video = stream->video_tracks()->at(j);
+ EXPECT_EQ(track_state, video->state());
+ }
+ }
+ }
+
+ // Initialize and setup a simple call between signaling1_ and signaling2_.
+ // signaling1_ send stream with label kStreamLabel1 to signaling2_.
+ void SetUpOneWayCall() {
+ // Initialize signaling1_ and signaling_2 by providing the candidates.
+ signaling1_->OnCandidatesReady(candidates_);
+ signaling2_->OnCandidatesReady(candidates_);
+
+ // Create a local stream collection to be sent on signaling1_.
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ CreateLocalCollection1());
+
+ talk_base::scoped_refptr<StreamCollection> local_collection2(
+ StreamCollection::Create());
+
+ // Connect all messages sent from signaling1_ to be received on signaling2_
+ observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+ // Connect all messages sent from Peer2 to be received on Peer1
+ observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+ signaling1_->CreateOffer(local_collection1);
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling1_->GetState());
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+ // Process posted messages to generate the offer and the answer to the
+ // offer.
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Make sure all is setup.
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+ EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
+ EXPECT_EQ(0u, signaling1_->remote_streams()->count());
+ EXPECT_EQ(1u, signaling2_->remote_streams()->count());
+ }
+
+ cricket::Candidates candidates_;
+ talk_base::scoped_ptr<MockSignalingObserver> observer1_;
+ talk_base::scoped_ptr<MockSignalingObserver> observer2_;
+ talk_base::scoped_ptr<MockSessionDescriptionProvider> provider1_;
+ talk_base::scoped_ptr<MockSessionDescriptionProvider> provider2_;
+ talk_base::scoped_ptr<PeerConnectionSignaling> signaling1_;
+ talk_base::scoped_ptr<PeerConnectionSignaling> signaling2_;
+ talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+TEST_F(PeerConnectionSignalingTest, SimpleOneWayCall) {
+ // Peer 1 create an offer with only one audio track.
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ CreateLocalCollection1());
+
+ // Verify that the local stream is now initializing.
+ VerifyStreamStates(local_collection1.get(),
+ MediaStreamInterface::kInitializing,
+ MediaStreamTrackInterface::kInitializing);
+
+ // Peer 2 only receive. Create an empty collection
+ talk_base::scoped_refptr<StreamCollection> local_collection2(
+ StreamCollection::Create());
+
+ // Connect all messages sent from Peer1 to be received on Peer2
+ observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+ // Connect all messages sent from Peer2 to be received on Peer1
+ observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+ // Peer 1 generates the offer. It is not sent since there is no
+ // local candidates ready.
+ signaling1_->CreateOffer(local_collection1);
+
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+ EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling1_->GetState());
+
+ // Initialize signaling1_ by providing the candidates.
+ signaling1_->OnCandidatesReady(candidates_);
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling1_->GetState());
+ // Process posted messages to allow signaling_1 to send the offer.
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Verify that signaling_2 is still not initialized.
+ // Even though it have received an offer.
+ EXPECT_EQ(PeerConnectionSignaling::kInitializing, signaling2_->GetState());
+
+ // Provide the candidates to signaling_2 and let it process the offer.
+ signaling2_->OnCandidatesReady(candidates_);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Verify that the offer/answer have been exchanged and the state is good.
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+ // Verify that the local stream is now sending.
+ VerifyStreamStates(local_collection1, MediaStreamInterface::kLive,
+ MediaStreamTrackInterface::kLive);
+
+ // Verify that PeerConnection2 is aware of the sending stream.
+ EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) != NULL);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(1u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(1u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, Glare) {
+ // Setup a call.
+ SetUpOneWayCall();
+
+ // Stop sending all messages automatically between Peer 1 and Peer 2.
+ observer1_->CancelAnswerPeer();
+ observer2_->CancelAnswerPeer();
+
+ // Create an empty collection for Peer 1.
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ StreamCollection::Create());
+ // Create a collection for Peer 2.
+ talk_base::scoped_refptr<StreamCollection> local_collection2(
+ CreateLocalCollection2());
+
+ // Peer 1 create an updated offer.
+ signaling1_->CreateOffer(local_collection1);
+ // Peer 2 create an updated offer.
+ signaling2_->CreateOffer(local_collection2);
+
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ std::string offer_1 = observer1_->last_message_;
+ std::string offer_2 = observer2_->last_message_;
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling1_->GetState());
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling2_->GetState());
+
+ // Connect all messages sent from Peer 1 to be received on Peer 2
+ observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+ // Connect all messages sent from Peer 2 to be received on Peer 1
+ observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+ // Insert the two offers to each Peer to create the Glare.
+ signaling1_->ProcessSignalingMessage(offer_2, local_collection1);
+ signaling2_->ProcessSignalingMessage(offer_1, local_collection2);
+
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Make sure all is good.
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling2_->GetState());
+
+ // Verify that Peer 1 is receiving kStreamLabel2.
+ EXPECT_TRUE(observer1_->RemoteStream(kStreamLabel2) != NULL);
+ // Verify that Peer 2 don't receive any streams
+ // since it has been removed.
+ EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, AddRemoveStream) {
+ // Initialize signaling1_ and signaling_2 by providing the candidates.
+ signaling1_->OnCandidatesReady(candidates_);
+ signaling2_->OnCandidatesReady(candidates_);
+ // Create a local stream.
+ std::string label(kStreamLabel1);
+ talk_base::scoped_refptr<LocalMediaStreamInterface> stream(
+ MediaStream::Create(label));
+
+ // Add a local audio track.
+ talk_base::scoped_refptr<LocalAudioTrackInterface>
+ audio_track(AudioTrack::CreateLocal(kAudioTrackLabel1, NULL));
+ stream->AddTrack(audio_track);
+
+ // Add a local video track.
+ talk_base::scoped_refptr<LocalVideoTrackInterface>
+ video_track(VideoTrack::CreateLocal(kVideoTrackLabel1, NULL));
+ stream->AddTrack(video_track);
+
+ // Peer 1 create an empty collection
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ StreamCollection::Create());
+
+ // Peer 2 create an empty collection
+ talk_base::scoped_refptr<StreamCollection> local_collection2(
+ StreamCollection::Create());
+
+ // Connect all messages sent from Peer1 to be received on Peer2
+ observer1_->AnswerPeer(signaling2_.get(), local_collection2);
+ // Connect all messages sent from Peer2 to be received on Peer1
+ observer2_->AnswerPeer(signaling1_.get(), local_collection1);
+
+ // Peer 1 creates an empty offer and send it to Peer2.
+ signaling1_->CreateOffer(local_collection1);
+ // Process posted messages.
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(1u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(1u, provider2_->update_session_description_counter_);
+
+ // Peer2 add a stream.
+ local_collection2->AddStream(stream);
+
+ signaling2_->CreateOffer(local_collection2);
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Verify that the PeerConnection 2 local stream is now sending.
+ VerifyStreamStates(local_collection2, MediaStreamInterface::kLive ,
+ MediaStreamTrackInterface::kLive);
+
+ // Verify that PeerConnection1 is aware of the sending stream.
+ EXPECT_TRUE(observer1_->RemoteStream(label) != NULL);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+
+ // Remove the stream
+ local_collection2->RemoveStream(stream);
+
+ signaling2_->CreateOffer(local_collection2);
+ talk_base::Thread::Current()->ProcessMessages(1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+ // Verify that PeerConnection1 is not aware of the sending stream.
+ EXPECT_TRUE(observer1_->RemoteStream(label) == NULL);
+
+ // Verify that the PeerConnection 2 local stream is now ended.
+ VerifyStreamStates(local_collection2, MediaStreamInterface::kEnded ,
+ MediaStreamTrackInterface::kEnded);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(3u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(3u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, ShutDown) {
+ // Setup a call.
+ SetUpOneWayCall();
+
+ signaling1_->SendShutDown();
+
+ EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
+ signaling1_->GetState(), 10);
+ EXPECT_EQ_WAIT(PeerConnectionSignaling::kShutdownComplete,
+ signaling2_->GetState(), 10);
+
+ EXPECT_EQ(0u, signaling1_->remote_streams()->count());
+ EXPECT_EQ(0u, signaling2_->remote_streams()->count());
+ EXPECT_TRUE(observer2_->RemoteStream(kStreamLabel1) == NULL);
+ EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer1_->state_);
+ EXPECT_EQ(PeerConnectionSignaling::kShutdownComplete, observer2_->state_);
+
+ // Verify that both peers have updated the session descriptions.
+ EXPECT_EQ(2u, provider1_->update_session_description_counter_);
+ EXPECT_EQ(2u, provider2_->update_session_description_counter_);
+}
+
+TEST_F(PeerConnectionSignalingTest, ReceiveError) {
+ // Initialize signaling1_
+ signaling1_->OnCandidatesReady(candidates_);
+
+ talk_base::scoped_refptr<StreamCollection> local_collection1(
+ CreateLocalCollection1());
+
+ signaling1_->CreateOffer(local_collection1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling1_->GetState());
+
+ RoapSession roap_session;
+ roap_session.Parse(observer1_->last_message_);
+ signaling1_->ProcessSignalingMessage(roap_session.CreateErrorMessage(
+ kNoMatch), local_collection1);
+ EXPECT_EQ(kNoMatch, observer1_->last_error_);
+
+ // Check signaling have cleaned up.
+ EXPECT_EQ(PeerConnectionSignaling::kIdle, signaling1_->GetState());
+
+ signaling1_->CreateOffer(local_collection1);
+ talk_base::Thread::Current()->ProcessMessages(1);
+ EXPECT_EQ(PeerConnectionSignaling::kWaitingForAnswer,
+ signaling1_->GetState());
+}
+
+} // namespace webrtc
+
diff --git a/talk/app/webrtc/portallocatorfactory.cc b/talk/app/webrtc/portallocatorfactory.cc
new file mode 100644
index 0000000..e997216
--- /dev/null
+++ b/talk/app/webrtc/portallocatorfactory.cc
@@ -0,0 +1,95 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/portallocatorfactory.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+static const char kUserAgent[] = "PeerConnection User Agent";
+
+namespace webrtc {
+
+using talk_base::scoped_ptr;
+
+talk_base::scoped_refptr<PortAllocatorFactoryInterface>
+PortAllocatorFactory::Create(
+ talk_base::Thread* worker_thread) {
+ talk_base::RefCountedObject<PortAllocatorFactory>* allocator =
+ new talk_base::RefCountedObject<PortAllocatorFactory>(worker_thread);
+ return allocator;
+}
+
+PortAllocatorFactory::PortAllocatorFactory(talk_base::Thread* worker_thread)
+ : network_manager_(new talk_base::BasicNetworkManager()),
+ socket_factory_(new talk_base::BasicPacketSocketFactory(worker_thread)) {
+}
+
+PortAllocatorFactory::~PortAllocatorFactory() {}
+
+cricket::PortAllocator* PortAllocatorFactory::CreatePortAllocator(
+ const std::vector<StunConfiguration>& stun,
+ const std::vector<TurnConfiguration>& turn) {
+
+ scoped_ptr<cricket::HttpPortAllocator> allocator(
+ new cricket::HttpPortAllocator(
+ network_manager_.get(), socket_factory_.get(), kUserAgent));
+
+ std::vector<talk_base::SocketAddress> stun_hosts;
+ typedef std::vector<StunConfiguration>::const_iterator StunIt;
+ for (StunIt stun_it = stun.begin(); stun_it != stun.end(); ++stun_it) {
+ stun_hosts.push_back(stun_it->server);
+ }
+ allocator->SetStunHosts(stun_hosts);
+
+ if (turn.size() > 0)
+ LOG(LS_INFO) << "Not using turn server params";
+
+ // TODO - Enable TURN support once WebRtcSession can handle
+ // relay candidates.
+#if 0
+ std::vector<std::string> relay_hosts;
+ typedef std::vector<TurnConfiguration>::const_iterator TurnIt;
+ for (TurnIt turn_it = turn.begin(); turn_it != turn.end(); ++turn_it) {
+ relay_hosts.push_back(turn_it->server.hostname());
+ }
+ allocator->SetRelayHosts(relay_hosts);
+
+ // Currently we can only set the password of one relay server.
+ // Use the password of the first server. User name can currently not be set.
+ // TODO: See above limitations.
+ if (turn.size() > 0)
+ allocator->SetRelayToken(turn[0].password);
+#endif
+
+ return allocator.release();
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/portallocatorfactory.h b/talk/app/webrtc/portallocatorfactory.h
new file mode 100644
index 0000000..1b0d753
--- /dev/null
+++ b/talk/app/webrtc/portallocatorfactory.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file defines the default implementation of
+// PortAllocatorFactoryInterface.
+// This implementation creates instances of cricket::HTTPPortAllocator and uses
+// the BasicNetworkManager and BasicPacketSocketFactory.
+
+#ifndef TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
+#define TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace cricket {
+class PortAllocator;
+}
+
+namespace talk_base {
+class BasicNetworkManager;
+class BasicPacketSocketFactory;
+}
+
+namespace webrtc {
+
+class PortAllocatorFactory : public PortAllocatorFactoryInterface {
+ public:
+ static talk_base::scoped_refptr<PortAllocatorFactoryInterface> Create(
+ talk_base::Thread* worker_thread);
+
+ virtual cricket::PortAllocator* CreatePortAllocator(
+ const std::vector<StunConfiguration>& stun,
+ const std::vector<TurnConfiguration>& turn);
+
+ protected:
+ explicit PortAllocatorFactory(talk_base::Thread* worker_thread);
+ ~PortAllocatorFactory();
+
+ private:
+ talk_base::scoped_ptr<talk_base::BasicNetworkManager> network_manager_;
+ talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> socket_factory_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PORTALLOCATORFACTORY_H_
diff --git a/talk/app/webrtc/roaperrorcodes.h b/talk/app/webrtc/roaperrorcodes.h
new file mode 100644
index 0000000..d13230d
--- /dev/null
+++ b/talk/app/webrtc/roaperrorcodes.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contain an enum of possible error codes used in Roap.
+// The meaning of the error codes are defined in
+// http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+
+#ifndef TALK_APP_WEBRTC_ROAPERRORCODES_H_
+#define TALK_APP_WEBRTC_ROAPERRORCODES_H_
+
+namespace webrtc {
+
+enum RoapErrorCode {
+ kNoMatch,
+ kTimeout,
+ kRefused,
+ kConflict,
+ kDoubleConflict,
+ kFailed
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_ROAPERRORCODES_H_
diff --git a/talk/app/webrtc/roapmessages.cc b/talk/app/webrtc/roapmessages.cc
new file mode 100644
index 0000000..e0528a7
--- /dev/null
+++ b/talk/app/webrtc/roapmessages.cc
@@ -0,0 +1,293 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/roapmessages.h"
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/json.h"
+
+namespace webrtc {
+
+using cricket::Candidate;
+using cricket::SessionDescription;
+
+// ROAP message types. Must match the enum RoapMessageType.
+static const char* kMessageTypes[] = {
+ "OFFER",
+ "ANSWER",
+ "OK",
+ "SHUTDOWN",
+ "ERROR",
+};
+
+// ROAP error messages. Must match the enum RoapErrorCode.
+static const char* kErrorMessages[] = {
+ "NOMATCH",
+ "TIMEOUT",
+ "REFUSED",
+ "CONFLICT",
+ "DOUBLECONFLICT",
+ "FAILED",
+};
+
+// ROAP json keys.
+static const char kOffererSessionId[] = "offererSessionId";
+static const char kAnswererSessionId[] = "answererSessionId";
+static const char kSetSessionToken[] = "setSessionToken";
+static const char kSetResponseToken[] = "setResponseToken";
+static const char kResponseToken[] = "responseToken";
+static const char kSessionToken[] = "sessionToken";
+static const char kMessageType[] = "messageType";
+static const char kSequenceNumber[] = "seq";
+static const char kSessionDescription[] = "sdp";
+static const char kErrorType[] = "errorType";
+static const char kTieBreaker[] = "tieBreaker";
+static const char kMoreComing[] = "moreComing";
+
+RoapMessageBase::RoapMessageBase() : type_(kInvalid), seq_(0) {
+}
+
+RoapMessageBase::RoapMessageBase(RoapMessageType type,
+ const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq)
+ : type_(type),
+ offer_session_id_(offer_session_id),
+ answer_session_id_(answer_session_id),
+ session_token_(session_token),
+ response_token_(response_token),
+ seq_(seq) {
+}
+
+bool RoapMessageBase::Parse(const std::string& message) {
+ Json::Reader reader;
+ if (!reader.parse(message, jmessage_))
+ return false;
+
+ std::string message_type;
+ GetStringFromJsonObject(jmessage_, kMessageType, &message_type);
+ if (message_type.empty())
+ return false;
+ bool valid_message_type = false;
+ for (int i = 0; i < kInvalid; i++) {
+ if (message_type == kMessageTypes[i]) {
+ type_ = static_cast<RoapMessageType>(i);
+ valid_message_type = true;
+ break;
+ }
+ }
+ if (!valid_message_type)
+ return false;
+
+ if (!GetStringFromJsonObject(jmessage_, kOffererSessionId,
+ &offer_session_id_) ||
+ offer_session_id_.empty()) {
+ // Parse offererSessionId. Allow error messages to not have an
+ // offererSessionId.
+ if (type_ != kError)
+ return false;
+ }
+
+ // answererSessionId does not necessarily need to exist in MessageBase.
+ GetStringFromJsonObject(jmessage_, kAnswererSessionId, &answer_session_id_);
+ // setSessionToken and setResponseToken is not required.
+ GetStringFromJsonObject(jmessage_, kSetSessionToken, &session_token_);
+ GetStringFromJsonObject(jmessage_, kSetResponseToken, &response_token_);
+
+ unsigned int temp_seq;
+ if (!GetUIntFromJsonObject(jmessage_, kSequenceNumber, &temp_seq)) {
+ return false;
+ }
+ if (temp_seq > 0xFFFFFFFF)
+ return false;
+ seq_ = static_cast<uint32>(temp_seq);
+
+ return true;
+}
+
+std::string RoapMessageBase::Serialize() {
+ Json::Value message;
+ SerializeElement(&message);
+ Json::StyledWriter writer;
+ return writer.write(message);
+}
+
+void RoapMessageBase::SerializeElement(Json::Value* message) {
+ ASSERT(message != NULL);
+ (*message)[kMessageType] = kMessageTypes[type_];
+ (*message)[kOffererSessionId] = offer_session_id_;
+ if (!answer_session_id_.empty())
+ (*message)[kAnswererSessionId] = answer_session_id_;
+ if (!session_token_.empty())
+ (*message)[kSessionToken] = session_token_;
+ if (!response_token_.empty())
+ (*message)[kResponseToken] = response_token_;
+ (*message)[kSequenceNumber] = seq_;
+}
+
+RoapOffer::RoapOffer(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ uint32 seq,
+ uint32 tie_breaker,
+ const SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates)
+ : RoapMessageBase(kOffer, offer_session_id, answer_session_id,
+ session_token, "", seq),
+ tie_breaker_(tie_breaker),
+ desc_(desc),
+ candidates_(candidates) {
+}
+
+RoapOffer::RoapOffer(const RoapMessageBase& base)
+ : RoapMessageBase(base),
+ desc_(NULL) {}
+
+bool RoapOffer::Parse() {
+ if (!GetUIntFromJsonObject(jmessage_, kTieBreaker, &tie_breaker_)) {
+ return false;
+ }
+
+ std::string sdp_message;
+ if (!GetStringFromJsonObject(jmessage_, kSessionDescription, &sdp_message))
+ return false;
+
+ parsed_desc_.reset(new cricket::SessionDescription());
+ return SdpDeserialize(sdp_message, parsed_desc_.get(),
+ &candidates_);
+}
+
+void RoapOffer::SerializeElement(Json::Value* message) {
+ ASSERT(message != NULL);
+ RoapMessageBase::SerializeElement(message);
+ (*message)[kTieBreaker] = tie_breaker_;
+ (*message)[kSessionDescription] = SdpSerialize(*desc_, candidates_);
+}
+
+RoapAnswer::RoapAnswer(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq,
+ const SessionDescription* desc,
+ const std::vector<Candidate>& candidates)
+ : RoapMessageBase(kAnswer, offer_session_id, answer_session_id,
+ session_token, response_token, seq),
+ desc_(desc),
+ candidates_(candidates) {
+}
+
+RoapAnswer::RoapAnswer(const RoapMessageBase& base)
+ : RoapMessageBase(base),
+ more_coming_(false),
+ desc_(NULL) {}
+
+bool RoapAnswer::Parse() {
+ std::string more;
+ if (GetStringFromJsonObject(jmessage_, kMoreComing, &more) && more == "true")
+ more_coming_ = true;
+
+ std::string sdp_message;
+ if (!GetStringFromJsonObject(jmessage_, kSessionDescription, &sdp_message))
+ return false;
+
+ parsed_desc_.reset(new cricket::SessionDescription());
+ return SdpDeserialize(sdp_message, parsed_desc_.get(), &candidates_);
+}
+
+void RoapAnswer::SerializeElement(Json::Value* message) {
+ ASSERT(message != NULL);
+ RoapMessageBase::SerializeElement(message);
+
+ (*message)[kSessionDescription] = SdpSerialize(*desc_, candidates_);
+}
+
+RoapError::RoapError(const RoapMessageBase& base)
+ : RoapMessageBase(base), error_(kFailed) {
+}
+
+RoapError::RoapError(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq,
+ RoapErrorCode error)
+ : RoapMessageBase(kError, offer_session_id, answer_session_id,
+ session_token, response_token, seq),
+ error_(error) {
+}
+
+bool RoapError::Parse() {
+ std::string error_string;
+ GetStringFromJsonObject(jmessage_, kErrorType, &error_string);
+ if (error_string.empty())
+ return false;
+ for (int i = 0; i < ARRAY_SIZE(kErrorMessages); i++) {
+ if (error_string == kErrorMessages[i]) {
+ error_ = static_cast<RoapErrorCode>(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+void RoapError::SerializeElement(Json::Value* message) {
+ ASSERT(message != NULL);
+ ASSERT(error_< ARRAY_SIZE(kErrorMessages));
+ RoapMessageBase::SerializeElement(message);
+
+ (*message)[kErrorType] = kErrorMessages[error_];
+}
+
+RoapOk::RoapOk(const RoapMessageBase& base)
+ : RoapMessageBase(base) {
+}
+
+RoapOk::RoapOk(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq)
+ : RoapMessageBase(kOk, offer_session_id, answer_session_id, session_token,
+ response_token, seq) {
+}
+
+RoapShutdown::RoapShutdown(const RoapMessageBase& base)
+ : RoapMessageBase(base) {
+}
+
+RoapShutdown::RoapShutdown(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ uint32 seq)
+ : RoapMessageBase(kShutdown, offer_session_id, answer_session_id,
+ session_token, "", seq) {
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/roapmessages.h b/talk/app/webrtc/roapmessages.h
new file mode 100644
index 0000000..7b18010
--- /dev/null
+++ b/talk/app/webrtc/roapmessages.h
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contain classes for parsing and serializing ROAP messages.
+// The ROAP messages are defined in
+// http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+
+#ifndef TALK_APP_WEBRTC_ROAPMESSAGES_H_
+#define TALK_APP_WEBRTC_ROAPMESSAGES_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/json.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+class RoapMessageBase {
+ public:
+ enum RoapMessageType {
+ kOffer = 0,
+ kAnswer = 1,
+ kOk = 2,
+ kShutdown = 3,
+ kError = 4,
+ kInvalid = 5,
+ };
+ RoapMessageBase();
+ RoapMessageBase(RoapMessageType type,
+ const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq);
+
+ bool Parse(const std::string& message);
+ std::string Serialize();
+
+ RoapMessageType type() const { return type_; }
+ const std::string& offer_session_id() const { return offer_session_id_; }
+ const std::string& answer_session_id() const { return answer_session_id_; }
+ const std::string& session_token() const { return session_token_; }
+ const std::string& response_token() const { return response_token_; }
+ uint32 seq() const { return seq_; }
+
+ protected:
+ virtual void SerializeElement(Json::Value* message);
+ Json::Value jmessage_; // Contains the parsed json message.
+
+ private:
+ RoapMessageType type_;
+ std::string offer_session_id_;
+ std::string answer_session_id_;
+ std::string session_token_;
+ std::string response_token_;
+ uint32 seq_;
+};
+
+class RoapAnswer : public RoapMessageBase {
+ public:
+ explicit RoapAnswer(const RoapMessageBase& base);
+ // Note that the SessionDescription desc is used as a weak reference.
+ // The user of this class must ensure that desc outlives an instance of this
+ // object.
+ RoapAnswer(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq,
+ const cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates);
+ bool Parse();
+
+ // Get remote SessionDescription if the session description has been parsed
+ // and the ownership is transferred to the caller.
+ // NULL otherwise.
+ cricket::SessionDescription* ReleaseSessionDescription() {
+ return parsed_desc_.release();
+ }
+ const std::vector<cricket::Candidate>& candidates() const {
+ return candidates_;
+ }
+ bool more_coming() const { return more_coming_ ; }
+
+ protected:
+ virtual void SerializeElement(Json::Value* message);
+
+ private:
+ bool more_coming_;
+ // Session description parsed in an offer.
+ talk_base::scoped_ptr<cricket::SessionDescription> parsed_desc_;
+ // Weak ref to a session description provided in the constructor.
+ const cricket::SessionDescription* desc_;
+ std::vector<cricket::Candidate> candidates_;
+};
+
+class RoapOffer : public RoapMessageBase {
+ public:
+ explicit RoapOffer(const RoapMessageBase& base);
+ // Note that the SessionDescription desc is used as a weak reference.
+ // The user of this class must ensure that desc outlives an instance of this
+ // object.
+ RoapOffer(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ uint32 seq,
+ uint32 tie_breaker,
+ const cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates);
+ bool Parse();
+
+ uint32 tie_breaker() const { return tie_breaker_; }
+ // Get remote SessionDescription if the session description has been parsed
+ // and the ownership is transferred to the caller.
+ // NULL otherwise.
+ cricket::SessionDescription* ReleaseSessionDescription() {
+ return parsed_desc_.release();
+ }
+ const std::vector<cricket::Candidate>& candidates() { return candidates_; }
+
+ protected:
+ virtual void SerializeElement(Json::Value* message);
+
+ private:
+ uint32 tie_breaker_;
+ // Session description parsed in an offer.
+ talk_base::scoped_ptr<cricket::SessionDescription> parsed_desc_;
+ // Weak reference to a session description provided in the constructor.
+ const cricket::SessionDescription* desc_;
+ std::vector<cricket::Candidate> candidates_;
+};
+
+class RoapError : public RoapMessageBase {
+ public:
+ explicit RoapError(const RoapMessageBase& base);
+ RoapError(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq,
+ RoapErrorCode error);
+ bool Parse();
+ RoapErrorCode error() const { return error_; }
+
+ protected:
+ virtual void SerializeElement(Json::Value* message);
+
+ private:
+ RoapErrorCode error_;
+};
+
+class RoapOk : public RoapMessageBase {
+ public:
+ explicit RoapOk(const RoapMessageBase& base);
+ RoapOk(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ const std::string& response_token,
+ uint32 seq);
+};
+
+class RoapShutdown : public RoapMessageBase {
+ public:
+ explicit RoapShutdown(const RoapMessageBase& base);
+ RoapShutdown(const std::string& offer_session_id,
+ const std::string& answer_session_id,
+ const std::string& session_token,
+ uint32 seq);
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_ROAPMESSAGES_H_
diff --git a/talk/app/webrtc/roapmessages_unittest.cc b/talk/app/webrtc/roapmessages_unittest.cc
new file mode 100644
index 0000000..b432667
--- /dev/null
+++ b/talk/app/webrtc/roapmessages_unittest.cc
@@ -0,0 +1,227 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/session/phone/mediasession.h"
+
+using cricket::Candidates;
+using cricket::AudioContentDescription;
+using cricket::SessionDescription;
+using cricket::StreamParams;
+using cricket::VideoContentDescription;
+
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+
+static const char kOfferSessionId[] = "offer_1";
+static const char kAnswerSessionId[] = "answer_1";
+static const char kSessionToken[] = "session_1";
+
+static const char kOfferReference[] =
+ "{\n"
+ " \"answererSessionId\" : \"answer_1\",\n"
+ " \"messageType\" : \"OFFER\",\n"
+ " \"offererSessionId\" : \"offer_1\",\n"
+ " \"sdp\" : \"v=0\\r\\n"
+ "o=- 0 0 IN IP4 127.0.0.1\\r\\n"
+ "s=\\r\\n"
+ "t=0 0\\r\\n"
+ "m=audio 0 RTP/AVPF\\r\\n"
+ "a=mid:audio\\r\\n"
+ "a=rtcp-mux\\r\\n"
+ "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_audio_1\\r\\n"
+ "\",\n" // End of sdp.
+ " \"seq\" : 1,\n"
+ " \"tieBreaker\" : 0\n"
+ "}\n";
+
+static const char kAnswerReference[] =
+ "{\n"
+ " \"answererSessionId\" : \"answer_1\",\n"
+ " \"messageType\" : \"ANSWER\",\n"
+ " \"offererSessionId\" : \"offer_1\",\n"
+ " \"sdp\" : \"v=0\\r\\n"
+ "o=- 0 0 IN IP4 127.0.0.1\\r\\n"
+ "s=\\r\\n"
+ "t=0 0\\r\\n"
+ "m=audio 0 RTP/AVPF\\r\\n"
+ "a=mid:audio\\r\\n"
+ "a=rtcp-mux\\r\\n"
+ "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_audio_1\\r\\n"
+ "\",\n" // End of sdp.
+ " \"seq\" : 1\n"
+ "}\n";
+
+static const char kOkReference[] =
+ "{\n"
+ " \"answererSessionId\" : \"answer_1\",\n"
+ " \"messageType\" : \"OK\",\n"
+ " \"offererSessionId\" : \"offer_1\",\n"
+ " \"seq\" : 1\n"
+ "}\n";
+
+static const char kShutdownReference[] =
+ "{\n"
+ " \"answererSessionId\" : \"answer_1\",\n"
+ " \"messageType\" : \"SHUTDOWN\",\n"
+ " \"offererSessionId\" : \"offer_1\",\n"
+ " \"seq\" : 1\n"
+ "}\n";
+
+static const char kErrorReference[] =
+ "{\n"
+ " \"answererSessionId\" : \"answer_1\",\n"
+ " \"errorType\" : \"TIMEOUT\",\n"
+ " \"messageType\" : \"ERROR\",\n"
+ " \"offererSessionId\" : \"offer_1\",\n"
+ " \"seq\" : 1\n"
+ "}\n";
+
+// RoapMessageTest creates a session description that matches the
+// reference messages above.
+class RoapMessageTest: public testing::Test {
+ public:
+ void SetUp() {
+ talk_base::scoped_ptr<AudioContentDescription> audio(
+ new AudioContentDescription());
+ audio->set_rtcp_mux(true);
+ StreamParams audio_stream;
+ audio_stream.name = kAudioTrackLabel1;
+ audio_stream.cname = kStream1Cname;
+ audio_stream.sync_label = kStreamLabel1;
+ audio_stream.ssrcs.push_back(kAudioTrack1Ssrc);
+ audio->AddStream(audio_stream);
+ desc1_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ audio.release());
+ }
+ protected:
+ cricket::SessionDescription desc1_;
+ cricket::Candidates empty_candidates_;
+};
+
+static bool CompareRoapBase(const webrtc::RoapMessageBase& base1,
+ const webrtc::RoapMessageBase& base2) {
+ return base1.type() == base2.type() &&
+ base1.offer_session_id() == base2.offer_session_id() &&
+ base1.answer_session_id() == base2.answer_session_id() &&
+ base1.session_token() == base2.session_token() &&
+ base1.response_token() == base2.response_token() &&
+ base1.seq() == base2.seq();
+}
+
+static bool CompareRoapOffer(const webrtc::RoapOffer& offer1,
+ const webrtc::RoapOffer& offer2) {
+ return CompareRoapBase(offer1, offer2) &&
+ offer1.tie_breaker() == offer2.tie_breaker();
+}
+
+static bool CompareRoapAnswer(const webrtc::RoapAnswer& answer1,
+ const webrtc::RoapAnswer& answer2) {
+ return CompareRoapBase(answer1, answer2) &&
+ answer1.more_coming() == answer1.more_coming();
+}
+
+static bool CompareRoapError(const webrtc::RoapError& error1,
+ const webrtc::RoapError& error2) {
+ return CompareRoapBase(error1, error2) &&
+ error1.error() == error2.error();
+}
+
+TEST_F(RoapMessageTest, RoapOffer) {
+ webrtc::RoapOffer offer(kOfferSessionId, kAnswerSessionId, "", 1, 0, &desc1_,
+ empty_candidates_);
+ std::string offer_string = offer.Serialize();
+ EXPECT_TRUE(kOfferReference == offer_string);
+
+ webrtc::RoapMessageBase base;
+ EXPECT_TRUE(base.Parse(kOfferReference));
+ EXPECT_EQ(webrtc::RoapMessageBase::kOffer, base.type());
+ webrtc::RoapOffer parsed_offer(base);
+ EXPECT_TRUE(parsed_offer.Parse());
+ EXPECT_TRUE(CompareRoapOffer(offer, parsed_offer));
+}
+
+TEST_F(RoapMessageTest, RoapAnswer) {
+ webrtc::RoapAnswer answer(kOfferSessionId, kAnswerSessionId, "", "", 1,
+ &desc1_, empty_candidates_);
+ std::string answer_string = answer.Serialize();
+ EXPECT_TRUE(kAnswerReference == answer_string);
+
+ webrtc::RoapMessageBase base;
+ EXPECT_TRUE(base.Parse(kAnswerReference));
+ EXPECT_EQ(webrtc::RoapMessageBase::kAnswer, base.type());
+ webrtc::RoapAnswer parsed_answer(base);
+ EXPECT_TRUE(parsed_answer.Parse());
+ EXPECT_TRUE(CompareRoapAnswer(answer, parsed_answer));
+}
+
+TEST_F(RoapMessageTest, RoapOk) {
+ webrtc::RoapOk ok(kOfferSessionId, kAnswerSessionId, "", "", 1);
+ std::string ok_string = ok.Serialize();
+ EXPECT_TRUE(kOkReference == ok_string);
+
+ webrtc::RoapMessageBase base;
+ EXPECT_TRUE(base.Parse(kOkReference));
+ EXPECT_EQ(webrtc::RoapMessageBase::kOk, base.type());
+ webrtc::RoapOk parsed_ok(base);
+ EXPECT_TRUE(CompareRoapBase(ok, parsed_ok));
+}
+
+TEST_F(RoapMessageTest, RoapShutdown) {
+ webrtc::RoapShutdown shutdown(kOfferSessionId, kAnswerSessionId, "", 1);
+ std::string shutdown_string = shutdown.Serialize();
+ EXPECT_TRUE(kShutdownReference == shutdown_string);
+
+ webrtc::RoapMessageBase base;
+ EXPECT_TRUE(base.Parse(kShutdownReference));
+ EXPECT_EQ(webrtc::RoapMessageBase::kShutdown, base.type());
+ webrtc::RoapShutdown parsed_shutdown(base);
+ EXPECT_TRUE(CompareRoapBase(shutdown, parsed_shutdown));
+}
+
+TEST_F(RoapMessageTest, RoapError) {
+ webrtc::RoapError error(kOfferSessionId, kAnswerSessionId, "", "", 1,
+ webrtc::kTimeout);
+ std::string error_string = error.Serialize();
+ EXPECT_TRUE(kErrorReference == error_string);
+
+ webrtc::RoapMessageBase base;
+ EXPECT_TRUE(base.Parse(kErrorReference));
+ EXPECT_EQ(webrtc::RoapMessageBase::kError, base.type());
+ webrtc::RoapError parsed_error(base);
+ EXPECT_TRUE(parsed_error.Parse());
+ EXPECT_TRUE(CompareRoapError(error, parsed_error));
+}
diff --git a/talk/app/webrtc/roapsession.cc b/talk/app/webrtc/roapsession.cc
new file mode 100644
index 0000000..014539f
--- /dev/null
+++ b/talk/app/webrtc/roapsession.cc
@@ -0,0 +1,286 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/roapsession.h"
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+static const uint32 kMaxTieBreaker = 0xFFFFFFFE;
+
+static std::string CreateLocalId(const std::string& remote_id) {
+ std::string local_id;
+ do {
+ talk_base::CreateRandomString(32, &local_id);
+ ASSERT(!local_id.empty());
+ } while (local_id == remote_id);
+ return local_id;
+}
+
+RoapSession::RoapSession()
+ : seq_(0),
+ waiting_for_answer_(false),
+ received_seq_(0) {
+}
+
+std::string RoapSession::CreateOffer(
+ const SessionDescription* desc,
+ const std::vector<Candidate>& candidates) {
+ if (local_id_.empty()) {
+ local_id_ = CreateLocalId(remote_id_);
+ }
+
+ do {
+ local_tie_breaker_ = talk_base::CreateRandomNonZeroId();
+ } while (local_tie_breaker_ > kMaxTieBreaker);
+
+ RoapOffer offer(local_id_, remote_id_, session_token_, ++seq_,
+ local_tie_breaker_, desc, candidates);
+ waiting_for_answer_ = true;
+ return offer.Serialize();
+}
+
+std::string RoapSession::CreateAnswer(
+ const SessionDescription* desc,
+ const std::vector<Candidate>& candidates) {
+ ASSERT(!remote_id_.empty());
+ if (local_id_.empty()) {
+ local_id_ = CreateLocalId(remote_id_);
+ }
+
+ RoapAnswer answer(remote_id_, local_id_, session_token_, response_token_,
+ seq_, desc, candidates);
+ response_token_.clear();
+ return answer.Serialize();
+}
+
+std::string RoapSession::CreateOk() {
+ ASSERT(!remote_id_.empty());
+
+ if (local_id_.empty()) {
+ local_id_ = CreateLocalId(remote_id_);
+ }
+ RoapOk ok(remote_id_, local_id_, session_token_, response_token_, seq_);
+ response_token_.clear();
+ return ok.Serialize();
+}
+
+std::string RoapSession::CreateShutDown() {
+ if (local_id_.empty()) {
+ local_id_ = CreateLocalId(remote_id_);
+ }
+ RoapShutdown shutdown(local_id_, remote_id_, session_token_, ++seq_);
+ return shutdown.Serialize();
+}
+
+std::string RoapSession::CreateErrorMessage(RoapErrorCode error) {
+ if (local_id_.empty()) {
+ local_id_ = CreateLocalId(remote_id_);
+ }
+
+ RoapError message(received_offer_id_, local_id_, session_token_,
+ response_token_, received_seq_, error);
+ response_token_.clear();
+ return message.Serialize();
+}
+
+RoapSession::ParseResult RoapSession::Parse(
+ const std::string& msg) {
+ RoapMessageBase message;
+ if (!message.Parse(msg)) {
+ LOG(LS_ERROR) << "Parse failed. Invalid Roap message?";
+ return kInvalidMessage;
+ }
+
+ received_offer_id_ = message.offer_session_id();
+ received_answer_id_ = message.answer_session_id();
+ received_seq_ = message.seq();
+ session_token_ = message.session_token();
+ response_token_ = message.response_token();
+ ParseResult result = kInvalidMessage;
+
+ switch (message.type()) {
+ case RoapMessageBase::kOffer: {
+ RoapOffer offer(message);
+ if (!offer.Parse()) {
+ LOG(LS_ERROR) << "Parse failed. Invalid Offer message?";
+ return kInvalidMessage;
+ }
+ result = ValidateOffer(&offer);
+ break;
+ }
+ case RoapMessageBase::kAnswer: {
+ RoapAnswer answer(message);
+ if (!answer.Parse()) {
+ LOG(LS_ERROR) << "Parse failed. Invalid Answer message?";
+ result = kInvalidMessage;
+ } else {
+ result = ValidateAnswer(&answer);
+ }
+ break;
+ }
+ case RoapMessageBase::kOk: {
+ result = ValidateOk(message);
+ break;
+ }
+ case RoapMessageBase::kShutdown: {
+ // Always accept shutdown messages.
+ if (remote_id_.empty()) {
+ remote_id_ = message.offer_session_id();
+ }
+ seq_ = message.seq();
+ result = kShutDown;
+ break;
+ }
+ case RoapMessageBase::kError: {
+ RoapError error(message);
+ if (!error.Parse()) {
+ LOG(LS_ERROR) << "Parse failed. Invalid Error message?";
+ result = kInvalidMessage;
+ } else if (ValidateError(error) == kError) {
+ result = kError;
+ } // else ignore this error message.
+ break;
+ }
+ default: {
+ ASSERT(!"Unknown message type.");
+ LOG(LS_ERROR) << "Received unknown message.";
+ result = kInvalidMessage;
+ break;
+ }
+ }
+ return result;
+}
+
+RoapSession::ParseResult RoapSession::ValidateOffer(
+ RoapOffer* received_offer) {
+
+ /* Check if the incoming OFFER has a answererSessionId, if not it is
+ an initial offer. If the outstanding OFFER also is an initial
+ OFFER there is an Error. */
+ if (received_offer->answer_session_id().empty() &&
+ remote_id_.empty() && waiting_for_answer_) {
+ return kInvalidMessage;
+ }
+
+ if (remote_id_.empty()) {
+ remote_id_ = received_offer->offer_session_id();
+ }
+
+ // Check the message belong to this session.
+ bool result =
+ received_offer->offer_session_id() == remote_id_ &&
+ received_offer->answer_session_id() == local_id_;
+
+ if (!result || received_offer->seq() < seq_) {
+ return kInvalidMessage; // Old seq.
+ }
+
+ if (waiting_for_answer_) {
+ if (received_offer->seq() != seq_) {
+ return kInvalidMessage;
+ }
+ // Glare.
+ if (received_offer->tie_breaker() < local_tie_breaker_) {
+ return kConflict;
+ } else if (received_offer->tie_breaker() == local_tie_breaker_) {
+ return kDoubleConflict;
+ }
+ }
+ // seq ok or remote offer won the glare resolution.
+ seq_ = received_offer->seq();
+ remote_desc_.reset(received_offer->ReleaseSessionDescription());
+ remote_candidates_ = received_offer->candidates();
+ return kOffer;
+}
+
+RoapSession::ParseResult RoapSession::ValidateAnswer(
+ RoapAnswer* received_answer) {
+ if (remote_id_.empty()) {
+ remote_id_ = received_answer->answer_session_id();
+ }
+ bool result =
+ received_answer->offer_session_id() == local_id_ &&
+ received_answer->seq() == seq_ &&
+ received_answer->answer_session_id() == remote_id_;
+ if (!result) {
+ return kInvalidMessage;
+ }
+
+ remote_desc_.reset(received_answer->ReleaseSessionDescription());
+ remote_candidates_ = received_answer->candidates();
+ if (received_answer->more_coming()) {
+ return kAnswerMoreComing;
+ }
+ waiting_for_answer_ = false;
+ return kAnswer;
+}
+
+RoapSession::ParseResult RoapSession::ValidateOk(
+ const RoapMessageBase& message) {
+ if (remote_id_.empty()) {
+ remote_id_ = message.answer_session_id();
+ }
+ bool result =
+ message.offer_session_id() == local_id_ &&
+ message.seq() == seq_ &&
+ message.answer_session_id() == remote_id_;
+ if (!result) {
+ return kInvalidMessage;
+ }
+ return kOk;
+}
+
+RoapSession::ParseResult RoapSession::ValidateError(
+ const RoapError& message) {
+ bool result =
+ message.offer_session_id() == local_id_ && message.seq() == seq_;
+
+ if (!result) {
+ return kInvalidMessage;
+ }
+ waiting_for_answer_ = false;
+ remote_error_ = message.error();
+ return kError;
+}
+
+SessionDescription* RoapSession::ReleaseRemoteDescription() {
+ return remote_desc_.release();
+}
+
+const std::vector<Candidate>& RoapSession::RemoteCandidates() {
+ return remote_candidates_;
+}
+
+RoapErrorCode RoapSession::RemoteError() {
+ return remote_error_;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/roapsession.h b/talk/app/webrtc/roapsession.h
new file mode 100644
index 0000000..7a0c27f
--- /dev/null
+++ b/talk/app/webrtc/roapsession.h
@@ -0,0 +1,121 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains a class used for creating and parsing ROAP messages
+// as defined in http://tools.ietf.org/html/draft-jennings-rtcweb-signaling-01.
+// The RoapSession is responsible for keeping track of ROAP specific
+// attributes such as offerSessionId etc of a single session but not the logic
+// for when to create a specific message.
+
+#ifndef TALK_APP_WEBRTC_ROAPSESSION_H_
+#define TALK_APP_WEBRTC_ROAPSESSION_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/roaperrorcodes.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+using cricket::Candidate;
+using cricket::SessionDescription;
+
+class RoapAnswer;
+class RoapError;
+class RoapMessageBase;
+class RoapOffer;
+
+class RoapSession {
+ public:
+ // ParseResult is the result of parsing a message.
+ // It can be either an identified message type or a detected error.
+ enum ParseResult {
+ kOffer,
+ kAnswerMoreComing, // More coming flag set. The SDP contains candidates.
+ kAnswer,
+ kOk,
+ kShutDown,
+ kError,
+ // The messages below is errors that can occur during parsing.
+ kConflict, // Conflict detected during parsing of offer.
+ kDoubleConflict, // Double conflict detected during parsing of offer.
+ kInvalidMessage // The parsed message is invalid.
+ };
+
+ RoapSession();
+
+ // Creates a ROAP offer message based on the provided session description and
+ // candidates. This will update states in the ROAP sessions variables such as
+ // sequence number and create a local session id.
+ std::string CreateOffer(const SessionDescription* desc,
+ const std::vector<Candidate>& candidates);
+
+ // Creates a ROAP answer message based on the provided session description and
+ // candidates. An offer must have been parsed before this function can be
+ // called.
+ std::string CreateAnswer(const SessionDescription* desc,
+ const std::vector<Candidate>& candidates);
+ std::string CreateOk();
+ std::string CreateShutDown();
+ std::string CreateErrorMessage(RoapErrorCode error);
+ ParseResult Parse(const std::string& msg);
+ RoapErrorCode RemoteError();
+ // Get remote SessionDescription. The ownership is transferred to the caller.
+ SessionDescription* ReleaseRemoteDescription();
+ const std::vector<Candidate>& RemoteCandidates();
+
+ private:
+ ParseResult ValidateOffer(RoapOffer* received_offer);
+ ParseResult ValidateAnswer(RoapAnswer* received_answer);
+ ParseResult ValidateOk(const RoapMessageBase& message);
+ ParseResult ValidateError(const RoapError& message);
+
+ uint32 seq_; // Sequence number of current message exchange.
+ std::string local_id_; // offererSessionId / answerSessionId of local peer.
+ std::string remote_id_; // offererSessionId / answerSessionId of remote peer.
+ uint32 local_tie_breaker_; // tieBreaker of last sent offer.
+ bool waiting_for_answer_;
+
+ std::string received_offer_id_; // offererSessionId in last received message.
+ std::string received_answer_id_; // answerSessionId in last received message.
+ uint32 received_seq_; // Sequence number of last received message.
+ std::string session_token_;
+ std::string response_token_;
+
+ talk_base::scoped_ptr<SessionDescription> remote_desc_;
+ std::vector<Candidate> remote_candidates_;
+
+ RoapErrorCode remote_error_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_ROAPSESSION_H_
diff --git a/talk/app/webrtc/roapsession_unittest.cc b/talk/app/webrtc/roapsession_unittest.cc
new file mode 100644
index 0000000..33cb7ab
--- /dev/null
+++ b/talk/app/webrtc/roapsession_unittest.cc
@@ -0,0 +1,350 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/roapmessages.h"
+#include "talk/app/webrtc/roapsession.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/session/phone/mediasession.h"
+
+using cricket::AudioContentDescription;
+using cricket::Candidates;
+using cricket::ContentInfo;
+using cricket::SessionDescription;
+using cricket::VideoContentDescription;
+using webrtc::RoapMessageBase;
+using webrtc::RoapSession;
+using webrtc::RoapOffer;
+
+// MediaStream 1
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+static const char kVideoTrackLabel1[] = "local_video_1";
+static const uint32 kVideoTrack1Ssrc = 2;
+static const char kVideoTrackLabel2[] = "local_video_2";
+static const uint32 kVideoTrack2Ssrc = 3;
+
+// MediaStream 2
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStream2Cname[] = "stream_2_cname";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const uint32 kAudioTrack2Ssrc = 4;
+static const char kVideoTrackLabel3[] = "local_video_3";
+static const uint32 kVideoTrack3Ssrc = 5;
+
+class RoapSessionTest: public testing::Test {
+ public:
+ void SetUp() {
+ talk_base::scoped_ptr<AudioContentDescription> audio(
+ new AudioContentDescription());
+ audio->set_rtcp_mux(true);
+ cricket::StreamParams audio_stream1;
+ audio_stream1.name = kAudioTrackLabel1;
+ audio_stream1.cname = kStream1Cname;
+ audio_stream1.sync_label = kStreamLabel1;
+ audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc);
+ audio->AddStream(audio_stream1);
+ desc1_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ audio.release());
+
+ talk_base::scoped_ptr<VideoContentDescription> video(
+ new VideoContentDescription());
+
+ cricket::StreamParams video_stream1;
+ video_stream1.name = kVideoTrackLabel1;
+ video_stream1.cname = kStream1Cname;
+ video_stream1.sync_label = kStreamLabel1;
+ video_stream1.ssrcs.push_back(kVideoTrack1Ssrc);
+ video->AddStream(video_stream1);
+
+ cricket::StreamParams video_stream2;
+ video_stream2.name = kVideoTrackLabel2;
+ video_stream2.cname = kStream1Cname;
+ video_stream2.sync_label = kStreamLabel1;
+ video_stream2.ssrcs.push_back(kVideoTrack2Ssrc);
+ video->AddStream(video_stream2);
+ desc1_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+ video.release());
+
+ audio.reset(new AudioContentDescription());
+ audio->set_rtcp_mux(true);
+ cricket::StreamParams audio_stream2;
+ audio_stream2.name = kAudioTrackLabel2;
+ audio_stream2.cname = kStream2Cname;
+ audio_stream2.sync_label = kStreamLabel2;
+ audio_stream2.ssrcs.push_back(kAudioTrack2Ssrc);
+ audio->AddStream(audio_stream2);
+ desc2_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ audio.release());
+
+ video.reset(new VideoContentDescription());
+ cricket::StreamParams video_stream3;
+ video_stream3.name = kVideoTrackLabel3;
+ video_stream3.cname = kStream2Cname;
+ video_stream3.sync_label = kStreamLabel2;
+ video_stream3.ssrcs.push_back(kVideoTrack3Ssrc);
+ video->AddStream(video_stream3);
+ desc2_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+ video.release());
+
+ int port = 1234;
+ talk_base::SocketAddress address("127.0.0.1", port++);
+ cricket::Candidate candidate1("video_rtcp", "udp", address, 1,
+ "user_video_rtcp", "password_video_rtcp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate2("video_rtp", "udp", address, 1,
+ "user_video_rtp", "password_video_rtp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate3("rtp", "udp", address, 1,
+ "user_rtp", "password_rtp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate4("rtcp", "udp", address, 1,
+ "user_rtcp", "password_rtcp", "local", "eth0", 0);
+
+ candidates_.push_back(candidate1);
+ candidates_.push_back(candidate2);
+ candidates_.push_back(candidate3);
+ candidates_.push_back(candidate4);
+ }
+
+ bool CompareSessionDescription(const SessionDescription* desc1,
+ const SessionDescription* desc2) {
+ const ContentInfo* audio_1 = desc1->GetContentByName("audio");
+ const AudioContentDescription* audio_desc_1 =
+ static_cast<const AudioContentDescription*>(audio_1->description);
+ const ContentInfo* video_1 = desc1->GetContentByName("video");
+ const VideoContentDescription* video_desc_1 =
+ static_cast<const VideoContentDescription*>(video_1->description);
+
+ const ContentInfo* audio_2 = desc2->GetContentByName("audio");
+ const AudioContentDescription* audio_desc_2 =
+ static_cast<const AudioContentDescription*>(audio_2->description);
+ const ContentInfo* video_2 = desc2->GetContentByName("video");
+ const VideoContentDescription* video_desc_2 =
+ static_cast<const VideoContentDescription*>(video_2->description);
+
+ // Check that all streams are equal. We only check that the number of
+ // codecs are the same and leave it for other unit tests to test
+ // parsing / serialization of the session description.
+ return audio_desc_1->codecs().size() == audio_desc_2->codecs().size() &&
+ audio_desc_1->streams() == audio_desc_2->streams() &&
+ video_desc_1->codecs().size() == video_desc_2->codecs().size() &&
+ video_desc_1->streams() == video_desc_2->streams();
+ }
+
+ bool CompareCandidates(const Candidates& c1, const Candidates& c2) {
+ if (c1.size() != c2.size())
+ return false;
+
+ Candidates::const_iterator it1 = c1.begin();
+ for (; it1 != c1.end(); ++it1) {
+ // It is ok if the order in the vector have changed.
+ Candidates::const_iterator it2 = c2.begin();
+ for (; it2 != c2.end(); ++it2) {
+ if (it1->IsEquivalent(*it2)) {
+ break;
+ }
+ }
+ if (it2 == c2.end())
+ return false;
+ }
+ return true;
+ }
+
+ protected:
+ cricket::SessionDescription desc1_;
+ cricket::SessionDescription desc2_;
+ cricket::Candidates candidates_;
+};
+
+TEST_F(RoapSessionTest, OfferAnswer) {
+ RoapSession roap_session1;
+ RoapSession roap_session2;
+
+ std::string offer_message = roap_session1.CreateOffer(&desc1_, candidates_);
+
+ // Check that it is valid to send to another peer.
+ EXPECT_EQ(RoapSession::kOffer, roap_session2.Parse(offer_message));
+ talk_base::scoped_ptr<const cricket::SessionDescription> received_offer(
+ roap_session2.ReleaseRemoteDescription());
+
+ ASSERT_TRUE(received_offer.get() != NULL);
+ EXPECT_TRUE(CompareSessionDescription(&desc1_, received_offer.get()));
+ EXPECT_TRUE(CompareCandidates(candidates_, roap_session2.RemoteCandidates()));
+
+ std::string answer_message = roap_session2.CreateAnswer(&desc2_, candidates_);
+
+ EXPECT_EQ(RoapSession::kAnswer, roap_session1.Parse(answer_message));
+ talk_base::scoped_ptr<const cricket::SessionDescription> received_answer(
+ roap_session1.ReleaseRemoteDescription());
+
+ EXPECT_TRUE(CompareSessionDescription(&desc2_, received_answer.get()));
+ EXPECT_FALSE(CompareSessionDescription(received_offer.get(),
+ received_answer.get()));
+ EXPECT_TRUE(CompareCandidates(candidates_, roap_session1.RemoteCandidates()));
+}
+
+TEST_F(RoapSessionTest, InvalidInitialization) {
+ RoapSession roap_session1;
+ RoapSession roap_session2;
+
+ std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+ std::string offer_message2 = roap_session2.CreateOffer(&desc2_, candidates_);
+
+ // It is an error to receive an initial offer if you have sent an
+ // initial offer.
+ EXPECT_EQ(RoapSession::kInvalidMessage,
+ roap_session1.Parse(offer_message2));
+
+ EXPECT_EQ(RoapSession::kInvalidMessage,
+ roap_session2.Parse(offer_message1));
+}
+
+TEST_F(RoapSessionTest, Glare) {
+ RoapSession roap_session1;
+ RoapSession roap_session2;
+
+ // Setup. Need to exchange an offer and an answer in order to test for glare.
+ std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+
+ roap_session2.Parse(offer_message1);
+ talk_base::scoped_ptr<const SessionDescription> received_offer(
+ roap_session2.ReleaseRemoteDescription());
+ std::string answer_message2 = roap_session2.CreateAnswer(&desc2_,
+ candidates_);
+ roap_session1.Parse(answer_message2);
+
+ // Ok- we should now have all we need. Create a glare condition by
+ // updating the offer simultaneously.
+ offer_message1 = roap_session1.CreateOffer(&desc2_, candidates_);
+ std::string offer_message2 = roap_session2.CreateOffer(&desc1_, candidates_);
+
+ EXPECT_TRUE(
+ (RoapSession::kOffer == roap_session1.Parse(offer_message2) &&
+ RoapSession::kConflict == roap_session2.Parse(offer_message1)) ||
+ (RoapSession::kOffer == roap_session2.Parse(offer_message1) &&
+ RoapSession::kConflict == roap_session1.Parse(offer_message2)));
+}
+
+// Test Glare resolution by setting different TieBreakers.
+TEST_F(RoapSessionTest, TieBreaker) {
+ RoapSession roap_session1;
+ RoapSession roap_session2;
+
+ // Offer 1
+ std::string offer_message1 = roap_session1.CreateOffer(&desc1_, candidates_);
+
+ EXPECT_EQ(RoapSession::kOffer, roap_session2.Parse(offer_message1));
+ talk_base::scoped_ptr<const SessionDescription> received_offer(
+ roap_session2.ReleaseRemoteDescription());
+ std::string answer_message2 = roap_session2.CreateAnswer(&desc2_,
+ candidates_);
+
+ EXPECT_EQ(RoapSession::kAnswer, roap_session1.Parse(answer_message2));
+
+ // Ok- we should now have all we need. Create a double conflict condition.
+ offer_message1 = roap_session1.CreateOffer(&desc2_, candidates_);
+ RoapMessageBase message_base;
+ EXPECT_TRUE(message_base.Parse(offer_message1));
+ RoapOffer message_offer(message_base);
+ EXPECT_TRUE(message_offer.Parse());
+ RoapOffer double_conflict_offer(message_offer.answer_session_id(),
+ message_offer.offer_session_id(),
+ "",
+ message_offer.seq(),
+ message_offer.tie_breaker(),
+ &desc1_,
+ candidates_);
+ EXPECT_EQ(RoapSession::kDoubleConflict,
+ roap_session1.Parse(double_conflict_offer.Serialize()));
+
+ RoapOffer losing_offer(message_offer.answer_session_id(),
+ message_offer.offer_session_id(),
+ "",
+ message_offer.seq(),
+ 0,
+ &desc1_,
+ candidates_);
+ EXPECT_EQ(RoapSession::kConflict,
+ roap_session1.Parse(losing_offer.Serialize()));
+
+ RoapOffer winning_offer(message_offer.answer_session_id(),
+ message_offer.offer_session_id(),
+ "",
+ message_offer.seq(),
+ 0xFFFFFFFF,
+ &desc1_,
+ candidates_);
+ EXPECT_EQ(RoapSession::kOffer,
+ roap_session1.Parse(winning_offer.Serialize()));
+}
+
+TEST_F(RoapSessionTest, ShutDownOk) {
+ RoapSession roap_session1;
+ std::string shutdown = roap_session1.CreateShutDown();
+
+ RoapSession roap_session2;
+ EXPECT_EQ(RoapSession::kShutDown, roap_session2.Parse(shutdown));
+
+ std::string ok_message = roap_session2.CreateOk();
+ EXPECT_EQ(RoapSession::kOk, roap_session1.Parse(ok_message));
+}
+
+TEST_F(RoapSessionTest, ErrorMessageCreation) {
+ RoapSession roap_session1;
+ RoapSession roap_session2;
+
+ std::string message = roap_session1.CreateErrorMessage(webrtc::kNoMatch);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kNoMatch, roap_session2.RemoteError());
+
+ message = roap_session1.CreateErrorMessage(webrtc::kTimeout);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kTimeout, roap_session2.RemoteError());
+
+ message = roap_session1.CreateErrorMessage(webrtc::kRefused);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kRefused, roap_session2.RemoteError());
+
+ message = roap_session1.CreateErrorMessage(webrtc::kConflict);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kConflict, roap_session2.RemoteError());
+
+ message = roap_session1.CreateErrorMessage(webrtc::kDoubleConflict);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kDoubleConflict, roap_session2.RemoteError());
+
+ message = roap_session1.CreateErrorMessage(webrtc::kFailed);
+ EXPECT_EQ(RoapSession::kError, roap_session2.Parse(message));
+ EXPECT_EQ(webrtc::kFailed, roap_session2.RemoteError());
+}
diff --git a/talk/app/webrtc/sessiondescriptionprovider.h b/talk/app/webrtc/sessiondescriptionprovider.h
new file mode 100644
index 0000000..7142cdd
--- /dev/null
+++ b/talk/app/webrtc/sessiondescriptionprovider.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
+#define TALK_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
+
+#include "talk/session/phone/mediasession.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace webrtc {
+
+class SessionDescriptionProvider {
+ public:
+ // Creates a new SessionDescription based on |options|.
+ // It does not affect the currently set local or remote SessionDescription.
+ // Caller is owner of the created SessionDescription.
+ virtual cricket::SessionDescription* CreateOffer(
+ const cricket::MediaSessionOptions& options) = 0;
+
+ // Creates a new SessionDescription based on |offer| and |options|.
+ // It does not affect the currently set local or remote SessionDescription.
+ // Caller is owner of the created SessionDescription.
+ virtual cricket::SessionDescription* CreateAnswer(
+ const cricket::SessionDescription* offer,
+ const cricket::MediaSessionOptions& options) = 0;
+
+ // Transfers ownership of session description.
+ virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+ cricket::ContentAction type) = 0;
+ virtual void SetRemoteDescription(cricket::SessionDescription* desc,
+ cricket::ContentAction type) = 0;
+ // Sets all the currently known remote candidates.
+ virtual void SetRemoteCandidates(
+ const std::vector<cricket::Candidate>& remote_candidates) = 0;
+
+ // Current local session description.
+ virtual const cricket::SessionDescription* local_description() const = 0;
+ // Current remote session description.
+ virtual const cricket::SessionDescription* remote_description() const = 0;
+
+ protected:
+ virtual ~SessionDescriptionProvider() {}
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_SESSIONDESCRIPTIONPROVIDER_H_
diff --git a/talk/app/webrtc/streamcollectionimpl.h b/talk/app/webrtc/streamcollectionimpl.h
new file mode 100644
index 0000000..76df3fd
--- /dev/null
+++ b/talk/app/webrtc/streamcollectionimpl.h
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
+#define TALK_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/peerconnection.h"
+
+namespace webrtc {
+
+// Implementation of StreamCollection.
+class StreamCollection : public StreamCollectionInterface {
+ public:
+ static talk_base::scoped_refptr<StreamCollection> Create() {
+ talk_base::RefCountedObject<StreamCollection>* implementation =
+ new talk_base::RefCountedObject<StreamCollection>();
+ return implementation;
+ }
+
+ static talk_base::scoped_refptr<StreamCollection> Create(
+ StreamCollection* streams) {
+ talk_base::RefCountedObject<StreamCollection>* implementation =
+ new talk_base::RefCountedObject<StreamCollection>(streams);
+ return implementation;
+ }
+
+ virtual size_t count() {
+ return media_streams_.size();
+ }
+
+ virtual MediaStreamInterface* at(size_t index) {
+ return media_streams_.at(index);
+ }
+
+ virtual MediaStreamInterface* find(const std::string& label) {
+ for (StreamVector::iterator it = media_streams_.begin();
+ it != media_streams_.end(); ++it) {
+ if ((*it)->label().compare(label) == 0) {
+ return (*it);
+ }
+ }
+ return NULL;
+ }
+
+ void AddStream(MediaStreamInterface* stream) {
+ for (StreamVector::iterator it = media_streams_.begin();
+ it != media_streams_.end(); ++it) {
+ if ((*it)->label().compare(stream->label()) == 0)
+ return;
+ }
+ media_streams_.push_back(stream);
+ }
+
+ void RemoveStream(MediaStreamInterface* remove_stream) {
+ for (StreamVector::iterator it = media_streams_.begin();
+ it != media_streams_.end(); ++it) {
+ if ((*it)->label().compare(remove_stream->label()) == 0) {
+ media_streams_.erase(it);
+ break;
+ }
+ }
+ }
+
+ protected:
+ StreamCollection() {}
+ explicit StreamCollection(StreamCollection* original)
+ : media_streams_(original->media_streams_) {
+ }
+ typedef std::vector<talk_base::scoped_refptr<MediaStreamInterface> >
+ StreamVector;
+ StreamVector media_streams_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_STREAMCOLLECTIONIMPL_H_
diff --git a/talk/app/webrtc/videorendererimpl.cc b/talk/app/webrtc/videorendererimpl.cc
new file mode 100644
index 0000000..6355e4b
--- /dev/null
+++ b/talk/app/webrtc/videorendererimpl.cc
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/mediastreamimpl.h"
+
+#include "talk/session/phone/videorenderer.h"
+
+namespace webrtc {
+
+// VideoRendererImpl take ownership of cricket::VideoRenderer.
+class VideoRendererImpl : public VideoRendererWrapperInterface {
+ public:
+ explicit VideoRendererImpl(cricket::VideoRenderer* renderer)
+ : renderer_(renderer) {
+ }
+ virtual cricket::VideoRenderer* renderer() {
+ return renderer_;
+ }
+ protected:
+ ~VideoRendererImpl() {
+ delete renderer_;
+ }
+ private:
+ cricket::VideoRenderer* renderer_;
+};
+
+talk_base::scoped_refptr<VideoRendererWrapperInterface> CreateVideoRenderer(
+ cricket::VideoRenderer* renderer) {
+ talk_base::RefCountedObject<VideoRendererImpl>* r =
+ new talk_base::RefCountedObject<VideoRendererImpl>(renderer);
+ return r;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/videotrackimpl.cc b/talk/app/webrtc/videotrackimpl.cc
new file mode 100644
index 0000000..0fb218a
--- /dev/null
+++ b/talk/app/webrtc/videotrackimpl.cc
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "talk/app/webrtc/videotrackimpl.h"
+
+#include <string>
+
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+namespace webrtc {
+
+static const char kVideoTrackKind[] = "video";
+
+VideoTrack::VideoTrack(const std::string& label)
+ : MediaStreamTrack<LocalVideoTrackInterface>(label),
+ video_device_(NULL) {
+}
+
+VideoTrack::VideoTrack(const std::string& label,
+ cricket::VideoCapturer* video_device)
+ : MediaStreamTrack<LocalVideoTrackInterface>(label),
+ video_device_(NULL) {
+ video_device_.reset(video_device);
+}
+
+void VideoTrack::SetRenderer(VideoRendererWrapperInterface* renderer) {
+ video_renderer_ = renderer;
+ Notifier<LocalVideoTrackInterface>::FireOnChanged();
+}
+
+VideoRendererWrapperInterface* VideoTrack::GetRenderer() {
+ return video_renderer_.get();
+}
+
+ // Get the VideoCapture device associated with this track.
+cricket::VideoCapturer* VideoTrack::GetVideoCapture() {
+ return video_device_.get();
+}
+
+std::string VideoTrack::kind() const {
+ return kVideoTrackKind;
+}
+
+talk_base::scoped_refptr<VideoTrack> VideoTrack::CreateRemote(
+ const std::string& label) {
+ talk_base::RefCountedObject<VideoTrack>* track =
+ new talk_base::RefCountedObject<VideoTrack>(label);
+ return track;
+}
+
+talk_base::scoped_refptr<VideoTrack> VideoTrack::CreateLocal(
+ const std::string& label,
+ cricket::VideoCapturer* video_device) {
+ talk_base::RefCountedObject<VideoTrack>* track =
+ new talk_base::RefCountedObject<VideoTrack>(label, video_device);
+ return track;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/videotrackimpl.h b/talk/app/webrtc/videotrackimpl.h
new file mode 100644
index 0000000..de99e6a
--- /dev/null
+++ b/talk/app/webrtc/videotrackimpl.h
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_VIDEOTRACKIMPL_H_
+#define TALK_APP_WEBRTC_VIDEOTRACKIMPL_H_
+
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/mediatrackimpl.h"
+#include "talk/app/webrtc/notifierimpl.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace cricket {
+
+class VideoCapturer;
+
+} // namespace cricket
+
+namespace webrtc {
+
+class VideoTrack : public MediaStreamTrack<LocalVideoTrackInterface> {
+ public:
+ // Create a video track used for remote video tracks.
+ static talk_base::scoped_refptr<VideoTrack> CreateRemote(
+ const std::string& label);
+ // Create a video track used for local video tracks.
+ static talk_base::scoped_refptr<VideoTrack> CreateLocal(
+ const std::string& label,
+ cricket::VideoCapturer* video_device);
+
+ virtual cricket::VideoCapturer* GetVideoCapture();
+ virtual void SetRenderer(VideoRendererWrapperInterface* renderer);
+ VideoRendererWrapperInterface* GetRenderer();
+
+ virtual std::string kind() const;
+
+ protected:
+ explicit VideoTrack(const std::string& label);
+ VideoTrack(const std::string& label, cricket::VideoCapturer* video_device);
+
+ private:
+ talk_base::scoped_ptr<cricket::VideoCapturer> video_device_;
+ talk_base::scoped_refptr<VideoRendererWrapperInterface> video_renderer_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_VIDEOTRACKIMPL_H_
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
index 271d413..88ea3f4 100644
--- a/talk/app/webrtc/webrtc.scons
+++ b/talk/app/webrtc/webrtc.scons
@@ -3,58 +3,68 @@
Import('env')
-# local sources
-talk.Library(
- env,
- name = 'webrtc',
- srcs = [
- 'peerconnectionimpl.cc',
- 'peerconnectionproxy.cc',
- 'peerconnectionfactory.cc',
- 'webrtcjson.cc',
- 'webrtcsession.cc',
- ],
-)
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+ # local sources
+ talk.Library(
+ env,
+ name = 'peerconnection',
+ srcs = [
+ 'audiotrackimpl.cc',
+ 'mediastreamhandler.cc',
+ 'mediastreamimpl.cc',
+ 'mediastreamproxy.cc',
+ 'mediastreamtrackproxy.cc',
+ 'peerconnectionfactoryimpl.cc',
+ 'peerconnectionimpl.cc',
+ 'peerconnectionsignaling.cc',
+ 'portallocatorfactory.cc',
+ 'roapmessages.cc',
+ 'roapsession.cc',
+ 'videorendererimpl.cc',
+ 'videotrackimpl.cc',
+ 'webrtcsdp.cc',
+ 'webrtcsession.cc',
+ ],
+ )
-talk.Unittest(
- env,
- name = 'webrtc',
- srcs = [
- 'peerconnection_unittest.cc',
- 'unittest_utilities.cc',
- 'webrtcsession_unittest.cc',
- ],
- libs = [
- 'base',
- 'expat',
- 'jpeg',
- 'json',
- 'webrtc',
- 'p2p',
- 'phone',
- 'srtp',
- 'xmpp',
- 'xmllite',
- 'yuvscaler'
- ],
- include_talk_media_libs = True,
- mac_libs = [
- 'crypto',
- 'ssl',
- ],
- mac_FRAMEWORKS = [
- 'Foundation',
- 'IOKit',
- 'QTKit',
- ],
- win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
- lin_libs = [
- 'rt',
- 'dl',
- 'sound',
- 'X11',
- 'Xext',
- 'Xfixes',
- 'Xrandr'
- ],
-)
+ talk.Unittest(
+ env,
+ name = 'peerconnection',
+ srcs = [
+ 'test/fakevideocapturemodule.cc',
+ 'test/fileframesource.cc',
+ 'test/i420framesource.cc',
+ 'test/staticframesource.cc',
+ 'mediastream_unittest.cc',
+ 'mediastreamhandler_unittest.cc',
+ 'peerconnectionimpl_unittest.cc',
+ 'peerconnection_unittest.cc',
+ 'peerconnectionfactory_unittest.cc',
+ 'peerconnectionsignaling_unittest.cc',
+ 'roapmessages_unittest.cc',
+ 'roapsession_unittest.cc',
+ 'webrtcsdp_unittest.cc',
+ 'webrtcsession_unittest.cc',
+ ],
+ libs = [
+ 'base',
+ 'expat',
+ 'jpeg',
+ 'json',
+ 'p2p',
+ 'phone',
+ 'srtp',
+ 'xmllite',
+ 'xmpp',
+ 'yuvscaler',
+ 'peerconnection',
+ ],
+ win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
+ lin_libs = [
+ 'sound',
+ ],
+ mac_libs = [
+ 'crypto',
+ 'ssl',
+ ],
+ )
diff --git a/talk/app/webrtc/webrtcjson.cc b/talk/app/webrtc/webrtcjson.cc
index 1662e23..2aaac97 100644
--- a/talk/app/webrtc/webrtcjson.cc
+++ b/talk/app/webrtc/webrtcjson.cc
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -29,94 +29,161 @@
#include <stdio.h>
#include <string>
+#include <vector>
#include "talk/base/json.h"
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
#include "talk/session/phone/codec.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediasession.h"
#include "talk/session/phone/mediasessionclient.h"
namespace webrtc {
static const int kIceComponent = 1;
static const int kIceFoundation = 1;
-bool GetJsonSignalingMessage(
+static std::vector<Json::Value> ReadValues(const Json::Value& value,
+ const std::string& key);
+
+static void BuildContent(
const cricket::SessionDescription* sdp,
- const std::vector<cricket::Candidate>& candidates,
- std::string* signaling_message) {
- const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
- const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
-
- std::vector<Json::Value> media;
- if (audio_content) {
- Json::Value value;
- BuildMediaMessage(*audio_content, candidates, false, &value);
- media.push_back(value);
- }
-
- if (video_content) {
- Json::Value value;
- BuildMediaMessage(*video_content, candidates, true, &value);
- media.push_back(value);
- }
-
- Json::Value signal;
- Append(&signal, "media", media);
-
- // Now serialize.
- *signaling_message = Serialize(signal);
-
- return true;
-}
-
-bool BuildMediaMessage(
const cricket::ContentInfo& content_info,
const std::vector<cricket::Candidate>& candidates,
bool video,
- Json::Value* params) {
+ Json::Value* content);
+
+static void BuildCandidate(const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ std::vector<Json::Value>* jcandidates);
+
+static void BuildRtpMapParams(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* rtpmap);
+
+static void BuildCrypto(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* cryptos);
+
+static void BuildTrack(const cricket::SessionDescription* sdp,
+ bool video,
+ std::vector<Json::Value>* track);
+
+bool ParseContent(const Json::Value& jmessage,
+ cricket::SessionDescription* sdp,
+ std::vector<cricket::Candidate>* candidates);
+
+static bool ParseAudioCodec(const Json::Value& value,
+ cricket::AudioContentDescription* content);
+static bool ParseVideoCodec(const Json::Value& value,
+ cricket::VideoContentDescription* content);
+static bool ParseCrypto(const Json::Value& content,
+ cricket::MediaContentDescription* desc);
+
+static bool ParseCandidates(const Json::Value& content,
+ std::vector<cricket::Candidate>* candidates);
+
+static bool ParseTrack(const Json::Value& content,
+ cricket::MediaContentDescription* content_desc);
+
+static void Append(Json::Value* object, const std::string& key, bool value);
+static void Append(Json::Value* object, const std::string& key,
+ const char* value);
+static void Append(Json::Value* object, const std::string& key, int value);
+static void Append(Json::Value* object, const std::string& key,
+ const std::string& value);
+static void Append(Json::Value* object, const std::string& key, uint32 value);
+static void Append(Json::Value* object, const std::string& key,
+ const Json::Value& value);
+static void Append(Json::Value* object,
+ const std::string& key,
+ const std::vector<Json::Value>& values);
+
+void JsonSerializeSessionDescription(
+ const cricket::SessionDescription* sdp,
+ const std::vector<cricket::Candidate>& candidates,
+ Json::Value* message) {
+
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
+
+ std::vector<Json::Value> together;
+ together.push_back("audio");
+ together.push_back("video");
+
+ std::vector<Json::Value> contents;
+
+ if (audio_content) {
+ Json::Value content;
+ BuildContent(sdp, *audio_content, candidates, false, &content);
+ contents.push_back(content);
+ }
+
+ if (video_content) {
+ Json::Value content;
+ BuildContent(sdp, *video_content, candidates, true, &content);
+ contents.push_back(content);
+ }
+
+ Append(message, "content", contents);
+ Append(message, "TOGETHER", together);
+}
+
+void BuildContent(
+ const cricket::SessionDescription* sdp,
+ const cricket::ContentInfo& content_info,
+ const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ Json::Value* content) {
+ std::string label("media");
+ // TODO: Use enum instead of bool video to prepare for other
+ // media types such as the data media stream.
if (video) {
- Append(params, "label", 2); // always video 2
+ Append(content, label, "video");
} else {
- Append(params, "label", 1); // always audio 1
+ Append(content, label, "audio");
}
const cricket::MediaContentDescription* media_info =
- static_cast<const cricket::MediaContentDescription*> (
- content_info.description);
+ static_cast<const cricket::MediaContentDescription*> (
+ content_info.description);
if (media_info->rtcp_mux()) {
- Append(params, "rtcp_mux", true);
+ Append(content, "rtcp_mux", true);
}
+ // rtpmap
std::vector<Json::Value> rtpmap;
- if (!BuildRtpMapParams(content_info, video, &rtpmap)) {
- return false;
- }
+ BuildRtpMapParams(content_info, video, &rtpmap);
+ Append(content, "rtpmap", rtpmap);
- Append(params, "rtpmap", rtpmap);
+ // crypto
+ std::vector<Json::Value> crypto;
+ BuildCrypto(content_info, video, &crypto);
+ Append(content, "crypto", crypto);
- Json::Value attributes;
+ // candidate
std::vector<Json::Value> jcandidates;
+ BuildCandidate(candidates, video, &jcandidates);
+ Append(content, "candidate", jcandidates);
- if (!BuildAttributes(candidates, video, &jcandidates)) {
- return false;
- }
- Append(&attributes, "candidate", jcandidates);
- Append(params, "attributes", attributes);
- return true;
+ // track
+ std::vector<Json::Value> track;
+ BuildTrack(sdp, video, &track);
+ Append(content, "track", track);
}
-bool BuildRtpMapParams(const cricket::ContentInfo& content_info,
+void BuildRtpMapParams(const cricket::ContentInfo& content_info,
bool video,
std::vector<Json::Value>* rtpmap) {
if (!video) {
- const cricket::AudioContentDescription* audio_offer =
+ const cricket::AudioContentDescription* audio =
static_cast<const cricket::AudioContentDescription*>(
content_info.description);
std::vector<cricket::AudioCodec>::const_iterator iter =
- audio_offer->codecs().begin();
+ audio->codecs().begin();
std::vector<cricket::AudioCodec>::const_iterator iter_end =
- audio_offer->codecs().end();
+ audio->codecs().end();
for (; iter != iter_end; ++iter) {
Json::Value codec;
std::string codec_str(std::string("audio/").append(iter->name));
@@ -128,14 +195,14 @@
rtpmap->push_back(codec_id);
}
} else {
- const cricket::VideoContentDescription* video_offer =
+ const cricket::VideoContentDescription* video =
static_cast<const cricket::VideoContentDescription*>(
content_info.description);
std::vector<cricket::VideoCodec>::const_iterator iter =
- video_offer->codecs().begin();
+ video->codecs().begin();
std::vector<cricket::VideoCodec>::const_iterator iter_end =
- video_offer->codecs().end();
+ video->codecs().end();
for (; iter != iter_end; ++iter) {
Json::Value codec;
std::string codec_str(std::string("video/").append(iter->name));
@@ -145,12 +212,29 @@
rtpmap->push_back(codec_id);
}
}
- return true;
}
-bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
- bool video,
- std::vector<Json::Value>* jcandidates) {
+void BuildCrypto(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* cryptos) {
+ const cricket::MediaContentDescription* content_desc =
+ static_cast<const cricket::MediaContentDescription*>(
+ content_info.description);
+ std::vector<cricket::CryptoParams>::const_iterator iter =
+ content_desc->cryptos().begin();
+ std::vector<cricket::CryptoParams>::const_iterator iter_end =
+ content_desc->cryptos().end();
+ for (; iter != iter_end; ++iter) {
+ Json::Value crypto;
+ Append(&crypto, "cipher_suite", iter->cipher_suite);
+ Append(&crypto, "key_params", iter->key_params);
+ cryptos->push_back(crypto);
+ }
+}
+
+void BuildCandidate(const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ std::vector<Json::Value>* jcandidates) {
std::vector<cricket::Candidate>::const_iterator iter =
candidates.begin();
std::vector<cricket::Candidate>::const_iterator iter_end =
@@ -160,95 +244,117 @@
(!iter->name().compare("video_rtp")))) ||
(!video && (!iter->name().compare("rtp") ||
(!iter->name().compare("rtcp"))))) {
- Json::Value candidate;
- Append(&candidate, "component", kIceComponent);
- Append(&candidate, "foundation", kIceFoundation);
- Append(&candidate, "generation", iter->generation());
- Append(&candidate, "proto", iter->protocol());
- Append(&candidate, "priority", iter->preference_str());
- Append(&candidate, "ip", iter->address().IPAsString());
- Append(&candidate, "port", iter->address().PortAsString());
- Append(&candidate, "type", iter->type());
- Append(&candidate, "name", iter->name());
- Append(&candidate, "network_name", iter->network_name());
- Append(&candidate, "username", iter->username());
- Append(&candidate, "password", iter->password());
- jcandidates->push_back(candidate);
+ Json::Value jcandidate;
+ Append(&jcandidate, "component", kIceComponent);
+ Append(&jcandidate, "foundation", kIceFoundation);
+ Append(&jcandidate, "generation", iter->generation());
+ Append(&jcandidate, "proto", iter->protocol());
+ Append(&jcandidate, "priority", iter->preference_str());
+ Append(&jcandidate, "ip", iter->address().IPAsString());
+ Append(&jcandidate, "port", iter->address().PortAsString());
+ Append(&jcandidate, "type", iter->type());
+ Append(&jcandidate, "name", iter->name());
+ Append(&jcandidate, "network_name", iter->network_name());
+ Append(&jcandidate, "username", iter->username());
+ Append(&jcandidate, "password", iter->password());
+ jcandidates->push_back(jcandidate);
}
}
- return true;
}
-std::string Serialize(const Json::Value& value) {
- Json::StyledWriter writer;
- return writer.write(value);
-}
+void BuildTrack(const cricket::SessionDescription* sdp,
+ bool video,
+ std::vector<Json::Value>* tracks) {
+ const cricket::ContentInfo* content;
+ if (video)
+ content = GetFirstVideoContent(sdp);
+ else
+ content = GetFirstAudioContent(sdp);
-bool Deserialize(const std::string& message, Json::Value* value) {
- Json::Reader reader;
- return reader.parse(message, *value);
-}
+ if (!content)
+ return;
-bool ParseJsonSignalingMessage(const std::string& signaling_message,
- cricket::SessionDescription** sdp,
- std::vector<cricket::Candidate>* candidates) {
- ASSERT(!(*sdp)); // expect this to be NULL
- // first deserialize message
- Json::Value value;
- if (!Deserialize(signaling_message, &value)) {
- return false;
+ const cricket::MediaContentDescription* desc =
+ static_cast<const cricket::MediaContentDescription*>(
+ content->description);
+ for (cricket::StreamParamsVec::const_iterator it = desc->streams().begin();
+ it != desc->streams().end();
+ ++it) {
+ // TODO: Support ssrcsgroups.
+ Json::Value track;
+ Append(&track, "ssrc", it->ssrcs[0]);
+ Append(&track, "cname", it->cname);
+ Append(&track, "stream_label", it->sync_label);
+ Append(&track, "label", it->name);
+ tracks->push_back(track);
}
+}
- // get media objects
- std::vector<Json::Value> mlines = ReadValues(value, "media");
- if (mlines.empty()) {
- // no m-lines found
+bool JsonDeserializeSessionDescription(
+ const Json::Value& message,
+ cricket::SessionDescription* sdp,
+ std::vector<cricket::Candidate>* candidates) {
+
+ ASSERT(sdp != NULL);
+ ASSERT(candidates != NULL);
+
+ if (sdp == NULL || candidates == NULL)
return false;
- }
- *sdp = new cricket::SessionDescription();
-
- // get codec information
- for (size_t i = 0; i < mlines.size(); ++i) {
- if (mlines[i]["label"].asInt() == 1) {
+ // Get content
+ std::vector<Json::Value> contents = ReadValues(message, "content");
+ if (contents.size() == 0)
+ return false;
+ for (size_t i = 0; i < contents.size(); ++i) {
+ Json::Value content = contents[i];
+ // candidates
+ if (!ParseCandidates(content, candidates))
+ return false;
+ // rtcp_mux
+ bool rtcp_mux;
+ if (!GetBoolFromJsonObject(content, "rtcp_mux", &rtcp_mux))
+ rtcp_mux = false;
+ // rtpmap
+ if (content["media"].asString().compare("audio") == 0) {
cricket::AudioContentDescription* audio_content =
new cricket::AudioContentDescription();
- ParseAudioCodec(mlines[i], audio_content);
- audio_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+ if (!ParseAudioCodec(content, audio_content))
+ return false;
+ audio_content->set_rtcp_mux(rtcp_mux);
audio_content->SortCodecs();
- (*sdp)->AddContent(cricket::CN_AUDIO,
- cricket::NS_JINGLE_RTP, audio_content);
- ParseIceCandidates(mlines[i], candidates);
- } else {
+ // crypto
+ if (!ParseCrypto(content, audio_content))
+ return false;
+ // tracks
+ if (!ParseTrack(content, audio_content))
+ return false;
+ (sdp)->AddContent(cricket::CN_AUDIO,
+ cricket::NS_JINGLE_RTP, audio_content);
+ } else if (content["media"].asString().compare("video") == 0) {
cricket::VideoContentDescription* video_content =
new cricket::VideoContentDescription();
- ParseVideoCodec(mlines[i], video_content);
-
- video_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+ if (!ParseVideoCodec(content, video_content))
+ return false;
+ video_content->set_rtcp_mux(rtcp_mux);
video_content->SortCodecs();
- (*sdp)->AddContent(cricket::CN_VIDEO,
- cricket::NS_JINGLE_RTP, video_content);
- ParseIceCandidates(mlines[i], candidates);
+ // crypto
+ if (!ParseCrypto(content, video_content))
+ return false;
+ if (!ParseTrack(content, video_content))
+ return false;
+ (sdp)->AddContent(cricket::CN_VIDEO,
+ cricket::NS_JINGLE_RTP, video_content);
}
}
return true;
}
-bool ParseRtcpMux(const Json::Value& value) {
- Json::Value rtcp_mux(ReadValue(value, "rtcp_mux"));
- if (!rtcp_mux.empty()) {
- if (rtcp_mux.asBool()) {
- return true;
- }
- }
- return false;
-}
-
bool ParseAudioCodec(const Json::Value& value,
cricket::AudioContentDescription* content) {
std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+ // When there's no codecs in common, rtpmap can be empty.
if (rtpmap.empty())
- return false;
+ return true;
std::vector<Json::Value>::const_iterator iter =
rtpmap.begin();
@@ -259,11 +365,13 @@
std::string pltype(iter->begin().memberName());
talk_base::FromString(pltype, &codec.id);
Json::Value codec_info((*iter)[pltype]);
- std::string codec_name(ReadString(codec_info, "codec"));
+ std::string codec_name;
+ if (!GetStringFromJsonObject(codec_info, "codec", &codec_name))
+ continue;
std::vector<std::string> tokens;
talk_base::split(codec_name, '/', &tokens);
codec.name = tokens[1];
- codec.clockrate = ReadUInt(codec_info, "clockrate");
+ GetIntFromJsonObject(codec_info, "clockrate", &codec.clockrate);
content->AddCodec(codec);
}
@@ -273,8 +381,9 @@
bool ParseVideoCodec(const Json::Value& value,
cricket::VideoContentDescription* content) {
std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+ // When there's no codecs in common, rtpmap can be empty.
if (rtpmap.empty())
- return false;
+ return true;
std::vector<Json::Value>::const_iterator iter =
rtpmap.begin();
@@ -293,14 +402,9 @@
return true;
}
-bool ParseIceCandidates(const Json::Value& value,
- std::vector<cricket::Candidate>* candidates) {
- Json::Value attributes(ReadValue(value, "attributes"));
- std::string ice_pwd(ReadString(attributes, "ice-pwd"));
- std::string ice_ufrag(ReadString(attributes, "ice-ufrag"));
-
- std::vector<Json::Value> jcandidates(ReadValues(attributes, "candidate"));
-
+bool ParseCandidates(const Json::Value& content,
+ std::vector<cricket::Candidate>* candidates) {
+ std::vector<Json::Value> jcandidates(ReadValues(content, "candidate"));
std::vector<Json::Value>::const_iterator iter =
jcandidates.begin();
std::vector<Json::Value>::const_iterator iter_end =
@@ -361,6 +465,68 @@
return true;
}
+bool ParseCrypto(const Json::Value& content,
+ cricket::MediaContentDescription* desc) {
+ std::vector<Json::Value> jcryptos(ReadValues(content, "crypto"));
+ std::vector<Json::Value>::const_iterator iter =
+ jcryptos.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ jcryptos.end();
+ for (; iter != iter_end; ++iter) {
+ cricket::CryptoParams crypto;
+
+ std::string cipher_suite;
+ if (!GetStringFromJsonObject(*iter, "cipher_suite", &cipher_suite))
+ return false;
+ crypto.cipher_suite = cipher_suite;
+
+ std::string key_params;
+ if (!GetStringFromJsonObject(*iter, "key_params", &key_params))
+ return false;
+ crypto.key_params= key_params;
+
+ desc->AddCrypto(crypto);
+ }
+ return true;
+}
+
+bool ParseTrack(const Json::Value& content,
+ cricket::MediaContentDescription* content_desc) {
+ ASSERT(content_desc != NULL);
+ if (!content_desc)
+ return false;
+
+ std::vector<Json::Value> tracks(ReadValues(content, "track"));
+ std::vector<Json::Value>::const_iterator iter =
+ tracks.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ tracks.end();
+ for (; iter != iter_end; ++iter) {
+ uint32 ssrc;
+ std::string label;
+ std::string cname;
+ std::string stream_label;
+ if (!GetUIntFromJsonObject(*iter, "ssrc", &ssrc))
+ return false;
+ // label is optional, it will be empty string if doesn't exist
+ GetStringFromJsonObject(*iter, "label", &label);
+ if (!GetStringFromJsonObject(*iter, "cname", &cname))
+ return false;
+ // stream_label is optional, it will be the same as cname if it
+ // doesn't exist.
+ GetStringFromJsonObject(*iter, "stream_label", &stream_label);
+ if (stream_label.empty())
+ stream_label = cname;
+ cricket::StreamParams stream;
+ stream.name = label;
+ stream.cname = cname;
+ stream.sync_label = stream_label;
+ stream.ssrcs.push_back(ssrc);
+ content_desc->AddStream(stream);
+ }
+ return true;
+}
+
std::vector<Json::Value> ReadValues(
const Json::Value& value, const std::string& key) {
std::vector<Json::Value> objects;
@@ -370,27 +536,11 @@
return objects;
}
-Json::Value ReadValue(const Json::Value& value, const std::string& key) {
- return value[key];
-}
-
-std::string ReadString(const Json::Value& value, const std::string& key) {
- return value[key].asString();
-}
-
-uint32 ReadUInt(const Json::Value& value, const std::string& key) {
- return value[key].asUInt();
-}
-
-double ReadDouble(const Json::Value& value, const std::string& key) {
- return value[key].asDouble();
-}
-
void Append(Json::Value* object, const std::string& key, bool value) {
(*object)[key] = Json::Value(value);
}
-void Append(Json::Value* object, const std::string& key, char * value) {
+void Append(Json::Value* object, const std::string& key, const char* value) {
(*object)[key] = Json::Value(value);
}
diff --git a/talk/app/webrtc/webrtcjson.h b/talk/app/webrtc/webrtcjson.h
index 906326d..069f4b6 100644
--- a/talk/app/webrtc/webrtcjson.h
+++ b/talk/app/webrtc/webrtcjson.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -29,80 +29,33 @@
#define TALK_APP_WEBRTC_WEBRTCJSON_H_
#include <string>
+#include <vector>
#ifdef WEBRTC_RELATIVE_PATH
#include "json/json.h"
#else
#include "third_party/jsoncpp/json.h"
#endif
-#include "talk/p2p/base/candidate.h"
-#include "talk/session/phone/codec.h"
-namespace Json {
-class Value;
-}
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/sessiondescription.h"
namespace cricket {
-class AudioContentDescription;
-class VideoContentDescription;
-struct ContentInfo;
class SessionDescription;
}
namespace webrtc {
-std::vector<Json::Value> ReadValues(const Json::Value& value,
- const std::string& key);
-
-bool BuildMediaMessage(
- const cricket::ContentInfo& content_info,
- const std::vector<cricket::Candidate>& candidates,
- bool video,
- Json::Value* value);
-
-bool GetJsonSignalingMessage(
+void JsonSerializeSessionDescription(
const cricket::SessionDescription* sdp,
const std::vector<cricket::Candidate>& candidates,
- std::string* signaling_message);
+ Json::Value* media);
-bool BuildRtpMapParams(
- const cricket::ContentInfo& audio_offer,
- bool video,
- std::vector<Json::Value>* rtpmap);
+bool JsonDeserializeSessionDescription(
+ const Json::Value& message,
+ cricket::SessionDescription* sdp,
+ std::vector<cricket::Candidate>* candidates);
-bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
- bool video,
- std::vector<Json::Value>* jcandidates);
-
-std::string Serialize(const Json::Value& value);
-bool Deserialize(const std::string& message, Json::Value& value);
-
-bool ParseJsonSignalingMessage(const std::string& signaling_message,
- cricket::SessionDescription** sdp,
- std::vector<cricket::Candidate>* candidates);
-bool ParseRtcpMux(const Json::Value& value);
-bool ParseAudioCodec(const Json::Value& value,
- cricket::AudioContentDescription* content);
-bool ParseVideoCodec(const Json::Value& value,
- cricket::VideoContentDescription* content);
-bool ParseIceCandidates(const Json::Value& value,
- std::vector<cricket::Candidate>* candidates);
-Json::Value ReadValue(const Json::Value& value, const std::string& key);
-std::string ReadString(const Json::Value& value, const std::string& key);
-double ReadDouble(const Json::Value& value, const std::string& key);
-uint32 ReadUInt(const Json::Value& value, const std::string& key);
-
-void Append(Json::Value* object, const std::string& key, bool value);
-void Append(Json::Value* object, const std::string& key, char * value);
-void Append(Json::Value* object, const std::string& key, int value);
-void Append(Json::Value* object, const std::string& key,
- const std::string& value);
-void Append(Json::Value* object, const std::string& key, uint32 value);
-void Append(Json::Value* object, const std::string& key,
- const Json::Value& value);
-void Append(Json::Value* object,
- const std::string& key,
- const std::vector<Json::Value>& values);
-}
+} // namespace webrtc
#endif // TALK_APP_WEBRTC_WEBRTCJSON_H_
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
new file mode 100644
index 0000000..bdf4d00
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -0,0 +1,776 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/webrtcsdp.h"
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+using cricket::AudioContentDescription;
+using cricket::Candidate;
+using cricket::ContentDescription;
+using cricket::CryptoParams;
+using cricket::MediaContentDescription;
+using cricket::MediaType;
+using cricket::StreamParams;
+using cricket::VideoContentDescription;
+using talk_base::SocketAddress;
+
+namespace webrtc {
+
+// Line prefix
+static const int kLinePrefixLength = 2;
+static const char kLinePrefixVersion[] = "v=";
+static const char kLinePrefixOrigin[] = "o=";
+static const char kLinePrefixSessionName[] = "s=";
+static const char kLinePrefixSessionInfo[] = "i=";
+static const char kLinePrefixSessionUri[] = "u=";
+static const char kLinePrefixSessionEmail[] = "e=";
+static const char kLinePrefixSessionPhone[] = "p=";
+static const char kLinePrefixSessionConnection[] = "c=";
+static const char kLinePrefixSessionBandwidth[] = "b=";
+static const char kLinePrefixTiming[] = "t=";
+static const char kLinePrefixRepeatTimes[] = "r=";
+static const char kLinePrefixTimeZone[] = "z=";
+static const char kLinePrefixEncryptionKey[] = "k=";
+static const char kLinePrefixMedia[] = "m=";
+static const char kLinePrefixAttributes[] = "a=";
+
+// Attributes
+static const char kAttributeMid[] = "mid:";
+static const char kAttributeRtcpMux[] = "rtcp-mux";
+static const char kAttributeSsrc[] = "ssrc:";
+static const char kAttributeCname[] = "cname:";
+static const char kAttributeMslabel[] = "mslabel:";
+static const char kAttributeLabel[] = "label:";
+static const char kAttributeCrypto[] = "crypto:";
+static const char kAttributeCandidate[] = "candidate:";
+static const char kAttributeCandidateTyp[] = "typ";
+static const char kAttributeCandidateName[] = "name";
+static const char kAttributeCandidateNetworkName[] = "network_name";
+static const char kAttributeCandidateUsername[] = "username";
+static const char kAttributeCandidatePassword[] = "password";
+static const char kAttributeCandidateGeneration[] = "generation";
+static const char kAttributeRtpmap[] = "rtpmap:";
+
+// Candidate
+static const char kCandidateHost[] = "host";
+static const char kCandidateSrflx[] = "srflx";
+// TODO: How to map the prflx with circket candidate type
+// static const char kCandidatePrflx[] = "prflx";
+static const char kCandidateRelay[] = "relay";
+
+static const char kSdpDelimiter = ' ';
+static const char kLineBreak[] = "\r\n";
+
+// TODO: Generate the Session and Time description
+// instead of hardcoding.
+static const char kSessionVersion[] = "v=0";
+static const char kSessionOrigin[] = "o=- 0 0 IN IP4 127.0.0.1";
+static const char kSessionName[] = "s=";
+static const char kTimeDescription[] = "t=0 0";
+static const char kAttrGroup[] = "a=group:BUNDLE audio video";
+static const int kIceComponent = 1;
+static const int kIceFoundation = 1;
+static const char kMediaTypeVideo[] = "video";
+static const char kMediaTypeAudio[] = "audio";
+
+// Default Video resolution.
+// TODO: Implement negotiation of video resolution.
+static const int kDefaultVideoWidth = 640;
+static const int kDefaultVideoHeight = 480;
+static const int kDefaultVideoFrameRate = 30;
+static const int kDefaultVideoPreference = 0;
+
+static void BuildMediaDescription(const cricket::ContentInfo& content_info,
+ const MediaType media_type,
+ std::string* message);
+static void BuildRtpMap(const MediaContentDescription* media_desc,
+ const MediaType media_type,
+ std::string* message);
+static void BuildCandidate(const std::vector<Candidate>& candidates,
+ const MediaType media_type,
+ std::string* message);
+
+static bool ParseSessionDescription(const std::string& message, size_t* pos);
+static bool ParseTimeDescription(const std::string& message, size_t* pos);
+static bool ParseMediaDescription(const std::string& message, size_t* pos,
+ cricket::SessionDescription* desc);
+static bool ParseContent(const std::string& message,
+ const MediaType media_type,
+ size_t* pos,
+ ContentDescription* content);
+static bool ParseCandidates(const std::string& message,
+ std::vector<Candidate>* candidates);
+
+// Helper functions
+#define LOG_PREFIX_PARSING_ERROR(line_prefix) LOG(LS_ERROR) \
+ << "Failed to parse the \"" << line_prefix << "\" line";
+
+#define LOG_LINE_PARSING_ERROR(line) LOG(LS_ERROR) \
+ << "Failed to parse line:" << line;
+
+static bool AddLine(const std::string& line, std::string* message) {
+ if (!message)
+ return false;
+
+ message->append(line);
+ message->append(kLineBreak);
+ return true;
+}
+
+static bool GetLine(const std::string& message,
+ size_t* pos,
+ std::string* line) {
+ size_t line_begin = *pos;
+ size_t line_end = message.find('\n', line_begin);
+ if (line_end == std::string::npos) {
+ return false;
+ }
+ // Update the new start position
+ *pos = line_end + 1;
+ if (line_end > 0 && (message.at(line_end - 1) == '\r')) {
+ --line_end;
+ }
+ *line = message.substr(line_begin, (line_end - line_begin));
+ return true;
+}
+
+static bool GetLineWithPrefix(const std::string& message, size_t* pos,
+ std::string* line, const char* type) {
+ if (message.compare(*pos, kLinePrefixLength, type) != 0) {
+ return false;
+ }
+
+ if (!GetLine(message, pos, line))
+ return false;
+
+ return true;
+}
+
+static bool HasPrefix(const std::string& line,
+ const std::string& prefix,
+ size_t pos) {
+ return (line.compare(pos, prefix.size(), prefix) == 0);
+}
+
+static bool HasPrefix(const std::string& line,
+ const std::string& prefix) {
+ return HasPrefix(line, prefix, 0);
+}
+
+static bool HasAttribute(const std::string& line,
+ const std::string& attribute) {
+ return (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0);
+}
+
+std::string SdpSerialize(const cricket::SessionDescription& desc,
+ const std::vector<Candidate>& candidates) {
+ return SdpFormat(SdpSerializeSessionDescription(desc),
+ SdpSerializeCandidates(candidates));
+}
+
+std::string SdpSerializeSessionDescription(
+ const cricket::SessionDescription& desc) {
+ std::string message;
+
+ // Session Description.
+ AddLine(kSessionVersion, &message);
+ AddLine(kSessionOrigin, &message);
+ AddLine(kSessionName, &message);
+
+ // Time Description.
+ AddLine(kTimeDescription, &message);
+
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(&desc);
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(&desc);
+
+ // Group
+ if (audio_content && video_content)
+ AddLine(kAttrGroup, &message);
+
+ // Media Description
+ if (audio_content) {
+ BuildMediaDescription(*audio_content, cricket::MEDIA_TYPE_AUDIO, &message);
+ }
+
+ if (video_content) {
+ BuildMediaDescription(*video_content, cricket::MEDIA_TYPE_VIDEO, &message);
+ }
+
+ return message;
+}
+
+std::string SdpSerializeCandidates(const std::vector<Candidate>& candidates) {
+ std::string message;
+ // rfc5245
+ // a=candidate:<foundation> <component-id> <transport> <priority>
+ // <connection-address> <port> typ <candidate-types>
+ // [raddr <connection-address>] [rport <port>]
+ BuildCandidate(candidates, cricket::MEDIA_TYPE_AUDIO, &message);
+ BuildCandidate(candidates, cricket::MEDIA_TYPE_VIDEO, &message);
+ return message;
+}
+
+std::string SdpFormat(const std::string& desc, const std::string& candidates) {
+ std::string sdp; // New sdp message.
+
+ std::vector<Candidate> candidates_vector;
+ if (!ParseCandidates(candidates, &candidates_vector))
+ return sdp;
+
+ size_t pos = 0;
+ std::string line;
+ while (GetLine(desc, &pos, &line)) {
+ AddLine(line, &sdp); // Copy old line to new sdp.
+ if (!HasPrefix(line, kLinePrefixMedia)) {
+ continue; // Loop until the next m line.
+ }
+ if (HasAttribute(line, kMediaTypeVideo)) {
+ BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_VIDEO, &sdp);
+ } else if (HasAttribute(line, kMediaTypeAudio)) {
+ BuildCandidate(candidates_vector, cricket::MEDIA_TYPE_AUDIO, &sdp);
+ }
+ }
+
+ return sdp;
+}
+
+
+bool SdpDeserialize(const std::string& message,
+ cricket::SessionDescription* desc,
+ std::vector<Candidate>* candidates) {
+ return SdpDeserializeSessionDescription(message, desc) &&
+ SdpDeserializeCandidates(message, candidates);
+}
+
+bool SdpDeserializeSessionDescription(const std::string& message,
+ cricket::SessionDescription* desc) {
+ size_t current_pos = 0;
+
+ // Session Description
+ if (!ParseSessionDescription(message, ¤t_pos)) {
+ return false;
+ }
+
+ // Time Description
+ if (!ParseTimeDescription(message, ¤t_pos)) {
+ return false;
+ }
+
+ // Media Description
+ if (!ParseMediaDescription(message, ¤t_pos, desc)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SdpDeserializeCandidates(const std::string& message,
+ std::vector<Candidate>* candidates) {
+ return ParseCandidates(message, candidates);
+}
+
+void BuildMediaDescription(const cricket::ContentInfo& content_info,
+ const MediaType media_type,
+ std::string* message) {
+ ASSERT(message != NULL);
+ // TODO: Rethink if we should use sprintfn instead of stringstream.
+ // According to the style guide, streams should only be used for logging.
+ // http://google-styleguide.googlecode.com/svn/
+ // trunk/cppguide.xml?showone=Streams#Streams
+ std::ostringstream os;
+ const MediaContentDescription* media_desc =
+ static_cast<const MediaContentDescription*> (
+ content_info.description);
+ ASSERT(media_desc != NULL);
+
+ // m=<media> <port> <proto> <fmt>
+ // fmt is a list of payload type numbers that MAY be used in the session.
+ const char* type = NULL;
+ if (media_type == cricket::MEDIA_TYPE_AUDIO)
+ type = kMediaTypeAudio;
+ else if (media_type == cricket::MEDIA_TYPE_VIDEO)
+ type = kMediaTypeVideo;
+ else
+ ASSERT(false);
+
+ std::string fmt;
+ if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+ const VideoContentDescription* video_desc =
+ static_cast<const VideoContentDescription*>(media_desc);
+ for (std::vector<cricket::VideoCodec>::const_iterator it =
+ video_desc->codecs().begin();
+ it != video_desc->codecs().end(); ++it) {
+ fmt.append(" ");
+ fmt.append(talk_base::ToString<int>(it->id));
+ }
+ } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+ const AudioContentDescription* audio_desc =
+ static_cast<const AudioContentDescription*>(media_desc);
+ for (std::vector<cricket::AudioCodec>::const_iterator it =
+ audio_desc->codecs().begin();
+ it != audio_desc->codecs().end(); ++it) {
+ fmt.append(" ");
+ fmt.append(talk_base::ToString<int>(it->id));
+ }
+ }
+ const int port = 0;
+ const char* proto = "RTP/AVPF";
+ os.str("");
+ os << kLinePrefixMedia << type << " " << port << " " << proto << fmt;
+ AddLine(os.str(), message);
+
+ // a=mid:<media>
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeMid << type;
+ AddLine(os.str(), message);
+
+ // a=rtcp-mux
+ if (media_desc->rtcp_mux()) {
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeRtcpMux;
+ AddLine(os.str(), message);
+ }
+
+ // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+ for (std::vector<CryptoParams>::const_iterator it =
+ media_desc->cryptos().begin();
+ it != media_desc->cryptos().end(); ++it) {
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeCrypto << it->tag << " "
+ << it->cipher_suite << " "
+ << it->key_params << " "
+ << it->session_params;
+ AddLine(os.str(), message);
+ }
+
+ // a=rtpmap:<payload type> <encoding name>/<clock rate>
+ // [/<encodingparameters>]
+ BuildRtpMap(media_desc, media_type, message);
+
+ // draft - Mechanisms for Media Source Selection in SDP
+ // a=ssrc:<ssrc-id> <attribute>:<value>
+ // a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
+ for (cricket::StreamParamsVec::const_iterator it =
+ media_desc->streams().begin();
+ it != media_desc->streams().end(); ++it) {
+ // Require that the track belongs to a media stream,
+ // ie the sync_label is set. This extra check is necessary since the
+ // MediaContentDescription always contains a streamparam with an ssrc even
+ // if no track or media stream have been created.
+ if (it->sync_label.empty()) continue;
+
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeSsrc << it->ssrcs[0] << " "
+ << kAttributeCname << it->cname << " "
+ << kAttributeMslabel << it->sync_label << " "
+ << kAttributeLabel << it->name;
+ AddLine(os.str(), message);
+ }
+}
+
+void BuildRtpMap(const MediaContentDescription* media_desc,
+ const MediaType media_type,
+ std::string* message) {
+ ASSERT(message != NULL);
+ ASSERT(media_desc != NULL);
+ std::ostringstream os;
+ if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+ const VideoContentDescription* video_desc =
+ static_cast<const VideoContentDescription*>(media_desc);
+ for (std::vector<cricket::VideoCodec>::const_iterator it =
+ video_desc->codecs().begin();
+ it != video_desc->codecs().end(); ++it) {
+ // a=rtpmap:<payload type> <encoding name>/<clock rate>
+ // [/<encodingparameters>]
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
+ << it->name << "/" << 0;
+ AddLine(os.str(), message);
+ }
+ } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+ const AudioContentDescription* audio_desc =
+ static_cast<const AudioContentDescription*>(media_desc);
+ for (std::vector<cricket::AudioCodec>::const_iterator it =
+ audio_desc->codecs().begin();
+ it != audio_desc->codecs().end(); ++it) {
+ // a=rtpmap:<payload type> <encoding name>/<clock rate>
+ // [/<encodingparameters>]
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeRtpmap << it->id << " "
+ << it->name << "/" << it->clockrate;
+ AddLine(os.str(), message);
+ }
+ }
+}
+
+void BuildCandidate(const std::vector<Candidate>& candidates,
+ const MediaType media_type,
+ std::string* message) {
+ std::ostringstream os;
+ for (std::vector<Candidate>::const_iterator it = candidates.begin();
+ it != candidates.end(); ++it) {
+ // a=candidate:<foundation> <component-id> <transport> <priority>
+ // <connection-address> <port> typ <candidate-types>
+ // [raddr <connection-address>] [rport <port>]
+ // *(SP extension-att-name SP extension-att-value)
+ if (((media_type == cricket::MEDIA_TYPE_VIDEO) &&
+ (it->name() == "video_rtcp" || it->name() == "video_rtp")) ||
+ ((media_type == cricket::MEDIA_TYPE_AUDIO) &&
+ (it->name() == "rtp" || it->name() == "rtcp"))) {
+ std::string type;
+ // Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay"
+ if (it->type() == cricket::LOCAL_PORT_TYPE) {
+ type = kCandidateHost;
+ } else if (it->type() == cricket::STUN_PORT_TYPE) {
+ type = kCandidateSrflx;
+ } else if (it->type() == cricket::RELAY_PORT_TYPE) {
+ type = kCandidateRelay;
+ } else {
+ ASSERT(false);
+ }
+ os.str("");
+ os << kLinePrefixAttributes << kAttributeCandidate
+ << kIceFoundation << " " << kIceComponent << " "
+ << it->protocol() << " " << it->preference_str() << " "
+ << it->address().IPAsString() << " "
+ << it->address().PortAsString() << " "
+ << kAttributeCandidateTyp << " " << type << " "
+ << kAttributeCandidateName << " " << it->name() << " "
+ << kAttributeCandidateNetworkName << " " << it->network_name() << " "
+ << kAttributeCandidateUsername << " " << it->username() << " "
+ << kAttributeCandidatePassword << " " << it->password() << " "
+ << kAttributeCandidateGeneration << " " << it->generation();
+ AddLine(os.str(), message);
+ }
+ }
+}
+
+bool ParseSessionDescription(const std::string& message, size_t* pos) {
+ std::string line;
+
+ // v= (protocol version)
+ if (!GetLineWithPrefix(message, pos, &line, kLinePrefixVersion)) {
+ LOG_PREFIX_PARSING_ERROR(kLinePrefixVersion);
+ return false;
+ }
+ // o= (originator and session identifier)
+ if (!GetLineWithPrefix(message, pos, &line, kLinePrefixOrigin)) {
+ LOG_PREFIX_PARSING_ERROR(kLinePrefixOrigin);
+ return false;
+ }
+ // s= (session name)
+ if (!GetLineWithPrefix(message, pos, &line, kLinePrefixSessionName)) {
+ LOG_PREFIX_PARSING_ERROR(kLinePrefixSessionName);
+ return false;
+ }
+
+ // Optional lines
+ // Those are the optional lines, so shouldn't return false if not present.
+ // i=* (session information)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixSessionInfo);
+
+ // u=* (URI of description)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixSessionUri);
+
+ // e=* (email address)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixSessionEmail);
+
+ // p=* (phone number)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixSessionPhone);
+
+ // c=* (connection information -- not required if included in
+ // all media)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixSessionConnection);
+
+ // b=* (zero or more bandwidth information lines)
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixSessionBandwidth)) {
+ // By pass zero or more b lines.
+ }
+
+ return true;
+}
+
+bool ParseTimeDescription(const std::string& message, size_t* pos) {
+ std::string line;
+ // One or more time descriptions ("t=" and "r=" lines; see below)
+ // t= (time the session is active)
+ // r=* (zero or more repeat times)
+ // Ensure there's at least one time description
+ if (!GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
+ LOG_PREFIX_PARSING_ERROR(kLinePrefixTiming);
+ return false;
+ }
+
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
+ // By pass zero or more r lines.
+ }
+
+ // Go through the rest of the time descriptions
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixTiming)) {
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixRepeatTimes)) {
+ // By pass zero or more r lines.
+ }
+ }
+
+ // z=* (time zone adjustments)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixTimeZone);
+
+ // k=* (encryption key)
+ GetLineWithPrefix(message, pos, &line, kLinePrefixEncryptionKey);
+
+ // a=* (zero or more session attribute lines)
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixAttributes)) {
+ // TODO: parse the a=group:BUNDLE
+ }
+
+ return true;
+}
+
+bool ParseMediaDescription(const std::string& message, size_t* pos,
+ cricket::SessionDescription* desc) {
+ ASSERT(desc != NULL);
+
+ std::string line;
+
+ // Zero or more media descriptions
+ // m=<media> <port> <proto> <fmt>
+ while (GetLineWithPrefix(message, pos, &line, kLinePrefixMedia)) {
+ MediaType media_type = cricket::MEDIA_TYPE_VIDEO;
+ ContentDescription* content = NULL;
+ if (HasAttribute(line, kMediaTypeVideo)) {
+ media_type = cricket::MEDIA_TYPE_VIDEO;
+ content = new VideoContentDescription();
+ desc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, content);
+ } else if (HasAttribute(line, kMediaTypeAudio)) {
+ media_type = cricket::MEDIA_TYPE_AUDIO;
+ content = new AudioContentDescription();
+ desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, content);
+ } else {
+ LOG(LS_WARNING) << "Unsupported media type: " << line;
+ }
+
+ if (!ParseContent(message, media_type, pos, content))
+ return false;
+ }
+ return true;
+}
+
+bool ParseContent(const std::string& message,
+ const MediaType media_type,
+ size_t* pos,
+ ContentDescription* content) {
+ std::string line;
+ // Loop until the next m line
+ while (!HasPrefix(message, kLinePrefixMedia, *pos)) {
+ if (!GetLine(message, pos, &line)) {
+ if (*pos >= message.size())
+ return true; // Done parsing
+ else
+ return false;
+ }
+
+ if (!content) {
+ // Unsupported media type, just skip it.
+ continue;
+ }
+
+ if (!HasPrefix(line, kLinePrefixAttributes)) {
+ // TODO: Handle other lines if needed.
+ continue;
+ }
+
+ MediaContentDescription* media_desc =
+ static_cast<MediaContentDescription*> (content);
+
+ std::vector<std::string> fields;
+ talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
+
+ if (HasAttribute(line, kAttributeMid)) {
+ continue;
+ } else if (HasAttribute(line, kAttributeRtcpMux)) {
+ media_desc->set_rtcp_mux(true);
+ } else if (HasAttribute(line, kAttributeSsrc)) {
+ // a=ssrc:<ssrc-id> cname:<value> mslabel:<value> label:<value>
+ uint32 ssrc = 0;
+ std::string cname;
+ std::string mslabel;
+ std::string label;
+ for (std::vector<std::string>::const_iterator it = fields.begin();
+ it != fields.end(); ++it) {
+ if (HasPrefix(*it, kAttributeSsrc)) {
+ ASSERT(it == fields.begin());
+ ssrc = talk_base::FromString<uint32>(
+ it->substr(strlen(kAttributeSsrc)));
+ } else if (HasPrefix(*it, kAttributeCname)) {
+ cname = it->substr(strlen(kAttributeCname));
+ } else if (HasPrefix(*it, kAttributeMslabel)) {
+ mslabel = it->substr(strlen(kAttributeMslabel));
+ } else if (HasPrefix(*it, kAttributeLabel)) {
+ label = it->substr(strlen(kAttributeLabel));
+ }
+ }
+ StreamParams stream;
+ stream.name = label;
+ stream.cname = cname;
+ stream.sync_label = mslabel;
+ stream.ssrcs.push_back(ssrc);
+ media_desc->AddStream(stream);
+ } else if (HasAttribute(line, kAttributeCrypto)) {
+ // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>]
+ if (fields.size() < 3) { // 3 mandatory fields
+ LOG_LINE_PARSING_ERROR(line);
+ return false;
+ }
+ int tag = talk_base::FromString<int>(
+ fields[0].substr(strlen(kAttributeCrypto)));
+ const std::string crypto_suite = fields[1];
+ const std::string key_params = fields[2];
+ media_desc->AddCrypto(CryptoParams(tag, crypto_suite, key_params, ""));
+ } else if (HasAttribute(line, kAttributeCandidate)) {
+ continue; // Parse candidates separately.
+ } else if (HasAttribute(line, kAttributeRtpmap)) {
+ // a=rtpmap:<payload type> <encoding name>/<clock rate>
+ // [/<encodingparameters>]
+ // 2 mandatory fields
+ if (fields.size() < 2) {
+ LOG_LINE_PARSING_ERROR(line);
+ return false;
+ }
+ const int payload_type = talk_base::FromString<int>(
+ fields[0].substr(strlen(kAttributeRtpmap)));
+ const std::string encoder = fields[1];
+ const size_t pos = encoder.find("/");
+ if (pos == std::string::npos)
+ return false;
+ const std::string encoding_name = encoder.substr(0, pos);
+ const int clock_rate =
+ talk_base::FromString<int>(encoder.substr(pos + 1));
+ if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+ VideoContentDescription* video_desc =
+ static_cast<VideoContentDescription*>(media_desc);
+ // TODO: We will send resolution in SDP. For now, use VGA.
+ video_desc->AddCodec(cricket::VideoCodec(payload_type, encoding_name,
+ kDefaultVideoWidth,
+ kDefaultVideoHeight,
+ kDefaultVideoFrameRate,
+ kDefaultVideoPreference));
+ } else if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+ AudioContentDescription* audio_desc =
+ static_cast<AudioContentDescription*>(media_desc);
+ audio_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name,
+ clock_rate, 0, 0, 0));
+ }
+ } else {
+ LOG(LS_WARNING) << "Unsupported line: " << line;
+ }
+ }
+ return true;
+}
+
+bool ParseCandidates(const std::string& message,
+ std::vector<Candidate>* candidates) {
+ ASSERT(candidates != NULL);
+ std::string line;
+ size_t pos = 0;
+
+ // Loop until the next attribute line.
+ while (GetLine(message, &pos, &line)) {
+ if (!HasPrefix(line, kLinePrefixAttributes) ||
+ !HasAttribute(line, kAttributeCandidate)) {
+ continue; // Only parse candidates
+ }
+ std::vector<std::string> fields;
+ talk_base::split(line.substr(kLinePrefixLength), kSdpDelimiter, &fields);
+ // a=candidate:<foundation> <component-id> <transport> <priority>
+ // <connection-address> <port> typ <candidate-types>
+ // [raddr <connection-address>] [rport <port>]
+ // *(SP extension-att-name SP extension-att-value)
+ // 8 mandatory fields
+ if (fields.size() < 8 || (fields[6] != kAttributeCandidateTyp)) {
+ LOG_LINE_PARSING_ERROR(line);
+ return false;
+ }
+ const std::string transport = fields[2];
+ const float priority = talk_base::FromString<float>(fields[3]);
+ const std::string connection_address = fields[4];
+ const int port = talk_base::FromString<int>(fields[5]);
+ std::string candidate_type;
+ const std::string type = fields[7];
+ if (type == kCandidateHost) {
+ candidate_type = cricket::LOCAL_PORT_TYPE;
+ } else if (type == kCandidateSrflx) {
+ candidate_type = cricket::STUN_PORT_TYPE;
+ } else if (type == kCandidateRelay) {
+ candidate_type = cricket::RELAY_PORT_TYPE;
+ } else {
+ LOG(LS_ERROR) << "Unsupported candidate type from line: " << line;
+ return false;
+ }
+
+ // extension
+ std::string name;
+ std::string network_name;
+ std::string username;
+ std::string password;
+ uint32 generation = 0;
+ for (size_t i = 8; i < (fields.size() - 1); ++i) {
+ const std::string field = fields.at(i);
+ if (field == kAttributeCandidateName) {
+ name = fields.at(++i);
+ } else if (field == kAttributeCandidateNetworkName) {
+ network_name = fields.at(++i);
+ } else if (field == kAttributeCandidateUsername) {
+ username = fields.at(++i);
+ } else if (field == kAttributeCandidatePassword) {
+ password = fields.at(++i);
+ } else if (field == kAttributeCandidateGeneration) {
+ generation = talk_base::FromString<uint32>(fields.at(++i));
+ }
+ }
+
+ SocketAddress address(connection_address, port);
+ Candidate candidate(name, transport, address, priority, username,
+ password, candidate_type, network_name, generation);
+ candidates->push_back(candidate);
+ }
+ return true;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsdp.h b/talk/app/webrtc/webrtcsdp.h
new file mode 100644
index 0000000..e5c1b12
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp.h
@@ -0,0 +1,99 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contain functions for parsing and serializing SDP messages.
+// Related RFC/draft including:
+// * RFC 4566 - SDP
+// * RFC 5245 - ICE
+// * RFC 3388 - Grouping of Media Lines in SDP
+// * RFC 4568 - SDP Security Descriptions for Media Streams
+// * draft-lennox-mmusic-sdp-source-selection-02 -
+// Mechanisms for Media Source Selection in SDP
+
+#ifndef TALK_APP_WEBRTC_WEBRTCSDP_H_
+#define TALK_APP_WEBRTC_WEBRTCSDP_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/p2p/base/candidate.h"
+
+namespace cricket {
+class SessionDescription;
+}
+
+namespace webrtc {
+
+// Serializes the passed in SessionDescription and Candidates to a SDP string.
+// desc - The SessionDescription object to be serialized.
+// candidates - The Set of Candidate objects to be serialized.
+// return - SDP string serialized from the arguments.
+std::string SdpSerialize(const cricket::SessionDescription& desc,
+ const std::vector<cricket::Candidate>& candidates);
+
+// Serializes the passed in SessionDescription to a SDP string.
+// desc - The SessionDescription object to be serialized.
+std::string SdpSerializeSessionDescription(
+ const cricket::SessionDescription& desc);
+
+// Serializes the passed in Candidates to a SDP string.
+// candidates - The Set of Candidate objects to be serialized.
+std::string SdpSerializeCandidates(
+ const std::vector<cricket::Candidate>& candidates);
+
+// Deserializes the passed in SDP string to a SessionDescription and Candidates.
+// message - SDP string to be Deserialized.
+// desc - The SessionDescription object deserialized from the SDP string.
+// candidates - The set of Candidate deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserialize(const std::string& message,
+ cricket::SessionDescription* desc,
+ std::vector<cricket::Candidate>* candidates);
+
+// Deserializes the passed in SDP string to a SessionDescription.
+// Candidates are ignored.
+// message - SDP string to be Deserialized.
+// desc - The SessionDescription object deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeSessionDescription(const std::string& message,
+ cricket::SessionDescription* desc);
+
+// Deserializes the passed in SDP string to Candidates.
+// Only the candidates are parsed from the SDP string.
+// message - SDP string to be Deserialized.
+// candidates - The set of Candidate deserialized from the SDP string.
+// return - true on success, false on failure.
+bool SdpDeserializeCandidates(const std::string& message,
+ std::vector<cricket::Candidate>* candidates);
+
+// Formats a correct SDP string by reformatting a session description and
+// candidates.
+std::string SdpFormat(const std::string& desc, const std::string& candidates);
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_WEBRTCSDP_H_
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
new file mode 100644
index 0000000..dbc7ccd
--- /dev/null
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -0,0 +1,374 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/app/webrtc/webrtcsdp.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/mediasession.h"
+
+typedef std::vector<cricket::Candidate> Candidates;
+using cricket::AudioCodec;
+using cricket::AudioContentDescription;
+using cricket::ContentInfo;
+using cricket::CryptoParams;
+using cricket::SessionDescription;
+using cricket::StreamParams;
+using cricket::VideoCodec;
+using cricket::VideoContentDescription;
+
+// Reference sdp string
+static const char kSdpFullString[] =
+ "v=0\r\n"
+ "o=- 0 0 IN IP4 127.0.0.1\r\n"
+ "s=\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "m=audio 0 RTP/AVPF 103 104\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+ "eth0 username user_rtp password password_rtp generation 0\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+ "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+ "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_audio_1\r\n"
+ "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
+ "label:local_audio_2\r\n"
+ "m=video 0 RTP/AVPF 120\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+ "network_name eth0 username user_video_rtcp password password_video_rtcp "
+ "generation 0\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+ "network_name eth0 username user_video_rtp password password_video_rtp "
+ "generation 0\r\n"
+ "a=mid:video\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+ "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
+ "a=rtpmap:120 VP8/0\r\n"
+ "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_video_1\r\n"
+ "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_video_2\r\n"
+ "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
+ "label:local_video_3\r\n";
+
+// SDP reference string without the candidates.
+static const char kSdpString[] =
+ "v=0\r\n"
+ "o=- 0 0 IN IP4 127.0.0.1\r\n"
+ "s=\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE audio video\r\n"
+ "m=audio 0 RTP/AVPF 103 104\r\n"
+ "a=mid:audio\r\n"
+ "a=rtcp-mux\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_32 "
+ "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 \r\n"
+ "a=rtpmap:103 ISAC/16000\r\n"
+ "a=rtpmap:104 ISAC/32000\r\n"
+ "a=ssrc:1 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_audio_1\r\n"
+ "a=ssrc:4 cname:stream_2_cname mslabel:local_stream_2 "
+ "label:local_audio_2\r\n"
+ "m=video 0 RTP/AVPF 120\r\n"
+ "a=mid:video\r\n"
+ "a=crypto:1 AES_CM_128_HMAC_SHA1_80 "
+ "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 \r\n"
+ "a=rtpmap:120 VP8/0\r\n"
+ "a=ssrc:2 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_video_1\r\n"
+ "a=ssrc:3 cname:stream_1_cname mslabel:local_stream_1 "
+ "label:local_video_2\r\n"
+ "a=ssrc:5 cname:stream_2_cname mslabel:local_stream_2 "
+ "label:local_video_3\r\n";
+
+// Candidates reference string.
+static const char kSdpCandidates[] =
+ "a=candidate:1 1 udp 1 127.0.0.1 1234 typ host name rtp network_name "
+ "eth0 username user_rtp password password_rtp generation 0\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1235 typ host name rtcp network_name "
+ "eth0 username user_rtcp password password_rtcp generation 0\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1236 typ host name video_rtcp "
+ "network_name eth0 username user_video_rtcp password password_video_rtcp "
+ "generation 0\r\n"
+ "a=candidate:1 1 udp 1 127.0.0.1 1237 typ host name video_rtp "
+ "network_name eth0 username user_video_rtp password password_video_rtp "
+ "generation 0\r\n";
+
+static const char kSdpDestroyer[] = "!@#$%^&";
+
+// MediaStream 1
+static const char kStreamLabel1[] = "local_stream_1";
+static const char kStream1Cname[] = "stream_1_cname";
+static const char kAudioTrackLabel1[] = "local_audio_1";
+static const uint32 kAudioTrack1Ssrc = 1;
+static const char kVideoTrackLabel1[] = "local_video_1";
+static const uint32 kVideoTrack1Ssrc = 2;
+static const char kVideoTrackLabel2[] = "local_video_2";
+static const uint32 kVideoTrack2Ssrc = 3;
+
+// MediaStream 2
+static const char kStreamLabel2[] = "local_stream_2";
+static const char kStream2Cname[] = "stream_2_cname";
+static const char kAudioTrackLabel2[] = "local_audio_2";
+static const uint32 kAudioTrack2Ssrc = 4;
+static const char kVideoTrackLabel3[] = "local_video_3";
+static const uint32 kVideoTrack3Ssrc = 5;
+
+class WebRtcSdpTest : public testing::Test {
+ public:
+ WebRtcSdpTest() {
+ // AudioContentDescription
+ talk_base::scoped_ptr<AudioContentDescription> audio(
+ new AudioContentDescription());
+ audio->set_rtcp_mux(true);
+ StreamParams audio_stream1;
+ audio_stream1.name = kAudioTrackLabel1;
+ audio_stream1.cname = kStream1Cname;
+ audio_stream1.sync_label = kStreamLabel1;
+ audio_stream1.ssrcs.push_back(kAudioTrack1Ssrc);
+ audio->AddStream(audio_stream1);
+ StreamParams audio_stream2;
+ audio_stream2.name = kAudioTrackLabel2;
+ audio_stream2.cname = kStream2Cname;
+ audio_stream2.sync_label = kStreamLabel2;
+ audio_stream2.ssrcs.push_back(kAudioTrack2Ssrc);
+ audio->AddStream(audio_stream2);
+ audio->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_32",
+ "inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32", ""));
+ audio->AddCodec(AudioCodec(103, "ISAC", 16000, 0, 0, 0));
+ audio->AddCodec(AudioCodec(104, "ISAC", 32000, 0, 0, 0));
+ desc_.AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ audio.release());
+
+ // VideoContentDescription
+ talk_base::scoped_ptr<VideoContentDescription> video(
+ new VideoContentDescription());
+ StreamParams video_stream1;
+ video_stream1.name = kVideoTrackLabel1;
+ video_stream1.cname = kStream1Cname;
+ video_stream1.sync_label = kStreamLabel1;
+ video_stream1.ssrcs.push_back(kVideoTrack1Ssrc);
+ video->AddStream(video_stream1);
+ StreamParams video_stream2;
+ video_stream2.name = kVideoTrackLabel2;
+ video_stream2.cname = kStream1Cname;
+ video_stream2.sync_label = kStreamLabel1;
+ video_stream2.ssrcs.push_back(kVideoTrack2Ssrc);
+ video->AddStream(video_stream2);
+ StreamParams video_stream3;
+ video_stream3.name = kVideoTrackLabel3;
+ video_stream3.cname = kStream2Cname;
+ video_stream3.sync_label = kStreamLabel2;
+ video_stream3.ssrcs.push_back(kVideoTrack3Ssrc);
+ video->AddStream(video_stream3);
+ video->AddCrypto(CryptoParams(1, "AES_CM_128_HMAC_SHA1_80",
+ "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32", ""));
+ video->AddCodec(VideoCodec(120, "VP8", 640, 480, 30, 0));
+ desc_.AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+ video.release());
+
+ int port = 1234;
+ talk_base::SocketAddress address("127.0.0.1", port++);
+ cricket::Candidate candidate1("rtp", "udp", address, 1,
+ "user_rtp", "password_rtp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate2("rtcp", "udp", address, 1,
+ "user_rtcp", "password_rtcp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate3("video_rtcp", "udp", address, 1,
+ "user_video_rtcp", "password_video_rtcp", "local", "eth0", 0);
+ address.SetPort(port++);
+ cricket::Candidate candidate4("video_rtp", "udp", address, 1,
+ "user_video_rtp", "password_video_rtp", "local", "eth0", 0);
+
+ candidates_.push_back(candidate1);
+ candidates_.push_back(candidate2);
+ candidates_.push_back(candidate3);
+ candidates_.push_back(candidate4);
+ }
+
+ bool CompareSessionDescription(const SessionDescription& desc1,
+ const SessionDescription& desc2) {
+ const ContentInfo* ac1 = desc1.GetContentByName("audio");
+ const AudioContentDescription* acd1 =
+ static_cast<const AudioContentDescription*>(ac1->description);
+ const ContentInfo* vc1 = desc1.GetContentByName("video");
+ const VideoContentDescription* vcd1 =
+ static_cast<const VideoContentDescription*>(vc1->description);
+
+ const ContentInfo* ac2 = desc2.GetContentByName("audio");
+ const AudioContentDescription* acd2 =
+ static_cast<const AudioContentDescription*>(ac2->description);
+ const ContentInfo* vc2 = desc2.GetContentByName("video");
+ const VideoContentDescription* vcd2 =
+ static_cast<const VideoContentDescription*>(vc2->description);
+
+ // rtcp_mux
+ EXPECT_EQ(acd1->rtcp_mux(), acd2->rtcp_mux());
+ EXPECT_EQ(vcd1->rtcp_mux(), vcd2->rtcp_mux());
+
+ // cryptos
+ EXPECT_EQ(acd1->cryptos().size(), acd2->cryptos().size());
+ EXPECT_EQ(vcd1->cryptos().size(), vcd2->cryptos().size());
+ if (acd1->cryptos().size() != acd2->cryptos().size() ||
+ vcd1->cryptos().size() != vcd2->cryptos().size()) {
+ return false;
+ }
+ for (size_t i = 0; i< acd1->cryptos().size(); ++i) {
+ const CryptoParams c1 = acd1->cryptos().at(i);
+ const CryptoParams c2 = acd2->cryptos().at(i);
+ EXPECT_TRUE(c1.Matches(c2));
+ }
+ for (size_t i = 0; i< vcd1->cryptos().size(); ++i) {
+ const CryptoParams c1 = vcd1->cryptos().at(i);
+ const CryptoParams c2 = vcd2->cryptos().at(i);
+ EXPECT_TRUE(c1.Matches(c2));
+ }
+
+ // codecs
+ EXPECT_EQ(acd1->codecs().size(), acd2->codecs().size());
+ if (acd1->codecs().size() != acd2->codecs().size())
+ return false;
+ EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
+ if (vcd1->codecs().size() != vcd2->codecs().size())
+ return false;
+ for (size_t i = 0; i< acd1->codecs().size(); ++i) {
+ const AudioCodec c1 = acd1->codecs().at(i);
+ const AudioCodec c2 = acd2->codecs().at(i);
+ EXPECT_TRUE(c1.Matches(c2));
+ }
+ for (size_t i = 0; i< vcd1->codecs().size(); ++i) {
+ const VideoCodec c1 = vcd1->codecs().at(i);
+ const VideoCodec c2 = vcd2->codecs().at(i);
+ EXPECT_TRUE(c1.Matches(c2));
+ EXPECT_EQ(c1.width, c2.width);
+ EXPECT_EQ(c1.height, c2.height);
+ EXPECT_EQ(c1.framerate, c2.framerate);
+ }
+
+ // streams
+ EXPECT_EQ(acd1->streams(), acd2->streams());
+ EXPECT_EQ(vcd1->streams(), vcd2->streams());
+
+ return true;
+ }
+
+ bool CompareCandidates(const Candidates& cs1, const Candidates& cs2) {
+ EXPECT_EQ(cs1.size(), cs2.size());
+ if (cs1.size() != cs2.size())
+ return false;
+ for (size_t i = 0; i< cs1.size(); ++i) {
+ const cricket::Candidate c1 = cs1.at(i);
+ const cricket::Candidate c2 = cs2.at(i);
+ EXPECT_TRUE(c1.IsEquivalent(c2));
+ }
+ return true;
+ }
+
+ bool ReplaceAndTryToParse(const char* search, const char* replace) {
+ SessionDescription desc;
+ std::vector<cricket::Candidate> candidates;
+ std::string sdp = kSdpFullString;
+ talk_base::replace_substrs(search, strlen(search), replace,
+ strlen(replace), &sdp);
+ return webrtc::SdpDeserialize(sdp, &desc, &candidates);
+ }
+
+ protected:
+ SessionDescription desc_;
+ Candidates candidates_;
+};
+
+TEST_F(WebRtcSdpTest, Serialize) {
+ std::string message = webrtc::SdpSerialize(desc_, candidates_);
+ LOG(LS_INFO) << "SDP: " << message;
+ EXPECT_EQ(std::string(kSdpFullString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeSessionDescription) {
+ std::string message = webrtc::SdpSerializeSessionDescription(desc_);
+ EXPECT_EQ(std::string(kSdpString), message);
+}
+
+TEST_F(WebRtcSdpTest, SerializeCandidates) {
+ std::string message = webrtc::SdpSerializeCandidates(candidates_);
+ EXPECT_EQ(std::string(kSdpCandidates), message);
+}
+
+TEST_F(WebRtcSdpTest, Deserialize) {
+ SessionDescription desc;
+ std::vector<cricket::Candidate> candidates;
+ // Deserialize
+ EXPECT_TRUE(webrtc::SdpDeserialize(kSdpFullString, &desc, &candidates));
+ // Verify
+ LOG(LS_INFO) << "SDP: " << webrtc::SdpSerialize(desc, candidates);
+ EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSessionDescription) {
+ SessionDescription desc;
+ EXPECT_TRUE(webrtc::SdpDeserializeSessionDescription(kSdpString, &desc));
+ EXPECT_TRUE(CompareSessionDescription(desc_, desc));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeCandidates) {
+ std::vector<cricket::Candidate> candidates;
+ EXPECT_TRUE(webrtc::SdpDeserializeCandidates(kSdpCandidates, &candidates));
+ EXPECT_TRUE(CompareCandidates(candidates_, candidates));
+}
+
+TEST_F(WebRtcSdpTest, DeserializeBrokenSdp) {
+ // Broken session description
+ EXPECT_EQ(false, ReplaceAndTryToParse("v=", kSdpDestroyer));
+ EXPECT_EQ(false, ReplaceAndTryToParse("o=", kSdpDestroyer));
+ EXPECT_EQ(false, ReplaceAndTryToParse("s=", kSdpDestroyer));
+ // Broken time description
+ EXPECT_EQ(false, ReplaceAndTryToParse("t=", kSdpDestroyer));
+
+ // No group line
+ EXPECT_EQ(true, ReplaceAndTryToParse("a=group:BUNDLE audio video\r\n", ""));
+ EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:audio\r\n", ""));
+ EXPECT_EQ(true, ReplaceAndTryToParse("a=mid:video\r\n", ""));
+
+ // Broken media description
+ EXPECT_EQ(true, ReplaceAndTryToParse("video 0 RTP/AVPF", kSdpDestroyer));
+}
+
+TEST_F(WebRtcSdpTest, FormatSdp) {
+ std::string full_sdp = webrtc::SdpFormat(kSdpString, kSdpCandidates);
+ EXPECT_EQ(kSdpFullString, full_sdp);
+}
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index de414c5..e08344c 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -27,498 +27,408 @@
#include "talk/app/webrtc/webrtcsession.h"
-#include <string>
-#include <vector>
-
-#include "talk/base/common.h"
-#include "talk/base/json.h"
-#include "talk/base/scoped_ptr.h"
-#include "talk/p2p/base/constants.h"
-#include "talk/p2p/base/sessiondescription.h"
-#include "talk/p2p/base/p2ptransport.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtc/peerconnectionsignaling.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
#include "talk/session/phone/channel.h"
#include "talk/session/phone/channelmanager.h"
-#include "talk/session/phone/mediasessionclient.h"
-#include "talk/session/phone/voicechannel.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/videocapturer.h"
+
+using cricket::MediaContentDescription;
namespace webrtc {
enum {
MSG_CANDIDATE_TIMEOUT = 101,
+ MSG_CANDIDATE_DISCOVERY_TIMEOUT = 102,
};
-static const int kAudioMonitorPollFrequency = 100;
-static const int kMonitorPollFrequency = 1000;
-
-// We allow 30 seconds to establish a connection; beyond that we consider
-// it an error
+// We allow 30 seconds to establish a connection, otherwise it's an error.
static const int kCallSetupTimeout = 30 * 1000;
-// A loss of connectivity is probably due to the Internet connection going
-// down, and it might take a while to come back on wireless networks, so we
-// use a longer timeout for that.
-static const int kCallLostTimeout = 60 * 1000;
+static const int kCandidateDiscoveryTimeout = 2000;
-static const char kVideoStream[] = "video_rtp";
-static const char kAudioStream[] = "rtp";
+// TODO - These are magic string used by cricket::VideoChannel.
+// These should be moved to a common place.
+static const char kRtpVideoChannelStr[] = "video_rtp";
+static const char kRtcpVideoChannelStr[] = "video_rtcp";
-WebRtcSession::WebRtcSession(const std::string& id,
- bool incoming,
- cricket::PortAllocator* allocator,
- cricket::ChannelManager* channelmgr,
- talk_base::Thread* signaling_thread)
- : BaseSession(signaling_thread, channelmgr->worker_thread(),
- allocator, id, "", !incoming),
- transport_(NULL),
- channel_manager_(channelmgr),
- transports_writable_(false),
- muted_(false),
- camera_muted_(false),
- setup_timeout_(kCallSetupTimeout),
- signaling_thread_(signaling_thread),
- incoming_(incoming),
- port_allocator_(allocator),
- desc_factory_(channel_manager_) {
+// Constants for setting the default encoder size.
+// TODO: Implement proper negotiation of video resolution.
+static const int kDefaultVideoCodecId = 100;
+static const int kDefaultVideoCodecFramerate = 30;
+static const char kDefaultVideoCodecName[] = "VP8";
+static const int kDefaultVideoCodecWidth = 640;
+static const int kDefaultVideoCodecHeight = 480;
+
+// MediaSessionDescriptionFactory always creates one StreamParams for
+// legacy reasons even if options don't contain any streams.
+// We need to remove it in that case since here options contains all streams
+// we want to send.
+// TODO: This is placed in the wrong file. Can we solve this problem by
+// adding a method MediaSessionDescriptionFactory::set_add_legacy_streams(false)
+// to prevent it from creating legacy streams.
+static void RemoveLegacyStreams(const cricket::MediaSessionOptions& options,
+ cricket::SessionDescription* description) {
+ bool found_audio_stream = false;
+ bool found_video_stream = false;
+ for (cricket::MediaSessionOptions::Streams::const_iterator it =
+ options.streams.begin();
+ it != options.streams.end(); ++it) {
+ if (it->type == cricket::MEDIA_TYPE_AUDIO)
+ found_audio_stream = true;
+
+ if (it->type == cricket::MEDIA_TYPE_VIDEO)
+ found_video_stream = true;
+ }
+ if (found_audio_stream == false) {
+ const cricket::ContentInfo* audio_info =
+ cricket::GetFirstAudioContent(description);
+ if (audio_info) {
+ const cricket::MediaContentDescription* const_media_desc =
+ static_cast<const cricket::MediaContentDescription*>(
+ audio_info->description);
+ cricket::MediaContentDescription* media_desc =
+ const_cast<cricket::MediaContentDescription*> (const_media_desc);
+ media_desc->mutable_streams().clear();
+ }
+ }
+ if (found_video_stream == false) {
+ const cricket::ContentInfo* video_info =
+ cricket::GetFirstVideoContent(description);
+ if (video_info) {
+ const cricket::MediaContentDescription* const_media_desc =
+ static_cast<const cricket::MediaContentDescription*>(
+ video_info->description);
+ cricket::MediaContentDescription* media_desc =
+ const_cast<cricket::MediaContentDescription*> (const_media_desc);
+ media_desc->mutable_streams().clear();
+ }
+ }
+}
+
+WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
+ talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ cricket::PortAllocator* port_allocator)
+ : cricket::BaseSession(signaling_thread, worker_thread, port_allocator,
+ talk_base::ToString(talk_base::CreateRandomId()),
+ cricket::NS_JINGLE_RTP, true),
+ channel_manager_(channel_manager),
+ observer_(NULL),
+ session_desc_factory_(channel_manager),
+ offer_sent_(false) {
}
WebRtcSession::~WebRtcSession() {
- RemoveAllStreams();
- // TODO: Do we still need Terminate?
- // if (state_ != STATE_RECEIVEDTERMINATE) {
- // Terminate();
- // }
- if (transport_) {
- delete transport_;
- transport_ = NULL;
+ Terminate();
+}
+
+bool WebRtcSession::Initialize() {
+ // By default SRTP-SDES is enabled in WebRtc.
+ set_secure_policy(cricket::SEC_REQUIRED);
+
+ const cricket::VideoCodec default_codec(kDefaultVideoCodecId,
+ kDefaultVideoCodecName, kDefaultVideoCodecWidth, kDefaultVideoCodecHeight,
+ kDefaultVideoCodecFramerate, 0);
+ channel_manager_->SetDefaultVideoEncoderConfig(
+ cricket::VideoEncoderConfig(default_codec));
+
+ return CreateChannels();
+}
+
+void WebRtcSession::Terminate() {
+ if (voice_channel_.get()) {
+ channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+ }
+ if (video_channel_.get()) {
+ channel_manager_->DestroyVideoChannel(video_channel_.release());
}
}
-bool WebRtcSession::Initiate() {
- if (signaling_thread_ == NULL)
+void WebRtcSession::set_secure_policy(
+ cricket::SecureMediaPolicy secure_policy) {
+ session_desc_factory_.set_secure(secure_policy);
+}
+
+bool WebRtcSession::CreateChannels() {
+ voice_channel_.reset(channel_manager_->CreateVoiceChannel(
+ this, cricket::CN_AUDIO, true));
+ if (!voice_channel_.get()) {
+ LOG(LS_ERROR) << "Failed to create voice channel";
return false;
+ }
- transport_ = CreateTransport();
-
- if (transport_ == NULL)
+ video_channel_.reset(channel_manager_->CreateVideoChannel(
+ this, cricket::CN_VIDEO, true, voice_channel_.get()));
+ if (!video_channel_.get()) {
+ LOG(LS_ERROR) << "Failed to create video channel";
return false;
+ }
- transport_->set_allow_local_ips(true);
-
- // start transports
- transport_->SignalRequestSignaling.connect(
- this, &WebRtcSession::OnRequestSignaling);
- transport_->SignalCandidatesReady.connect(
- this, &WebRtcSession::OnCandidatesReady);
- transport_->SignalWritableState.connect(
- this, &WebRtcSession::OnWritableState);
- // Limit the amount of time that setting up a call may take.
- StartTransportTimeout(kCallSetupTimeout);
+ // TransportProxies and TransportChannels will be created when
+ // CreateVoiceChannel and CreateVideoChannel are called.
+ // Try connecting all transport channels. This is necessary to generate
+ // ICE candidates.
+ SpeculativelyConnectAllTransportChannels();
+ signaling_thread()->PostDelayed(
+ kCandidateDiscoveryTimeout, this, MSG_CANDIDATE_DISCOVERY_TIMEOUT);
return true;
}
-cricket::Transport* WebRtcSession::CreateTransport() {
+// Enabling voice and video channel.
+void WebRtcSession::EnableChannels() {
+ if (!voice_channel_->enabled())
+ voice_channel_->Enable(true);
+
+ if (!video_channel_->enabled())
+ video_channel_->Enable(true);
+}
+
+void WebRtcSession::OnTransportRequestSignaling(
+ cricket::Transport* transport) {
ASSERT(signaling_thread()->IsCurrent());
- return new cricket::P2PTransport(
- talk_base::Thread::Current(),
- channel_manager_->worker_thread(), port_allocator());
-}
-
-bool WebRtcSession::CreateVoiceChannel(const std::string& stream_id) {
- // RTCP disabled
- cricket::VoiceChannel* voice_channel =
- channel_manager_->CreateVoiceChannel(this, stream_id, true);
- if (voice_channel == NULL) {
- LOG(LERROR) << "Unable to create voice channel.";
- return false;
- }
- StreamInfo* stream_info = new StreamInfo(stream_id);
- stream_info->channel = voice_channel;
- stream_info->video = false;
- streams_.push_back(stream_info);
- return true;
-}
-
-bool WebRtcSession::CreateVideoChannel(const std::string& stream_id) {
- // RTCP disabled
- cricket::VideoChannel* video_channel =
- channel_manager_->CreateVideoChannel(this, stream_id, true, NULL);
- if (video_channel == NULL) {
- LOG(LERROR) << "Unable to create video channel.";
- return false;
- }
- StreamInfo* stream_info = new StreamInfo(stream_id);
- stream_info->channel = video_channel;
- stream_info->video = true;
- streams_.push_back(stream_info);
- return true;
-}
-
-cricket::TransportChannel* WebRtcSession::CreateChannel(
- const std::string& content_name,
- const std::string& name) {
- if (!transport_) {
- return NULL;
- }
- std::string type;
- if (content_name.compare(kVideoStream) == 0) {
- type = cricket::NS_GINGLE_VIDEO;
- } else {
- type = cricket::NS_GINGLE_AUDIO;
- }
- cricket::TransportChannel* transport_channel =
- transport_->CreateChannel(name, type);
- ASSERT(transport_channel != NULL);
- return transport_channel;
-}
-
-cricket::TransportChannel* WebRtcSession::GetChannel(
- const std::string& content_name, const std::string& name) {
- if (!transport_)
- return NULL;
-
- return transport_->GetChannel(name);
-}
-
-void WebRtcSession::DestroyChannel(
- const std::string& content_name, const std::string& name) {
- if (!transport_)
- return;
-
- transport_->DestroyChannel(name);
-}
-
-void WebRtcSession::OnMessage(talk_base::Message* message) {
- switch (message->message_id) {
- case MSG_CANDIDATE_TIMEOUT:
- if (transport_->writable()) {
- // This should never happen: The timout triggered even
- // though a call was successfully set up.
- ASSERT(false);
- }
- SignalFailedCall();
- break;
- default:
- cricket::BaseSession::OnMessage(message);
- break;
- }
-}
-
-bool WebRtcSession::Connect() {
- if (streams_.empty()) {
- // nothing to initiate
- return false;
- }
- // lets connect all the transport channels created before for this session
- transport_->ConnectChannels();
-
- // create an offer now. This is to call SetState
- // Actual offer will be send when OnCandidatesReady callback received
- cricket::SessionDescription* offer = CreateOffer();
- set_local_description(offer);
- SetState((incoming()) ? STATE_SENTACCEPT : STATE_SENTINITIATE);
-
- // Enable all the channels
- EnableAllStreams();
- SetVideoCapture(true);
- return true;
-}
-
-bool WebRtcSession::SetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer) {
- bool ret = false;
- StreamMap::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- StreamInfo* stream_info = (*iter);
- if (stream_info->stream_id.compare(stream_id) == 0) {
- ASSERT(stream_info->channel != NULL);
- ASSERT(stream_info->video);
- cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*>(
- stream_info->channel);
- ret = channel->SetRenderer(0, renderer);
- break;
- }
- }
- return ret;
-}
-
-bool WebRtcSession::SetVideoCapture(bool capture) {
- channel_manager_->SetVideoCapture(capture);
- return true;
-}
-
-bool WebRtcSession::RemoveStream(const std::string& stream_id) {
- bool ret = false;
- StreamMap::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- StreamInfo* sinfo = (*iter);
- if (sinfo->stream_id.compare(stream_id) == 0) {
- if (!sinfo->video) {
- cricket::VoiceChannel* channel = static_cast<cricket::VoiceChannel*> (
- sinfo->channel);
- channel->Enable(false);
- // Note: If later the channel is used by multiple streams, then we
- // should not destroy the channel until all the streams are removed.
- channel_manager_->DestroyVoiceChannel(channel);
- } else {
- cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*> (
- sinfo->channel);
- channel->Enable(false);
- // Note: If later the channel is used by multiple streams, then we
- // should not destroy the channel until all the streams are removed.
- channel_manager_->DestroyVideoChannel(channel);
- }
- // channel and transport will be deleted in
- // DestroyVoiceChannel/DestroyVideoChannel
- streams_.erase(iter);
- ret = true;
- break;
- }
- }
- if (!ret) {
- LOG(LERROR) << "No streams found for stream id " << stream_id;
- // TODO: trigger onError callback
- }
- return ret;
-}
-
-void WebRtcSession::EnableAllStreams() {
- StreamMap::const_iterator i;
- for (i = streams_.begin(); i != streams_.end(); ++i) {
- cricket::BaseChannel* channel = (*i)->channel;
- if (channel)
- channel->Enable(true);
- }
-}
-
-void WebRtcSession::RemoveAllStreams() {
- SetState(STATE_RECEIVEDTERMINATE);
-
- // signaling_thread_->Post(this, MSG_RTC_REMOVEALLSTREAMS);
- // First build a list of streams to remove and then remove them.
- // The reason we do this is that if we remove the streams inside the
- // loop, a stream might get removed while we're enumerating and the iterator
- // will become invalid (and we crash).
- // streams_ entry will be removed from ChannelManager callback method
- // DestroyChannel
- std::vector<std::string> streams_to_remove;
- StreamMap::iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter)
- streams_to_remove.push_back((*iter)->stream_id);
-
- for (std::vector<std::string>::iterator i = streams_to_remove.begin();
- i != streams_to_remove.end(); ++i) {
- RemoveStream(*i);
- }
-}
-
-bool WebRtcSession::HasStream(const std::string& stream_id) const {
- StreamMap::const_iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- StreamInfo* sinfo = (*iter);
- if (stream_id.compare(sinfo->stream_id) == 0) {
- return true;
- }
- }
- return false;
-}
-
-bool WebRtcSession::HasChannel(bool video) const {
- StreamMap::const_iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- StreamInfo* sinfo = (*iter);
- if (sinfo->video == video) {
- return true;
- }
- }
- return false;
-}
-
-bool WebRtcSession::HasAudioChannel() const {
- return HasChannel(false);
-}
-
-bool WebRtcSession::HasVideoChannel() const {
- return HasChannel(true);
-}
-
-void WebRtcSession::OnRequestSignaling(cricket::Transport* transport) {
transport->OnSignalingReady();
}
-void WebRtcSession::OnWritableState(cricket::Transport* transport) {
- ASSERT(transport == transport_);
- const bool transports_writable = transport_->writable();
- if (transports_writable) {
- if (transports_writable != transports_writable_) {
- signaling_thread_->Clear(this, MSG_CANDIDATE_TIMEOUT);
- } else {
- // At one point all channels were writable and we had full connectivity,
- // but then we lost it. Start the timeout again to kill the call if it
- // doesn't come back.
- StartTransportTimeout(kCallLostTimeout);
- }
- transports_writable_ = transports_writable;
+void WebRtcSession::OnTransportConnecting(cricket::Transport* transport) {
+ ASSERT(signaling_thread()->IsCurrent());
+ // start monitoring for the write state of the transport.
+ OnTransportWritable(transport);
+}
+
+void WebRtcSession::OnTransportWritable(cricket::Transport* transport) {
+ ASSERT(signaling_thread()->IsCurrent());
+ // If the transport is not in writable state, start a timer to monitor
+ // the state. If the transport doesn't become writable state in 30 seconds
+ // then we are assuming call can't be continued.
+ signaling_thread()->Clear(this, MSG_CANDIDATE_TIMEOUT);
+ if (transport->HasChannels() && !transport->writable()) {
+ signaling_thread()->PostDelayed(
+ kCallSetupTimeout, this, MSG_CANDIDATE_TIMEOUT);
}
- NotifyTransportState();
- return;
}
-void WebRtcSession::StartTransportTimeout(int timeout) {
- talk_base::Thread::Current()->PostDelayed(timeout, this,
- MSG_CANDIDATE_TIMEOUT,
- NULL);
+void WebRtcSession::OnTransportCandidatesReady(
+ cricket::Transport* transport, const cricket::Candidates& candidates) {
+ ASSERT(signaling_thread()->IsCurrent());
+ // Any new candidates after offer is sent will be dropped here.
+ if (offer_sent_)
+ return;
+ InsertTransportCandidates(candidates);
}
-void WebRtcSession::NotifyTransportState() {
+void WebRtcSession::SendCandidates() {
+ ASSERT(!local_candidates_.empty());
+ observer_->OnCandidatesReady(local_candidates_);
+ offer_sent_ = true;
}
-bool WebRtcSession::OnInitiateMessage(
- cricket::SessionDescription* offer,
- const std::vector<cricket::Candidate>& candidates) {
- if (!offer) {
- LOG(LERROR) << "No SessionDescription from peer";
+void WebRtcSession::OnTransportChannelGone(cricket::Transport* transport,
+ const std::string& name) {
+ ASSERT(signaling_thread()->IsCurrent());
+}
+
+void WebRtcSession::OnMessage(talk_base::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_CANDIDATE_TIMEOUT:
+ LOG(LS_ERROR) << "Transport is not in writable state.";
+ SignalError();
+ break;
+ case MSG_CANDIDATE_DISCOVERY_TIMEOUT:
+ SendCandidates();
+ break;
+ default:
+ break;
+ }
+}
+
+void WebRtcSession::InsertTransportCandidates(
+ const cricket::Candidates& candidates) {
+ for (cricket::Candidates::const_iterator citer = candidates.begin();
+ citer != candidates.end(); ++citer) {
+ local_candidates_.push_back(*citer);
+ }
+}
+
+bool WebRtcSession::SetCaptureDevice(const std::string& name,
+ cricket::VideoCapturer* camera) {
+ // should be called from a signaling thread
+ ASSERT(signaling_thread()->IsCurrent());
+
+ // TODO: Refactor this when there is support for multiple cameras.
+ const uint32 dummy_ssrc = 0;
+ if (!channel_manager_->SetVideoCapturer(camera, dummy_ssrc)) {
+ LOG(LS_ERROR) << "Failed to set capture device.";
return false;
}
- // Get capabilities from offer before generating an answer to it.
- cricket::MediaSessionOptions options;
- if (GetFirstAudioContent(offer))
- options.has_audio = true;
- if (GetFirstVideoContent(offer))
- options.has_video = true;
-
- talk_base::scoped_ptr<cricket::SessionDescription> answer;
- answer.reset(CreateAnswer(offer, options));
-
- if (!answer.get()) {
+ // Start the capture
+ cricket::CaptureResult ret = channel_manager_->SetVideoCapture(true);
+ if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+ LOG(LS_ERROR) << "Failed to start the capture device.";
return false;
}
- const cricket::ContentInfo* audio_content = GetFirstAudioContent(
- answer.get());
- const cricket::ContentInfo* video_content = GetFirstVideoContent(
- answer.get());
+ return true;
+}
- if (!audio_content && !video_content) {
- return false;
+void WebRtcSession::SetLocalRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) {
+ ASSERT(signaling_thread()->IsCurrent());
+ // TODO: Fix SetLocalRenderer.
+ // video_channel_->SetLocalRenderer(0, renderer);
+}
+
+void WebRtcSession::SetRemoteRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer) {
+ ASSERT(signaling_thread()->IsCurrent());
+
+ const cricket::ContentInfo* video_info =
+ cricket::GetFirstVideoContent(remote_description());
+ if (!video_info) {
+ LOG(LS_ERROR) << "Video not received in this call";
}
- bool ret = true;
- if (audio_content) {
- ret = !HasAudioChannel() &&
- CreateVoiceChannel(audio_content->name);
- if (!ret) {
- LOG(LERROR) << "Failed to create voice channel for "
- << audio_content->name;
- return false;
- }
- }
-
- if (video_content) {
- ret = !HasVideoChannel() &&
- CreateVideoChannel(video_content->name);
- if (!ret) {
- LOG(LERROR) << "Failed to create video channel for "
- << video_content->name;
- return false;
- }
- }
- // Provide remote candidates to the transport
- transport_->OnRemoteCandidates(candidates);
-
- set_remote_description(offer);
- SetState(STATE_RECEIVEDINITIATE);
-
- transport_->ConnectChannels();
- EnableAllStreams();
-
- set_local_description(answer.release());
-
- // AddStream called only once with Video label
- if (video_content) {
- SignalAddStream(video_content->name, true);
+ const cricket::MediaContentDescription* video_content =
+ static_cast<const cricket::MediaContentDescription*>(
+ video_info->description);
+ cricket::StreamParams stream;
+ if (cricket::GetStreamByNickAndName(video_content->streams(), "", name,
+ &stream)) {
+ video_channel_->SetRenderer(stream.first_ssrc(), renderer);
} else {
- SignalAddStream(audio_content->name, false);
+ // Allow that |stream| does not exist if renderer is null but assert
+ // otherwise.
+ VERIFY(renderer == NULL);
}
- SetState(STATE_SENTACCEPT);
- return true;
}
-bool WebRtcSession::OnRemoteDescription(
- cricket::SessionDescription* desc,
- const std::vector<cricket::Candidate>& candidates) {
- if (state() == STATE_SENTACCEPT ||
- state() == STATE_RECEIVEDACCEPT ||
- state() == STATE_INPROGRESS) {
- transport_->OnRemoteCandidates(candidates);
- return true;
+cricket::SessionDescription* WebRtcSession::CreateOffer(
+ const cricket::MediaSessionOptions& options) {
+ if (!options.has_video) {
+ LOG(LS_WARNING) << "To receive video, has_video flag must be set to true";
+ return NULL;
}
- // Session description is always accepted.
- set_remote_description(desc);
- SetState(STATE_RECEIVEDACCEPT);
- // Will trigger OnWritableState() if successful.
- transport_->OnRemoteCandidates(candidates);
- if (!incoming()) {
- // Trigger OnAddStream callback at the initiator
- const cricket::ContentInfo* video_content = GetFirstVideoContent(desc);
- if (video_content && !SendSignalAddStream(true)) {
- LOG(LERROR) << "Video stream unexpected in answer.";
- return false;
- } else {
- const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc);
- if (audio_content && !SendSignalAddStream(false)) {
- LOG(LERROR) << "Audio stream unexpected in answer.";
- return false;
- }
- }
- }
- return true;
-}
+ cricket::SessionDescription* offer(
+ session_desc_factory_.CreateOffer(options, local_description()));
-// Send the SignalAddStream with the stream_id based on the content type.
-bool WebRtcSession::SendSignalAddStream(bool video) {
- StreamMap::const_iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- StreamInfo* sinfo = (*iter);
- if (sinfo->video == video) {
- SignalAddStream(sinfo->stream_id, video);
- return true;
- }
- }
- return false;
-}
-
-cricket::SessionDescription* WebRtcSession::CreateOffer() {
- cricket::MediaSessionOptions options;
- options.has_audio = false; // disable default option
- StreamMap::const_iterator iter;
- for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
- if ((*iter)->video) {
- options.has_video = true;
- } else {
- options.has_audio = true;
- }
- }
- return desc_factory_.CreateOffer(options);
+ // MediaSessionDescriptionFactory always creates one StreamParams for
+ // legacy reasons even if options don't contain any streams.
+ // We need to remove it in that case since here options contains all streams
+ // we want to send.
+ RemoveLegacyStreams(options, offer);
+ return offer;
}
cricket::SessionDescription* WebRtcSession::CreateAnswer(
const cricket::SessionDescription* offer,
const cricket::MediaSessionOptions& options) {
- return desc_factory_.CreateAnswer(offer, options);
+ cricket::SessionDescription* answer(
+ session_desc_factory_.CreateAnswer(offer, options,
+ local_description()));
+ // MediaSessionDescriptionFactory always creates one StreamParams for
+ // legacy reasons even if options don't contain any streams.
+ // We need to remove it in that case since here options contains all streams
+ // we want to send.
+ RemoveLegacyStreams(options, answer);
+ return answer;
}
-void WebRtcSession::SetError(Error error) {
- BaseSession::SetError(error);
-}
-
-void WebRtcSession::OnCandidatesReady(
- cricket::Transport* transport,
- const std::vector<cricket::Candidate>& candidates) {
- std::vector<cricket::Candidate>::const_iterator iter;
- for (iter = candidates.begin(); iter != candidates.end(); ++iter) {
- local_candidates_.push_back(*iter);
+void WebRtcSession::SetLocalDescription(const cricket::SessionDescription* desc,
+ cricket::ContentAction type) {
+ if (!VERIFY((type == cricket::CA_ANSWER &&
+ state() == STATE_RECEIVEDINITIATE) ||
+ (type == cricket::CA_OFFER &&
+ (state() != STATE_RECEIVEDINITIATE &&
+ state() != STATE_SENTINITIATE)))) {
+ LOG(LS_ERROR) << "SetLocalDescription called with action in wrong state, "
+ << "action: " << type << " state: " << state();
+ return;
}
- SignalLocalDescription(local_description(), candidates);
+
+ set_local_description(desc);
+ if (type == cricket::CA_ANSWER) {
+ EnableChannels();
+ SetState(STATE_SENTACCEPT);
+ } else {
+ SetState(STATE_SENTINITIATE);
+ }
}
-} /* namespace webrtc */
+
+void WebRtcSession::SetRemoteDescription(cricket::SessionDescription* desc,
+ cricket::ContentAction type) {
+ if (!VERIFY((type == cricket::CA_ANSWER &&
+ state() == STATE_SENTINITIATE) ||
+ (type == cricket::CA_OFFER &&
+ (state() != STATE_RECEIVEDINITIATE &&
+ state() != STATE_SENTINITIATE)))) {
+ LOG(LS_ERROR) << "SetRemoteDescription called with action in wrong state, "
+ << "action: " << type << " state: " << state();
+ return;
+ }
+ set_remote_description(desc);
+
+ if (type == cricket::CA_ANSWER) {
+ EnableChannels();
+ SetState(STATE_RECEIVEDACCEPT);
+ } else {
+ SetState(STATE_RECEIVEDINITIATE);
+ }
+}
+
+void WebRtcSession::SetRemoteCandidates(
+ const cricket::Candidates& candidates) {
+ // First partition the candidates for the proxies. During creation of channels
+ // we created CN_AUDIO (audio) and CN_VIDEO (video) proxies.
+ cricket::Candidates audio_candidates;
+ cricket::Candidates video_candidates;
+ for (cricket::Candidates::const_iterator citer = candidates.begin();
+ citer != candidates.end(); ++citer) {
+ if (((*citer).name().compare(kRtpVideoChannelStr) == 0) ||
+ ((*citer).name().compare(kRtcpVideoChannelStr)) == 0) {
+ // Candidate names for video rtp and rtcp channel
+ video_candidates.push_back(*citer);
+ } else {
+ // Candidates for audio rtp and rtcp channel
+ // Channel name will be "rtp" and "rtcp"
+ audio_candidates.push_back(*citer);
+ }
+ }
+
+ // TODO: Justins comment:This is bad encapsulation, suggest we add a
+ // helper to BaseSession to allow us to
+ // pass in candidates without touching the transport proxies.
+ if (!audio_candidates.empty()) {
+ cricket::TransportProxy* audio_proxy = GetTransportProxy(cricket::CN_AUDIO);
+ if (audio_proxy) {
+ // CompleteNegotiation will set actual impl's in Proxy.
+ if (!audio_proxy->negotiated())
+ audio_proxy->CompleteNegotiation();
+ // TODO - Add a interface to TransportProxy to accept
+ // remote candidate list.
+ audio_proxy->impl()->OnRemoteCandidates(audio_candidates);
+ } else {
+ LOG(LS_INFO) << "No audio TransportProxy exists";
+ }
+ }
+
+ if (!video_candidates.empty()) {
+ cricket::TransportProxy* video_proxy = GetTransportProxy(cricket::CN_VIDEO);
+ if (video_proxy) {
+ // CompleteNegotiation will set actual impl's in Proxy.
+ if (!video_proxy->negotiated())
+ video_proxy->CompleteNegotiation();
+ // TODO - Add a interface to TransportProxy to accept
+ // remote candidate list.
+ video_proxy->impl()->OnRemoteCandidates(video_candidates);
+ } else {
+ LOG(LS_INFO) << "No video TransportProxy exists";
+ }
+ }
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
index 6338d25..3f8967e 100644
--- a/talk/app/webrtc/webrtcsession.h
+++ b/talk/app/webrtc/webrtcsession.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -28,178 +28,114 @@
#ifndef TALK_APP_WEBRTC_WEBRTCSESSION_H_
#define TALK_APP_WEBRTC_WEBRTCSESSION_H_
-#include <map>
#include <string>
#include <vector>
-#include "talk/base/logging.h"
-#include "talk/base/messagehandler.h"
-#include "talk/p2p/base/candidate.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
+#include "talk/app/webrtc/sessiondescriptionprovider.h"
+#include "talk/app/webrtc/webrtcsessionobserver.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
#include "talk/p2p/base/session.h"
-#include "talk/session/phone/channel.h"
-#include "talk/session/phone/mediachannel.h"
#include "talk/session/phone/mediasession.h"
namespace cricket {
+
class ChannelManager;
class Transport;
-class TransportChannel;
-class VoiceChannel;
+class VideoCapturer;
class VideoChannel;
-struct ConnectionInfo;
-}
+class VoiceChannel;
-namespace Json {
-class Value;
-}
+} // namespace cricket
namespace webrtc {
-typedef std::vector<cricket::AudioCodec> AudioCodecs;
-typedef std::vector<cricket::VideoCodec> VideoCodecs;
-
-class WebRtcSession : public cricket::BaseSession {
+class WebRtcSession : public cricket::BaseSession,
+ public MediaProviderInterface,
+ public SessionDescriptionProvider {
public:
- WebRtcSession(const std::string& id,
- bool incoming,
- cricket::PortAllocator* allocator,
- cricket::ChannelManager* channelmgr,
- talk_base::Thread* signaling_thread);
+ WebRtcSession(cricket::ChannelManager* channel_manager,
+ talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ cricket::PortAllocator* port_allocator);
+ virtual ~WebRtcSession();
- ~WebRtcSession();
+ bool Initialize();
- bool Initiate();
- bool Connect();
- bool OnRemoteDescription(cricket::SessionDescription* sdp,
- const std::vector<cricket::Candidate>& candidates);
- bool OnInitiateMessage(cricket::SessionDescription* sdp,
- const std::vector<cricket::Candidate>& candidates);
- bool CreateVoiceChannel(const std::string& stream_id);
- bool CreateVideoChannel(const std::string& stream_id);
- bool RemoveStream(const std::string& stream_id);
- void RemoveAllStreams();
-
- // Returns true if we have either a voice or video stream matching this label.
- bool HasStream(const std::string& label) const;
- bool HasChannel(bool video) const;
-
- // Returns true if there's one or more audio channels in the session.
- bool HasAudioChannel() const;
-
- // Returns true if there's one or more video channels in the session.
- bool HasVideoChannel() const;
-
- bool SetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer);
-
- // This signal occurs when all the streams have been removed.
- // It is triggered by a successful call to the RemoveAllStream or
- // the OnRemoteDescription with stream deleted signaling message with the
- // candidates port equal to 0.
- sigslot::signal1<WebRtcSession*> SignalRemoveStreamMessage;
-
- // This signal indicates a stream has been added properly.
- // It is triggered by a successful call to the OnInitiateMessage or
- // the OnRemoteDescription and if it's going to the STATE_RECEIVEDACCEPT.
- sigslot::signal2<const std::string&, bool> SignalAddStream;
-
- // This signal occurs when one stream is removed with the signaling
- // message from the remote peer with the candidates port equal to 0.
- sigslot::signal2<const std::string&, bool> SignalRemoveStream;
-
- // This signal occurs when the local candidate is ready
- sigslot::signal2<const cricket::SessionDescription*,
- const std::vector<cricket::Candidate>&> SignalLocalDescription;
-
- // This signal triggers when setting up or resuming a call has not been
- // successful before a certain time out.
- sigslot::signal0<> SignalFailedCall;
-
- bool muted() const { return muted_; }
- bool camera_muted() const { return camera_muted_; }
- const std::vector<cricket::Candidate>& local_candidates() {
- return local_candidates_;
+ void RegisterObserver(WebRtcSessionObserver* observer) {
+ observer_ = observer;
}
- void set_incoming(bool incoming) { incoming_ = incoming; }
- bool incoming() const { return incoming_; }
- cricket::PortAllocator* port_allocator() const { return port_allocator_; }
- talk_base::Thread* signaling_thread() const { return signaling_thread_; }
- protected:
- // methods from cricket::BaseSession
- virtual void SetError(cricket::BaseSession::Error error);
- virtual cricket::TransportChannel* CreateChannel(
- const std::string& content_name, const std::string& name);
- virtual cricket::TransportChannel* GetChannel(
- const std::string& content_name, const std::string& name);
- virtual void DestroyChannel(
- const std::string& content_name, const std::string& name);
+ const cricket::VoiceChannel* voice_channel() const {
+ return voice_channel_.get();
+ }
+ const cricket::VideoChannel* video_channel() const {
+ return video_channel_.get();
+ }
+
+ void set_secure_policy(cricket::SecureMediaPolicy secure_policy);
+ cricket::SecureMediaPolicy secure_policy() const {
+ return session_desc_factory_.secure();
+ }
+
+ // Generic error message callback from WebRtcSession.
+ // TODO - It may be necessary to supply error code as well.
+ sigslot::signal0<> SignalError;
+
+ // Implements SessionDescriptionProvider
+ virtual cricket::SessionDescription* CreateOffer(
+ const cricket::MediaSessionOptions& options);
+ virtual cricket::SessionDescription* CreateAnswer(
+ const cricket::SessionDescription* offer,
+ const cricket::MediaSessionOptions& options);
+ virtual void SetLocalDescription(const cricket::SessionDescription* desc,
+ cricket::ContentAction type);
+ virtual void SetRemoteDescription(cricket::SessionDescription* desc,
+ cricket::ContentAction type);
+ virtual void SetRemoteCandidates(
+ const std::vector<cricket::Candidate>& remote_candidates);
+ virtual const cricket::SessionDescription* local_description() const {
+ return cricket::BaseSession::local_description();
+ }
+ virtual const cricket::SessionDescription* remote_description() const {
+ return cricket::BaseSession::remote_description();
+ }
private:
- struct StreamInfo {
- explicit StreamInfo(const std::string stream_id)
- : channel(NULL),
- video(false),
- stream_id(stream_id) {}
+ // Implements MediaProviderInterface.
+ virtual bool SetCaptureDevice(const std::string& name,
+ cricket::VideoCapturer* camera);
+ virtual void SetLocalRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer);
+ virtual void SetRemoteRenderer(const std::string& name,
+ cricket::VideoRenderer* renderer);
- StreamInfo()
- : channel(NULL),
- video(false) {}
- cricket::BaseChannel* channel;
- bool video;
- std::string stream_id;
- };
- // Not really a map (vector).
- typedef std::vector<StreamInfo*> StreamMap;
+ // Transport related callbacks, override from cricket::BaseSession.
+ virtual void OnTransportRequestSignaling(cricket::Transport* transport);
+ virtual void OnTransportConnecting(cricket::Transport* transport);
+ virtual void OnTransportWritable(cricket::Transport* transport);
+ virtual void OnTransportCandidatesReady(
+ cricket::Transport* transport,
+ const cricket::Candidates& candidates);
+ virtual void OnTransportChannelGone(cricket::Transport* transport,
+ const std::string& name);
- // methods signaled by the transport
- void OnRequestSignaling(cricket::Transport* transport);
- void OnCandidatesReady(cricket::Transport* transport,
- const std::vector<cricket::Candidate>& candidates);
- void OnWritableState(cricket::Transport* transport);
- void OnTransportError(cricket::Transport* transport);
- void OnChannelGone(cricket::Transport* transport);
+ // Creates channels for voice and video.
+ bool CreateChannels();
+ void EnableChannels();
+ virtual void OnMessage(talk_base::Message* msg);
+ void InsertTransportCandidates(const cricket::Candidates& candidates);
+ void Terminate();
+ void SendCandidates();
- bool CheckForStreamDeleteMessage(
- const std::vector<cricket::Candidate>& candidates);
- void ProcessTerminateAccept(cricket::SessionDescription* desc);
-
- void UpdateTransportWritableState();
- bool CheckAllTransportsWritable();
- void StartTransportTimeout(int timeout);
- void NotifyTransportState();
-
- cricket::SessionDescription* CreateOffer();
- cricket::SessionDescription* CreateAnswer(
- const cricket::SessionDescription* answer,
- const cricket::MediaSessionOptions& options);
-
- // from MessageHandler
- virtual void OnMessage(talk_base::Message* message);
-
- virtual cricket::Transport* CreateTransport();
- cricket::Transport* GetTransport();
-
- typedef std::map<std::string, cricket::TransportChannel*> TransportChannelMap;
-
- bool SetVideoCapture(bool capture);
- void EnableAllStreams();
- bool SendSignalAddStream(bool video);
-
- cricket::Transport* transport_;
+ talk_base::scoped_ptr<cricket::VoiceChannel> voice_channel_;
+ talk_base::scoped_ptr<cricket::VideoChannel> video_channel_;
cricket::ChannelManager* channel_manager_;
- std::vector<StreamInfo*> streams_;
- TransportChannelMap transport_channels_;
- bool transports_writable_;
- bool muted_;
- bool camera_muted_;
- int setup_timeout_;
- std::vector<cricket::Candidate> local_candidates_;
-
- talk_base::Thread* signaling_thread_;
- bool incoming_;
- cricket::PortAllocator* port_allocator_;
- cricket::MediaSessionDescriptionFactory desc_factory_;
+ cricket::Candidates local_candidates_;
+ WebRtcSessionObserver* observer_;
+ cricket::MediaSessionDescriptionFactory session_desc_factory_;
+ bool offer_sent_;
};
} // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index 7a07614..9fb9b1d 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -25,383 +25,423 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <stdio.h>
-
-#include <list>
-
-#include "base/gunit.h"
-#include "base/helpers.h"
-#include "talk/app/webrtc/unittest_utilities.h"
#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/logging.h"
#include "talk/base/fakenetwork.h"
-#include "talk/base/scoped_ptr.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
#include "talk/base/thread.h"
-#include "talk/p2p/base/portallocator.h"
-#include "talk/p2p/base/sessiondescription.h"
-#include "talk/p2p/client/fakeportallocator.h"
-#include "talk/session/phone/fakesession.h"
-#include "talk/session/phone/mediasessionclient.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/stunserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediasession.h"
-class WebRtcSessionTest
- : public sigslot::has_slots<>,
- public testing::Test {
+using talk_base::SocketAddress;
+static const SocketAddress kClientAddr1("11.11.11.11", 0);
+static const SocketAddress kClientAddr2("22.22.22.22", 0);
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+
+static const char kStream1[] = "stream1";
+static const char kVideoTrack1[] = "video1";
+static const char kAudioTrack1[] = "audio1";
+
+static const char kStream2[] = "stream2";
+static const char kVideoTrack2[] = "video2";
+static const char kAudioTrack2[] = "audio2";
+
+
+class MockWebRtcSessionObserver : public webrtc::WebRtcSessionObserver {
public:
- enum CallbackId {
- kNone,
- kOnAddStream,
- kOnRemoveStream,
- kOnLocalDescription,
- kOnFailedCall,
- };
-
- WebRtcSessionTest()
- : callback_ids_(),
- last_stream_id_(""),
- last_was_video_(false),
- last_description_ptr_(NULL),
- last_candidates_(),
- session_(NULL),
- id_(),
- receiving_(false),
- allocator_(NULL),
- channel_manager_(NULL),
- worker_thread_(NULL),
- signaling_thread_(NULL) {
- }
-
- ~WebRtcSessionTest() {
- session_.reset();
- }
-
- void OnAddStream(const std::string& stream_id, bool video) {
- callback_ids_.push_back(kOnAddStream);
- last_stream_id_ = stream_id;
- last_was_video_ = video;
- }
- void OnRemoveStream(const std::string& stream_id, bool video) {
- callback_ids_.push_back(kOnRemoveStream);
- last_stream_id_ = stream_id;
- last_was_video_ = video;
- }
- void OnLocalDescription(
- const cricket::SessionDescription* desc,
+ virtual void OnCandidatesReady(
const std::vector<cricket::Candidate>& candidates) {
- callback_ids_.push_back(kOnLocalDescription);
- last_description_ptr_.reset(CopySessionDescription(desc));
- CopyCandidates(candidates, &last_candidates_);
- }
- cricket::SessionDescription* GetLocalDescription(
- std::vector<cricket::Candidate>* candidates) {
- if (last_candidates_.empty()) {
- return NULL;
+ for (cricket::Candidates::const_iterator iter = candidates.begin();
+ iter != candidates.end(); ++iter) {
+ candidates_.push_back(*iter);
+ LOG(LS_INFO) << iter->ToString();
}
- if (!last_description_ptr_.get()) {
- return NULL;
- }
- CopyCandidates(last_candidates_, candidates);
- return CopySessionDescription(last_description_ptr_.get());
}
-
- void OnFailedCall() {
- callback_ids_.push_back(kOnFailedCall);
- }
-
- CallbackId PopOldestCallback() {
- if (callback_ids_.empty()) {
- return kNone;
- }
- const CallbackId return_value = callback_ids_.front();
- callback_ids_.pop_front();
- return return_value;
- }
-
- CallbackId PeekOldestCallback() {
- if (callback_ids_.empty()) {
- return kNone;
- }
- const CallbackId return_value = callback_ids_.front();
- return return_value;
- }
-
- void Reset() {
- callback_ids_.clear();
- last_stream_id_ = "";
- last_was_video_ = false;
- last_description_ptr_.reset();
- last_candidates_.clear();
- }
-
- bool WaitForCallback(CallbackId id, int timeout_ms) {
- bool success = false;
- for (int ms = 0; ms < timeout_ms; ms++) {
- const CallbackId peek_id = PeekOldestCallback();
- if (peek_id == id) {
- PopOldestCallback();
- success = true;
- break;
- } else if (peek_id != kNone) {
- success = false;
- break;
- }
- talk_base::Thread::Current()->ProcessMessages(1);
- }
- return success;
- }
-
- bool Init(bool receiving) {
- if (signaling_thread_ != NULL)
- return false;
- signaling_thread_ = talk_base::Thread::Current();
- receiving_ = receiving;
-
- if (worker_thread_!= NULL)
- return false;
- worker_thread_ = talk_base::Thread::Current();
-
- cricket::FakePortAllocator* fake_port_allocator =
- new cricket::FakePortAllocator(worker_thread_, NULL);
-
- allocator_.reset(static_cast<cricket::PortAllocator*>(fake_port_allocator));
-
- channel_manager_.reset(new cricket::ChannelManager(worker_thread_));
- if (!channel_manager_->Init())
- return false;
-
- talk_base::CreateRandomString(8, &id_);
-
- session_.reset(new webrtc::WebRtcSession(
- id_, receiving_ , allocator_.get(),
- channel_manager_.get(),
- signaling_thread_));
- session_->SignalAddStream.connect(this, &WebRtcSessionTest::OnAddStream);
- session_->SignalRemoveStream.connect(this,
- &WebRtcSessionTest::OnRemoveStream);
- session_->SignalLocalDescription.connect(this,
- &WebRtcSessionTest::OnLocalDescription);
- session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
- return true;
- }
-
- // All session APIs must be called from the signaling thread.
- bool CallInitiate() {
- return session_->Initiate();
- }
-
- bool CallConnect() {
- if (!session_->Connect())
- return false;
- // This callback does not happen with FakeTransport!
- if (!WaitForCallback(kOnLocalDescription, 1000)) {
- return false;
- }
- return true;
- }
-
- bool CallOnRemoteDescription(
- cricket::SessionDescription* description,
- std::vector<cricket::Candidate> candidates) {
- if (!session_->OnRemoteDescription(description, candidates)) {
- return false;
- }
- if (!WaitForCallback(kOnAddStream, 1000)) {
- return false;
- }
- return true;
- }
-
- bool CallOnInitiateMessage(
- cricket::SessionDescription* description,
- const std::vector<cricket::Candidate>& candidates) {
- if (!session_->OnInitiateMessage(description, candidates)) {
- return false;
- }
- if (!WaitForCallback(kOnAddStream, 1000)) {
- return false;
- }
- return true;
- }
-
- bool CallCreateVoiceChannel(const std::string& stream_id) {
- if (!session_->CreateVoiceChannel(stream_id)) {
- return false;
- }
- return true;
- }
-
- bool CallCreateVideoChannel(const std::string& stream_id) {
- if (!session_->CreateVideoChannel(stream_id)) {
- return false;
- }
- return true;
- }
-
- bool CallRemoveStream(const std::string& stream_id) {
- return session_->RemoveStream(stream_id);
- }
-
- void CallRemoveAllStreams() {
- session_->RemoveAllStreams();
- }
-
- bool CallHasChannel(const std::string& label) {
- return session_->HasStream(label);
- }
-
- bool CallHasChannel(bool video) {
- return session_->HasChannel(video);
- }
-
- bool CallHasAudioChannel() {
- return session_->HasAudioChannel();
- }
-
- bool CallHasVideoChannel() {
- return session_->HasVideoChannel();
- }
-
- bool CallSetVideoRenderer(const std::string& stream_id,
- cricket::VideoRenderer* renderer) {
- return session_->SetVideoRenderer(stream_id, renderer);
- }
-
- const std::vector<cricket::Candidate>& CallLocalCandidates() {
- return session_->local_candidates();
- }
-
- private:
- std::list<CallbackId> callback_ids_;
-
- std::string last_stream_id_;
- bool last_was_video_;
- talk_base::scoped_ptr<cricket::SessionDescription> last_description_ptr_;
- std::vector<cricket::Candidate> last_candidates_;
-
- talk_base::scoped_ptr<webrtc::WebRtcSession> session_;
- std::string id_;
- bool receiving_;
-
- talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
-
- talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
-
- talk_base::Thread* worker_thread_;
- talk_base::Thread* signaling_thread_;
+ std::vector<cricket::Candidate> candidates_;
};
-bool CallbackReceived(WebRtcSessionTest* session, int timeout) {
- EXPECT_EQ_WAIT(WebRtcSessionTest::kNone, session->PeekOldestCallback(),
- timeout);
- const WebRtcSessionTest::CallbackId peek_id =
- session->PeekOldestCallback();
- return peek_id != WebRtcSessionTest::kNone;
-}
+class WebRtcSessionForTest : public webrtc::WebRtcSession {
+ public:
+ WebRtcSessionForTest(cricket::ChannelManager* cmgr,
+ talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ cricket::PortAllocator* port_allocator)
+ : WebRtcSession(cmgr, signaling_thread, worker_thread, port_allocator) {
+ }
+ virtual ~WebRtcSessionForTest() {}
-TEST_F(WebRtcSessionTest, InitializationReceiveSanity) {
- const bool kReceiving = true;
- ASSERT_TRUE(Init(kReceiving));
- ASSERT_TRUE(CallInitiate());
+ using webrtc::WebRtcSession::CreateOffer;
+ using webrtc::WebRtcSession::CreateAnswer;
+ using webrtc::WebRtcSession::SetLocalDescription;
+ using webrtc::WebRtcSession::SetRemoteDescription;
+ using webrtc::WebRtcSession::SetRemoteCandidates;
+};
- // Should return false because no stream has been set up yet.
- EXPECT_FALSE(CallConnect());
- const bool kVideo = true;
- EXPECT_FALSE(CallHasChannel(kVideo));
- EXPECT_FALSE(CallHasChannel(!kVideo));
-
- EXPECT_EQ(kNone, PopOldestCallback());
-}
-
-TEST_F(WebRtcSessionTest, AudioSendCallSetUp) {
- const bool kReceiving = false;
- ASSERT_TRUE(Init(kReceiving));
-
- ASSERT_TRUE(CallInitiate());
-
- ASSERT_TRUE(CallCreateVoiceChannel("Audio"));
- ASSERT_TRUE(CallConnect());
-
- std::vector<cricket::Candidate> candidates;
- cricket::SessionDescription* local_session = GetLocalDescription(
- &candidates);
- ASSERT_FALSE(candidates.empty());
- ASSERT_FALSE(local_session == NULL);
- if (!CallOnRemoteDescription(local_session, candidates)) {
- delete local_session;
- FAIL();
+class WebRtcSessionTest : public testing::Test {
+ protected:
+ // TODO Investigate why ChannelManager crashes, if it's created
+ // after stun_server.
+ WebRtcSessionTest()
+ : media_engine(new cricket::FakeMediaEngine()),
+ device_manager(new cricket::FakeDeviceManager()),
+ channel_manager_(new cricket::ChannelManager(
+ media_engine, device_manager, talk_base::Thread::Current())),
+ desc_factory_(new cricket::MediaSessionDescriptionFactory(
+ channel_manager_.get())),
+ pss_(new talk_base::PhysicalSocketServer),
+ vss_(new talk_base::VirtualSocketServer(pss_.get())),
+ fss_(new talk_base::FirewallSocketServer(vss_.get())),
+ ss_scope_(fss_.get()),
+ stun_server_(talk_base::Thread::Current(), kStunAddr),
+ allocator_(&network_manager_, kStunAddr,
+ SocketAddress(), SocketAddress(), SocketAddress()) {
+ EXPECT_TRUE(channel_manager_->Init());
}
- // All callbacks should be caught. Assert it.
- ASSERT_FALSE(CallbackReceived(this, 1000));
- ASSERT_TRUE(CallHasAudioChannel() &&
- !CallHasVideoChannel());
-}
-
-TEST_F(WebRtcSessionTest, VideoSendCallSetUp) {
- const bool kReceiving = false;
- ASSERT_TRUE(Init(kReceiving));
-
- ASSERT_TRUE(CallInitiate());
-
- ASSERT_TRUE(CallCreateVideoChannel("Video"));
- ASSERT_TRUE(CallConnect());
-
- std::vector<cricket::Candidate> candidates;
- cricket::SessionDescription* local_session = GetLocalDescription(
- &candidates);
- ASSERT_FALSE(candidates.empty());
- ASSERT_FALSE(local_session == NULL);
-
- if (!CallOnRemoteDescription(local_session, candidates)) {
- delete local_session;
- FAIL();
+ bool InitializeSession() {
+ return session_->Initialize();
}
- // All callbacks should be caught. Assert it.
- ASSERT_FALSE(CallbackReceived(this, 1000));
- ASSERT_TRUE(!CallHasAudioChannel() &&
- CallHasVideoChannel());
+ void AddInterface(const SocketAddress& addr) {
+ network_manager_.AddInterface(addr);
+ }
+
+ void Init() {
+ ASSERT_TRUE(session_.get() == NULL);
+ session_.reset(new WebRtcSessionForTest(
+ channel_manager_.get(), talk_base::Thread::Current(),
+ talk_base::Thread::Current(), &allocator_));
+ session_->RegisterObserver(&observer_);
+
+ EXPECT_TRUE(session_->Initialize());
+
+ video_channel_ = media_engine->GetVideoChannel(0);
+ voice_channel_ = media_engine->GetVoiceChannel(0);
+ }
+
+ void PopulateFakeCandidates() {
+ const int num_of_channels = 4;
+ const char* const channel_names[num_of_channels] = {
+ "rtp", "rtcp", "video_rtp", "video_rtcp"
+ };
+
+ // max 4 transport channels;
+ candidates_.clear();
+ for (int i = 0; i < num_of_channels; ++i) {
+ cricket::Candidate candidate;
+ candidate.set_name(channel_names[i]);
+ candidates_.push_back(candidate);
+ }
+ }
+
+ // Create a session description based on options. Used for testing but don't
+ // test WebRtcSession.
+ cricket::SessionDescription* CreateTestOffer(
+ const cricket::MediaSessionOptions& options) {
+ desc_factory_->set_secure(cricket::SEC_REQUIRED);
+ return desc_factory_->CreateOffer(options, NULL);
+ }
+
+ // Create a session description based on options. Used for testing but don't
+ // test WebRtcSession.
+ cricket::SessionDescription* CreateTestAnswer(
+ const cricket::SessionDescription* offer,
+ const cricket::MediaSessionOptions& options) {
+ desc_factory_->set_secure(cricket::SEC_REQUIRED);
+ return desc_factory_->CreateAnswer(offer, options, NULL);
+ }
+
+ cricket::MediaSessionOptions OptionsWithStream1() {
+ cricket::MediaSessionOptions options;
+ options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1);
+ options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1);
+ return options;
+ }
+
+ cricket::MediaSessionOptions OptionsWithStream2() {
+ cricket::MediaSessionOptions options;
+ options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2);
+ options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2);
+ return options;
+ }
+
+ cricket::MediaSessionOptions OptionsWithStream1And2() {
+ cricket::MediaSessionOptions options;
+ options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1, kStream1);
+ options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1, kStream1);
+ options.AddStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2, kStream2);
+ options.AddStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2, kStream2);
+ return options;
+ }
+
+ cricket::MediaSessionOptions OptionsReceiveOnly() {
+ cricket::MediaSessionOptions options;
+ options.has_video = true;
+ return options;
+ }
+
+ bool ChannelsExist() {
+ return (session_->voice_channel() != NULL &&
+ session_->video_channel() != NULL);
+ }
+
+ void CheckTransportChannels() {
+ EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtp") != NULL);
+ EXPECT_TRUE(session_->GetChannel(cricket::CN_AUDIO, "rtcp") != NULL);
+ EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtp") != NULL);
+ EXPECT_TRUE(session_->GetChannel(cricket::CN_VIDEO, "video_rtcp") != NULL);
+ }
+
+ void VerifyCryptoParams(const cricket::SessionDescription* sdp,
+ bool offer) {
+ ASSERT_TRUE(session_.get() != NULL);
+ const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+ ASSERT_TRUE(content != NULL);
+ const cricket::AudioContentDescription* audio_content =
+ static_cast<const cricket::AudioContentDescription*>(
+ content->description);
+ ASSERT_TRUE(audio_content != NULL);
+ if (offer) {
+ ASSERT_EQ(2U, audio_content->cryptos().size());
+ // key(40) + inline string
+ ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+ audio_content->cryptos()[0].cipher_suite);
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+ audio_content->cryptos()[1].cipher_suite);
+ ASSERT_EQ(47U, audio_content->cryptos()[1].key_params.size());
+ } else {
+ ASSERT_EQ(1U, audio_content->cryptos().size());
+ // key(40) + inline string
+ ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+ audio_content->cryptos()[0].cipher_suite);
+ }
+
+ content = cricket::GetFirstVideoContent(sdp);
+ ASSERT_TRUE(content != NULL);
+ const cricket::VideoContentDescription* video_content =
+ static_cast<const cricket::VideoContentDescription*>(
+ content->description);
+ ASSERT_TRUE(video_content != NULL);
+ ASSERT_EQ(1U, video_content->cryptos().size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+ video_content->cryptos()[0].cipher_suite);
+ ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
+ }
+
+ void VerifyNoCryptoParams(const cricket::SessionDescription* sdp) {
+ const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+ ASSERT_TRUE(content != NULL);
+ const cricket::AudioContentDescription* audio_content =
+ static_cast<const cricket::AudioContentDescription*>(
+ content->description);
+ ASSERT_TRUE(audio_content != NULL);
+ ASSERT_EQ(0U, audio_content->cryptos().size());
+
+ content = cricket::GetFirstVideoContent(sdp);
+ ASSERT_TRUE(content != NULL);
+ const cricket::VideoContentDescription* video_content =
+ static_cast<const cricket::VideoContentDescription*>(
+ content->description);
+ ASSERT_TRUE(video_content != NULL);
+ ASSERT_EQ(0U, video_content->cryptos().size());
+ }
+
+ void VerifyAnswerFromNonCryptoOffer() {
+ // Create a SDP without Crypto.
+ desc_factory_->set_secure(cricket::SEC_DISABLED);
+ cricket::MediaSessionOptions options;
+ options.has_video = true;
+ cricket::SessionDescription* offer =
+ desc_factory_->CreateOffer(options, NULL);
+ ASSERT_TRUE(offer != NULL);
+ VerifyNoCryptoParams(offer);
+ const cricket::SessionDescription* answer =
+ session_->CreateAnswer(offer, options);
+ // Answer should be NULL as no crypto params in offer.
+ ASSERT_TRUE(answer == NULL);
+ }
+
+ void VerifyAnswerFromCryptoOffer() {
+ desc_factory_->set_secure(cricket::SEC_REQUIRED);
+ cricket::MediaSessionOptions options;
+ options.has_video = true;
+ cricket::SessionDescription* offer =
+ desc_factory_->CreateOffer(options, NULL);
+ ASSERT_TRUE(offer != NULL);
+ VerifyCryptoParams(offer, true);
+ const cricket::SessionDescription* answer =
+ session_->CreateAnswer(offer, options);
+ ASSERT_TRUE(answer != NULL);
+ VerifyCryptoParams(answer, false);
+ }
+
+ cricket::FakeMediaEngine* media_engine;
+ cricket::FakeDeviceManager* device_manager;
+ talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+ talk_base::scoped_ptr<cricket::MediaSessionDescriptionFactory> desc_factory_;
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+ talk_base::scoped_ptr<talk_base::FirewallSocketServer> fss_;
+ talk_base::SocketServerScope ss_scope_;
+ cricket::TestStunServer stun_server_;
+ talk_base::FakeNetworkManager network_manager_;
+ cricket::BasicPortAllocator allocator_;
+ talk_base::scoped_ptr<WebRtcSessionForTest> session_;
+ MockWebRtcSessionObserver observer_;
+ std::vector<cricket::Candidate> candidates_;
+ cricket::FakeVideoMediaChannel* video_channel_;
+ cricket::FakeVoiceMediaChannel* voice_channel_;
+};
+
+TEST_F(WebRtcSessionTest, TestInitialize) {
+ WebRtcSessionTest::Init();
+ EXPECT_TRUE(ChannelsExist());
+ CheckTransportChannels();
}
-TEST_F(WebRtcSessionTest, AudioReceiveCallSetUp) {
- const bool kReceiving = true;
- const bool video = false;
-
- ASSERT_TRUE(Init(kReceiving));
-
- std::vector<cricket::Candidate> candidates;
- cricket::SessionDescription* local_session =
- GenerateFakeSession(video, &candidates);
- ASSERT_FALSE(candidates.empty());
- ASSERT_FALSE(local_session == NULL);
- ASSERT_TRUE(CallInitiate());
- if (!CallOnInitiateMessage(local_session, candidates)) {
- delete local_session;
- FAIL();
- }
- ASSERT_TRUE(CallConnect());
- ASSERT_FALSE(CallbackReceived(this, 1000));
-
- ASSERT_TRUE(CallHasAudioChannel() &&
- !CallHasVideoChannel());
+TEST_F(WebRtcSessionTest, TestSessionCandidates) {
+ AddInterface(kClientAddr1);
+ WebRtcSessionTest::Init();
+ EXPECT_EQ_WAIT(8u, observer_.candidates_.size(), 3000);
}
-TEST_F(WebRtcSessionTest, VideoReceiveCallSetUp) {
- const bool kReceiving = true;
- const bool video = true;
+TEST_F(WebRtcSessionTest, TestMultihomeCandidataes) {
+ AddInterface(kClientAddr1);
+ AddInterface(kClientAddr2);
+ WebRtcSessionTest::Init();
+ EXPECT_EQ_WAIT(16u, observer_.candidates_.size(), 3000);
+}
- ASSERT_TRUE(Init(kReceiving));
+TEST_F(WebRtcSessionTest, TestStunError) {
+ AddInterface(kClientAddr1);
+ AddInterface(kClientAddr2);
+ fss_->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY, kClientAddr1);
+ WebRtcSessionTest::Init();
+ // Since kClientAddr1 is blocked, not expecting stun candidates for it.
+ EXPECT_EQ_WAIT(12u, observer_.candidates_.size(), 3000);
+}
- std::vector<cricket::Candidate> candidates;
- cricket::SessionDescription* local_session =
- GenerateFakeSession(video, &candidates);
- ASSERT_FALSE(candidates.empty());
- ASSERT_FALSE(local_session == NULL);
- ASSERT_TRUE(CallInitiate());
- if (!CallOnInitiateMessage(local_session, candidates)) {
- delete local_session;
- FAIL();
- }
- ASSERT_TRUE(CallConnect());
- ASSERT_FALSE(CallbackReceived(this, 1000));
- ASSERT_TRUE(!CallHasAudioChannel() &&
- CallHasVideoChannel());
+// Test creating offers and receive answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestCreateOfferReceiveAnswer) {
+ WebRtcSessionTest::Init();
+ cricket::MediaSessionOptions options = OptionsWithStream1();
+ cricket::SessionDescription* offer = session_->CreateOffer(options);
+
+
+ cricket::MediaSessionOptions options2 = OptionsWithStream2();
+ cricket::SessionDescription* answer = CreateTestAnswer(offer, options2);
+
+
+ session_->SetLocalDescription(offer, cricket::CA_OFFER);
+ session_->SetRemoteDescription(answer, cricket::CA_ANSWER);
+
+ ASSERT_EQ(1u, video_channel_->recv_streams().size());
+ cricket::StreamParams recv_video_stream =
+ video_channel_->recv_streams()[0];
+ EXPECT_TRUE(kVideoTrack2 == recv_video_stream.name);
+
+ ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+ cricket::StreamParams recv_audio_stream =
+ voice_channel_->recv_streams()[0];
+ EXPECT_TRUE(kAudioTrack2 == recv_audio_stream.name);
+
+ ASSERT_EQ(1u, video_channel_->send_streams().size());
+ EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name);
+ ASSERT_EQ(1u, voice_channel_->send_streams().size());
+ EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name);
+
+ // Create new offer without send streams.
+ offer = session_->CreateOffer(OptionsReceiveOnly());
+ // Test with same answer.
+ session_->SetLocalDescription(offer, cricket::CA_OFFER);
+ session_->SetRemoteDescription(answer, cricket::CA_ANSWER);
+
+ EXPECT_EQ(0u, video_channel_->send_streams().size());
+ EXPECT_EQ(0u, voice_channel_->send_streams().size());
+
+ // Make sure the receive streams have not changed.
+ ASSERT_EQ(1u, video_channel_->recv_streams().size());
+ EXPECT_EQ(recv_video_stream, video_channel_->recv_streams()[0]);
+ ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+ EXPECT_EQ(recv_audio_stream, voice_channel_->recv_streams()[0]);
+}
+
+// Test receiving offers and creating answers and make sure the
+// media engine creates the expected send and receive streams.
+TEST_F(WebRtcSessionTest, TestReceiveOfferCreateAnswer) {
+ WebRtcSessionTest::Init();
+ cricket::SessionDescription* offer = CreateTestOffer(OptionsWithStream2());
+
+ cricket::MediaSessionOptions answer_options = OptionsWithStream1();
+ cricket::SessionDescription* answer =
+ session_->CreateAnswer(offer, answer_options);
+ session_->SetRemoteDescription(offer, cricket::CA_OFFER);
+ session_->SetLocalDescription(answer, cricket::CA_ANSWER);
+
+ ASSERT_EQ(1u, video_channel_->recv_streams().size());
+ EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[0].name);
+
+ ASSERT_EQ(1u, voice_channel_->recv_streams().size());
+ EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[0].name);
+
+ ASSERT_EQ(1u, video_channel_->send_streams().size());
+ EXPECT_TRUE(kVideoTrack1 == video_channel_->send_streams()[0].name);
+ ASSERT_EQ(1u, voice_channel_->send_streams().size());
+ EXPECT_TRUE(kAudioTrack1 == voice_channel_->send_streams()[0].name);
+
+ offer = CreateTestOffer(OptionsWithStream1And2());
+
+ // Answer by turning off all send streams.
+ answer = session_->CreateAnswer(offer, OptionsReceiveOnly());
+ session_->SetRemoteDescription(offer, cricket::CA_OFFER);
+ session_->SetLocalDescription(answer, cricket::CA_ANSWER);
+
+ ASSERT_EQ(2u, video_channel_->recv_streams().size());
+ EXPECT_TRUE(kVideoTrack1 == video_channel_->recv_streams()[0].name);
+ EXPECT_TRUE(kVideoTrack2 == video_channel_->recv_streams()[1].name);
+ ASSERT_EQ(2u, voice_channel_->recv_streams().size());
+ EXPECT_TRUE(kAudioTrack1 == voice_channel_->recv_streams()[0].name);
+ EXPECT_TRUE(kAudioTrack2 == voice_channel_->recv_streams()[1].name);
+
+ // Make we have no send streams.
+ EXPECT_EQ(0u, video_channel_->send_streams().size());
+ EXPECT_EQ(0u, voice_channel_->send_streams().size());
+}
+
+TEST_F(WebRtcSessionTest, TestDefaultSetSecurePolicy) {
+ WebRtcSessionTest::Init();
+ EXPECT_EQ(cricket::SEC_REQUIRED, session_->secure_policy());
+}
+
+TEST_F(WebRtcSessionTest, VerifyCryptoParamsInSDP) {
+ WebRtcSessionTest::Init();
+ VerifyCryptoParams(session_->CreateOffer(OptionsWithStream1()), true);
+}
+
+TEST_F(WebRtcSessionTest, VerifyNoCryptoParamsInSDP) {
+ WebRtcSessionTest::Init();
+ session_->set_secure_policy(cricket::SEC_DISABLED);
+ VerifyNoCryptoParams(session_->CreateOffer(OptionsWithStream1()));
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromNonCryptoOffer) {
+ WebRtcSessionTest::Init();
+ VerifyAnswerFromNonCryptoOffer();
+}
+
+TEST_F(WebRtcSessionTest, VerifyAnswerFromCryptoOffer) {
+ WebRtcSessionTest::Init();
+ VerifyAnswerFromCryptoOffer();
}
diff --git a/talk/app/webrtc/webrtcsessionobserver.h b/talk/app/webrtc/webrtcsessionobserver.h
new file mode 100644
index 0000000..c1653c9
--- /dev/null
+++ b/talk/app/webrtc/webrtcsessionobserver.h
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
+#define TALK_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
+
+#include <vector>
+
+#include "talk/p2p/base/candidate.h"
+
+namespace webrtc {
+
+class WebRtcSessionObserver {
+ public:
+ virtual void OnCandidatesReady(
+ const std::vector<cricket::Candidate>& candiddates) = 0;
+ protected:
+ virtual ~WebRtcSessionObserver() {}
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_WEBRTCSESSIONOBSERVER_H_
diff --git a/talk/app/webrtcv1/peerconnection.h b/talk/app/webrtcv1/peerconnection.h
new file mode 100644
index 0000000..c28508f
--- /dev/null
+++ b/talk/app/webrtcv1/peerconnection.h
@@ -0,0 +1,131 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_PEERCONNECTION_H_
+#define TALK_APP_WEBRTC_PEERCONNECTION_H_
+
+// TODO - Add a factory class or some kind of PeerConnection manager
+// to support multiple PeerConnection object instantiation. This class will
+// create ChannelManager object and pass it to PeerConnection object. Otherwise
+// each PeerConnection object will have its own ChannelManager hence MediaEngine
+// and VoiceEngine/VideoEngine.
+
+#include <string>
+
+namespace cricket {
+class VideoRenderer;
+}
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+class PeerConnectionObserver {
+ public:
+ // serialized signaling message
+ virtual void OnSignalingMessage(const std::string& msg) = 0;
+
+ // Triggered when a remote peer accepts a media connection.
+ virtual void OnAddStream(const std::string& stream_id, bool video) = 0;
+
+ // Triggered when a remote peer closes a media stream.
+ virtual void OnRemoveStream(const std::string& stream_id, bool video) = 0;
+
+ protected:
+ // Dtor protected as objects shouldn't be deleted via this interface.
+ virtual ~PeerConnectionObserver() {}
+};
+
+class PeerConnection {
+ public:
+ enum ReadyState {
+ NEW = 0,
+ NEGOTIATING,
+ ACTIVE,
+ CLOSED,
+ };
+
+ virtual ~PeerConnection() {}
+
+ // Register a listener
+ virtual void RegisterObserver(PeerConnectionObserver* observer) = 0;
+
+ // SignalingMessage in json format
+ virtual bool SignalingMessage(const std::string& msg) = 0;
+
+ // Asynchronously adds a local stream device to the peer
+ // connection.
+ virtual bool AddStream(const std::string& stream_id, bool video) = 0;
+
+ // Asynchronously removes a local stream device from the peer
+ // connection. The operation is complete when
+ // PeerConnectionObserver::OnRemoveStream is called.
+ virtual bool RemoveStream(const std::string& stream_id) = 0;
+
+ // Info the peerconnection that it is time to return the signaling
+ // information. The operation is complete when
+ // PeerConnectionObserver::OnSignalingMessage is called.
+ virtual bool Connect() = 0;
+
+ // Remove all the streams and tear down the session.
+ // After the Close() is called, the OnSignalingMessage will be invoked
+ // asynchronously. And before OnSignalingMessage is called,
+ // OnRemoveStream will be called for each stream that was active.
+ // TODO: Add an event such as onclose, or onreadystatechanged
+ // when the readystate reaches the closed state (no more streams in the
+ // peerconnection object.
+ virtual bool Close() = 0;
+
+ // Set the audio input & output devices based on the given device name.
+ // An empty device name means to use the default audio device.
+ virtual bool SetAudioDevice(const std::string& wave_in_device,
+ const std::string& wave_out_device,
+ int opts) = 0;
+
+ // Set the video renderer for the camera preview.
+ virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer) = 0;
+
+ // Set the video renderer for the specified stream.
+ virtual bool SetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer) = 0;
+
+ // Set video capture device
+ // For Chromium the cam_device should use the capture session id.
+ // For standalone app, cam_device is the camera name. It will try to
+ // set the default capture device when cam_device is "".
+ virtual bool SetVideoCapture(const std::string& cam_device) = 0;
+
+ // Returns the state of the PeerConnection object. See the ReadyState
+ // enum for valid values.
+ virtual ReadyState GetReadyState() = 0;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PEERCONNECTION_H_
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtcv1/peerconnectionfactory.cc
similarity index 90%
rename from talk/app/webrtc/peerconnectionfactory.cc
rename to talk/app/webrtcv1/peerconnectionfactory.cc
index 1b40c14..7bf2f89 100644
--- a/talk/app/webrtc/peerconnectionfactory.cc
+++ b/talk/app/webrtcv1/peerconnectionfactory.cc
@@ -25,9 +25,9 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "talk/app/webrtc/peerconnectionfactory.h"
+#include "talk/app/webrtcv1/peerconnectionfactory.h"
-#include "talk/app/webrtc/peerconnectionproxy.h"
+#include "talk/app/webrtcv1/peerconnectionproxy.h"
#include "talk/base/logging.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/session/phone/channelmanager.h"
@@ -35,22 +35,18 @@
namespace webrtc {
PeerConnectionFactory::PeerConnectionFactory(
- cricket::PortAllocator* port_allocator,
cricket::MediaEngineInterface* media_engine,
cricket::DeviceManagerInterface* device_manager,
talk_base::Thread* worker_thread)
: initialized_(false),
- port_allocator_(port_allocator),
channel_manager_(new cricket::ChannelManager(media_engine,
device_manager,
worker_thread)) {
}
PeerConnectionFactory::PeerConnectionFactory(
- cricket::PortAllocator* port_allocator,
talk_base::Thread* worker_thread)
: initialized_(false),
- port_allocator_(port_allocator),
channel_manager_(new cricket::ChannelManager(worker_thread)) {
}
@@ -64,11 +60,12 @@
}
PeerConnection* PeerConnectionFactory::CreatePeerConnection(
+ cricket::PortAllocator* port_allocator,
talk_base::Thread* signaling_thread) {
PeerConnectionProxy* pc = NULL;
if (initialized_) {
pc = new PeerConnectionProxy(
- port_allocator_.get(), channel_manager_.get(), signaling_thread);
+ port_allocator, channel_manager_.get(), signaling_thread);
if (!pc->Init()) {
LOG(LERROR) << "Error in initializing PeerConnection";
delete pc;
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtcv1/peerconnectionfactory.h
similarity index 85%
rename from talk/app/webrtc/peerconnectionfactory.h
rename to talk/app/webrtcv1/peerconnectionfactory.h
index 7c65e05..ea509d6 100644
--- a/talk/app/webrtc/peerconnectionfactory.h
+++ b/talk/app/webrtcv1/peerconnectionfactory.h
@@ -51,21 +51,20 @@
class PeerConnectionFactory {
public:
- PeerConnectionFactory(cricket::PortAllocator* port_allocator,
- cricket::MediaEngineInterface* media_engine,
+ PeerConnectionFactory(cricket::MediaEngineInterface* media_engine,
cricket::DeviceManagerInterface* device_manager,
talk_base::Thread* worker_thread);
- PeerConnectionFactory(cricket::PortAllocator* port_allocator,
- talk_base::Thread* worker_thread);
+ PeerConnectionFactory(talk_base::Thread* worker_thread);
virtual ~PeerConnectionFactory();
bool Initialize();
- PeerConnection* CreatePeerConnection(talk_base::Thread* signaling_thread);
+ PeerConnection* CreatePeerConnection(
+ cricket::PortAllocator* port_allocator,
+ talk_base::Thread* signaling_thread);
private:
bool initialized_;
- talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
};
diff --git a/talk/app/webrtcv1/peerconnectionimpl.cc b/talk/app/webrtcv1/peerconnectionimpl.cc
new file mode 100644
index 0000000..be67859
--- /dev/null
+++ b/talk/app/webrtcv1/peerconnectionimpl.cc
@@ -0,0 +1,225 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtcv1/peerconnectionimpl.h"
+
+#include "talk/app/webrtcv1/webrtcjson.h"
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace webrtc {
+
+
+PeerConnectionImpl::PeerConnectionImpl(
+ cricket::PortAllocator* port_allocator,
+ cricket::ChannelManager* channel_manager,
+ talk_base::Thread* signaling_thread)
+ : port_allocator_(port_allocator),
+ channel_manager_(channel_manager),
+ signaling_thread_(signaling_thread),
+ event_callback_(NULL),
+ session_(NULL) {
+}
+
+PeerConnectionImpl::~PeerConnectionImpl() {
+}
+
+bool PeerConnectionImpl::Init() {
+ std::string sid;
+ talk_base::CreateRandomString(8, &sid);
+ const bool incoming = false;
+ // default outgoing direction
+ session_.reset(CreateMediaSession(sid, incoming));
+ if (session_.get() == NULL) {
+ ASSERT(false && "failed to initialize a session");
+ return false;
+ }
+ return true;
+}
+
+void PeerConnectionImpl::RegisterObserver(PeerConnectionObserver* observer) {
+ // This assert is to catch cases where two observer pointers are registered.
+ // We only support one and if another is to be used, the current one must be
+ // cleared first.
+ ASSERT(observer == NULL || event_callback_ == NULL);
+ event_callback_ = observer;
+}
+
+bool PeerConnectionImpl::SignalingMessage(
+ const std::string& signaling_message) {
+ // Deserialize signaling message
+ cricket::SessionDescription* incoming_sdp = NULL;
+ std::vector<cricket::Candidate> candidates;
+ if (!ParseJsonSignalingMessage(signaling_message,
+ &incoming_sdp, &candidates)) {
+ return false;
+ }
+
+ bool ret = false;
+ if (GetReadyState() == NEW) {
+ // set direction to incoming, as message received first
+ session_->set_incoming(true);
+ ret = session_->OnInitiateMessage(incoming_sdp, candidates);
+ } else {
+ ret = session_->OnRemoteDescription(incoming_sdp, candidates);
+ }
+ return ret;
+}
+
+WebRtcSession* PeerConnectionImpl::CreateMediaSession(
+ const std::string& id, bool incoming) {
+ ASSERT(port_allocator_ != NULL);
+ WebRtcSession* session = new WebRtcSession(id, incoming,
+ port_allocator_, channel_manager_, signaling_thread_);
+
+ if (session->Initiate()) {
+ session->SignalAddStream.connect(
+ this,
+ &PeerConnectionImpl::OnAddStream);
+ session->SignalRemoveStream.connect(
+ this,
+ &PeerConnectionImpl::OnRemoveStream);
+ session->SignalLocalDescription.connect(
+ this,
+ &PeerConnectionImpl::OnLocalDescription);
+ session->SignalFailedCall.connect(
+ this,
+ &PeerConnectionImpl::OnFailedCall);
+ } else {
+ delete session;
+ session = NULL;
+ }
+ return session;
+}
+
+bool PeerConnectionImpl::AddStream(const std::string& stream_id, bool video) {
+ bool ret = false;
+ if (session_->HasStream(stream_id)) {
+ ASSERT(false && "A stream with this name already exists");
+ } else {
+ if (!video) {
+ ret = !session_->HasAudioChannel() &&
+ session_->CreateVoiceChannel(stream_id);
+ } else {
+ ret = !session_->HasVideoChannel() &&
+ session_->CreateVideoChannel(stream_id);
+ }
+ }
+ return ret;
+}
+
+bool PeerConnectionImpl::RemoveStream(const std::string& stream_id) {
+ return session_->RemoveStream(stream_id);
+}
+
+void PeerConnectionImpl::OnLocalDescription(
+ const cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates) {
+ if (!desc) {
+ LOG(WARNING) << "no local SDP ";
+ return;
+ }
+
+ std::string message;
+ if (GetJsonSignalingMessage(desc, candidates, &message)) {
+ if (event_callback_) {
+ event_callback_->OnSignalingMessage(message);
+ }
+ }
+}
+
+void PeerConnectionImpl::OnFailedCall() {
+ // TODO: implement.
+}
+
+bool PeerConnectionImpl::SetAudioDevice(const std::string& wave_in_device,
+ const std::string& wave_out_device,
+ int opts) {
+ return channel_manager_->SetAudioOptions(wave_in_device,
+ wave_out_device,
+ opts);
+}
+
+bool PeerConnectionImpl::SetLocalVideoRenderer(
+ cricket::VideoRenderer* renderer) {
+ return channel_manager_->SetLocalRenderer(renderer);
+}
+
+bool PeerConnectionImpl::SetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer) {
+ return session_->SetVideoRenderer(stream_id, renderer);
+}
+
+bool PeerConnectionImpl::SetVideoCapture(const std::string& cam_device) {
+ return channel_manager_->SetVideoOptions(cam_device);
+}
+
+bool PeerConnectionImpl::Connect() {
+ return session_->Connect();
+}
+
+// TODO - Close is not used anymore, should be removed.
+bool PeerConnectionImpl::Close() {
+ session_->RemoveAllStreams();
+ return true;
+}
+
+void PeerConnectionImpl::OnAddStream(const std::string& stream_id,
+ bool video) {
+ if (event_callback_) {
+ event_callback_->OnAddStream(stream_id, video);
+ }
+}
+
+void PeerConnectionImpl::OnRemoveStream(const std::string& stream_id,
+ bool video) {
+ if (event_callback_) {
+ event_callback_->OnRemoveStream(stream_id, video);
+ }
+}
+
+PeerConnectionImpl::ReadyState PeerConnectionImpl::GetReadyState() {
+ ReadyState ready_state;
+ cricket::BaseSession::State state = session_->state();
+ if (state == cricket::BaseSession::STATE_INIT) {
+ ready_state = NEW;
+ } else if (state == cricket::BaseSession::STATE_INPROGRESS) {
+ ready_state = ACTIVE;
+ } else if (state == cricket::BaseSession::STATE_DEINIT) {
+ ready_state = CLOSED;
+ } else {
+ ready_state = NEGOTIATING;
+ }
+ return ready_state;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtcv1/peerconnectionimpl.h
similarity index 63%
copy from talk/app/webrtc/peerconnectionproxy.h
copy to talk/app/webrtcv1/peerconnectionimpl.h
index e83b4ec..8cb2414 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtcv1/peerconnectionimpl.h
@@ -25,33 +25,36 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
-#define TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
#include <string>
+#include <vector>
-#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtcv1/peerconnection.h"
+#include "talk/base/sigslot.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
namespace cricket {
class ChannelManager;
class PortAllocator;
+class SessionDescription;
}
namespace webrtc {
+class WebRtcSession;
-class PeerConnectionImpl;
-
-class PeerConnectionProxy : public PeerConnection,
- public talk_base::MessageHandler {
+class PeerConnectionImpl : public PeerConnection,
+ public sigslot::has_slots<> {
public:
- PeerConnectionProxy(cricket::PortAllocator* port_allocator,
- cricket::ChannelManager* channel_manager,
- talk_base::Thread* signaling_thread);
- virtual ~PeerConnectionProxy();
+ PeerConnectionImpl(cricket::PortAllocator* port_allocator,
+ cricket::ChannelManager* channel_manager,
+ talk_base::Thread* signaling_thread);
+ virtual ~PeerConnectionImpl();
- // PeerConnection interface implementation.
+ // PeerConnection interfaces
virtual void RegisterObserver(PeerConnectionObserver* observer);
virtual bool SignalingMessage(const std::string& msg);
virtual bool AddStream(const std::string& stream_id, bool video);
@@ -66,16 +69,29 @@
virtual bool SetVideoCapture(const std::string& cam_device);
virtual ReadyState GetReadyState();
- private:
+ cricket::ChannelManager* channel_manager() {
+ return channel_manager_;
+ }
+
+ // Callbacks from PeerConnectionImplCallbacks
+ void OnAddStream(const std::string& stream_id, bool video);
+ void OnRemoveStream(const std::string& stream_id, bool video);
+ void OnLocalDescription(
+ const cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates);
+ void OnFailedCall();
bool Init();
- bool Send(uint32 id, talk_base::MessageData* data);
- virtual void OnMessage(talk_base::Message* message);
- talk_base::scoped_ptr<PeerConnectionImpl> peerconnection_impl_;
+ private:
+ WebRtcSession* CreateMediaSession(const std::string& id, bool incoming);
+
+ cricket::PortAllocator* port_allocator_;
+ cricket::ChannelManager* channel_manager_;
talk_base::Thread* signaling_thread_;
-
- friend class PeerConnectionFactory;
+ PeerConnectionObserver* event_callback_;
+ talk_base::scoped_ptr<WebRtcSession> session_;
};
-}
-#endif // TALK_APP_WEBRTC_PEERCONNECTIONPROXY_H_
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PEERCONNECTIONIMPL_H_
diff --git a/talk/app/webrtc/peerconnectionproxy.cc b/talk/app/webrtcv1/peerconnectionproxy.cc
similarity index 98%
rename from talk/app/webrtc/peerconnectionproxy.cc
rename to talk/app/webrtcv1/peerconnectionproxy.cc
index fca3ad4..3994b00 100644
--- a/talk/app/webrtc/peerconnectionproxy.cc
+++ b/talk/app/webrtcv1/peerconnectionproxy.cc
@@ -25,9 +25,9 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "talk/app/webrtc/peerconnectionproxy.h"
+#include "talk/app/webrtcv1/peerconnectionproxy.h"
-#include "talk/app/webrtc/peerconnectionimpl.h"
+#include "talk/app/webrtcv1/peerconnectionimpl.h"
#include "talk/base/logging.h"
namespace webrtc {
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtcv1/peerconnectionproxy.h
similarity index 98%
rename from talk/app/webrtc/peerconnectionproxy.h
rename to talk/app/webrtcv1/peerconnectionproxy.h
index e83b4ec..8d7fc26 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtcv1/peerconnectionproxy.h
@@ -30,7 +30,7 @@
#include <string>
-#include "talk/app/webrtc/peerconnection.h"
+#include "talk/app/webrtcv1/peerconnection.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/thread.h"
diff --git a/talk/app/webrtcv1/webrtcjson.cc b/talk/app/webrtcv1/webrtcjson.cc
new file mode 100644
index 0000000..644d76b
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcjson.cc
@@ -0,0 +1,530 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtcv1/webrtcjson.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "json/json.h"
+#else
+#include "third_party/jsoncpp/json.h"
+#endif
+
+// TODO: Remove webrtcsession.h once we can get size from signaling.
+// webrtcsession.h is for kDefaultVideoCodecWidth and kDefaultVideoCodecHeight.
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/json.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+namespace webrtc {
+static const int kIceComponent = 1;
+static const int kIceFoundation = 1;
+
+static std::vector<Json::Value> ReadValues(const Json::Value& value,
+ const std::string& key);
+
+static bool BuildMediaMessage(
+ const cricket::ContentInfo& content_info,
+ const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ Json::Value* value);
+
+static bool BuildRtpMapParams(
+ const cricket::ContentInfo& audio_offer,
+ bool video,
+ std::vector<Json::Value>* rtpmap);
+
+static void BuildCrypto(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* cryptos);
+
+
+static bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ std::vector<Json::Value>* jcandidates);
+
+static std::string Serialize(const Json::Value& value);
+static bool Deserialize(const std::string& message, Json::Value* value);
+
+static bool ParseRtcpMux(const Json::Value& value);
+static bool ParseAudioCodec(const Json::Value& value,
+ cricket::AudioContentDescription* content);
+static bool ParseVideoCodec(const Json::Value& value,
+ cricket::VideoContentDescription* content);
+static bool ParseCrypto(const Json::Value& content,
+ cricket::MediaContentDescription* desc);
+static bool ParseIceCandidates(const Json::Value& value,
+ std::vector<cricket::Candidate>* candidates);
+
+static Json::Value ReadValue(const Json::Value& value, const std::string& key);
+static std::string ReadString(const Json::Value& value, const std::string& key);
+static uint32 ReadUInt(const Json::Value& value, const std::string& key);
+
+static void Append(Json::Value* object, const std::string& key, bool value);
+static void Append(Json::Value* object, const std::string& key, int value);
+static void Append(Json::Value* object, const std::string& key,
+ const std::string& value);
+static void Append(Json::Value* object, const std::string& key, uint32 value);
+static void Append(Json::Value* object, const std::string& key,
+ const Json::Value& value);
+static void Append(Json::Value* object,
+ const std::string& key,
+ const std::vector<Json::Value>& values);
+
+bool GetJsonSignalingMessage(
+ const cricket::SessionDescription* sdp,
+ const std::vector<cricket::Candidate>& candidates,
+ std::string* signaling_message) {
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
+
+ std::vector<Json::Value> media;
+ if (audio_content) {
+ Json::Value value;
+ BuildMediaMessage(*audio_content, candidates, false, &value);
+ media.push_back(value);
+ }
+
+ if (video_content) {
+ Json::Value value;
+ BuildMediaMessage(*video_content, candidates, true, &value);
+ media.push_back(value);
+ }
+
+ Json::Value signal;
+ Append(&signal, "media", media);
+
+ // Now serialize.
+ *signaling_message = Serialize(signal);
+
+ return true;
+}
+
+bool BuildMediaMessage(
+ const cricket::ContentInfo& content_info,
+ const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ Json::Value* params) {
+ if (video) {
+ Append(params, "label", 2); // always video 2
+ } else {
+ Append(params, "label", 1); // always audio 1
+ }
+
+ const cricket::MediaContentDescription* media_info =
+ static_cast<const cricket::MediaContentDescription*> (
+ content_info.description);
+ if (media_info->rtcp_mux()) {
+ Append(params, "rtcp_mux", true);
+ }
+
+ // rtpmap
+ std::vector<Json::Value> rtpmap;
+ if (!BuildRtpMapParams(content_info, video, &rtpmap)) {
+ return false;
+ }
+ Append(params, "rtpmap", rtpmap);
+
+ // crypto
+ std::vector<Json::Value> crypto;
+ BuildCrypto(content_info, video, &crypto);
+ Append(params, "crypto", crypto);
+
+ // Candidates
+ Json::Value attributes;
+ std::vector<Json::Value> jcandidates;
+ if (!BuildAttributes(candidates, video, &jcandidates)) {
+ return false;
+ }
+ Append(&attributes, "candidate", jcandidates);
+
+ Append(params, "attributes", attributes);
+ return true;
+}
+
+bool BuildRtpMapParams(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* rtpmap) {
+ if (!video) {
+ const cricket::AudioContentDescription* audio_offer =
+ static_cast<const cricket::AudioContentDescription*>(
+ content_info.description);
+
+ std::vector<cricket::AudioCodec>::const_iterator iter =
+ audio_offer->codecs().begin();
+ std::vector<cricket::AudioCodec>::const_iterator iter_end =
+ audio_offer->codecs().end();
+ for (; iter != iter_end; ++iter) {
+ Json::Value codec;
+ std::string codec_str(std::string("audio/").append(iter->name));
+ // adding clockrate
+ Append(&codec, "clockrate", iter->clockrate);
+ Append(&codec, "codec", codec_str);
+ Json::Value codec_id;
+ Append(&codec_id, talk_base::ToString(iter->id), codec);
+ rtpmap->push_back(codec_id);
+ }
+ } else {
+ const cricket::VideoContentDescription* video_offer =
+ static_cast<const cricket::VideoContentDescription*>(
+ content_info.description);
+
+ std::vector<cricket::VideoCodec>::const_iterator iter =
+ video_offer->codecs().begin();
+ std::vector<cricket::VideoCodec>::const_iterator iter_end =
+ video_offer->codecs().end();
+ for (; iter != iter_end; ++iter) {
+ Json::Value codec;
+ std::string codec_str(std::string("video/").append(iter->name));
+ Append(&codec, "codec", codec_str);
+ Json::Value codec_id;
+ Append(&codec_id, talk_base::ToString(iter->id), codec);
+ rtpmap->push_back(codec_id);
+ }
+ }
+ return true;
+}
+
+void BuildCrypto(const cricket::ContentInfo& content_info,
+ bool video,
+ std::vector<Json::Value>* cryptos) {
+ const cricket::MediaContentDescription* content_desc =
+ static_cast<const cricket::MediaContentDescription*>(
+ content_info.description);
+ std::vector<cricket::CryptoParams>::const_iterator iter =
+ content_desc->cryptos().begin();
+ std::vector<cricket::CryptoParams>::const_iterator iter_end =
+ content_desc->cryptos().end();
+ for (; iter != iter_end; ++iter) {
+ Json::Value crypto;
+ Append(&crypto, "cipher_suite", iter->cipher_suite);
+ Append(&crypto, "key_params", iter->key_params);
+ cryptos->push_back(crypto);
+ }
+}
+
+bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+ bool video,
+ std::vector<Json::Value>* jcandidates) {
+ std::vector<cricket::Candidate>::const_iterator iter =
+ candidates.begin();
+ std::vector<cricket::Candidate>::const_iterator iter_end =
+ candidates.end();
+ for (; iter != iter_end; ++iter) {
+ if ((video && (!iter->name().compare("video_rtcp") ||
+ (!iter->name().compare("video_rtp")))) ||
+ (!video && (!iter->name().compare("rtp") ||
+ (!iter->name().compare("rtcp"))))) {
+ Json::Value candidate;
+ Append(&candidate, "component", kIceComponent);
+ Append(&candidate, "foundation", kIceFoundation);
+ Append(&candidate, "generation", iter->generation());
+ Append(&candidate, "proto", iter->protocol());
+ Append(&candidate, "priority", iter->preference_str());
+ Append(&candidate, "ip", iter->address().IPAsString());
+ Append(&candidate, "port", iter->address().PortAsString());
+ Append(&candidate, "type", iter->type());
+ Append(&candidate, "name", iter->name());
+ Append(&candidate, "network_name", iter->network_name());
+ Append(&candidate, "username", iter->username());
+ Append(&candidate, "password", iter->password());
+ jcandidates->push_back(candidate);
+ }
+ }
+ return true;
+}
+
+std::string Serialize(const Json::Value& value) {
+ Json::StyledWriter writer;
+ return writer.write(value);
+}
+
+bool Deserialize(const std::string& message, Json::Value* value) {
+ Json::Reader reader;
+ return reader.parse(message, *value);
+}
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+ cricket::SessionDescription** sdp,
+ std::vector<cricket::Candidate>* candidates) {
+ ASSERT(!(*sdp)); // expect this to be NULL
+ // first deserialize message
+ Json::Value value;
+ if (!Deserialize(signaling_message, &value)) {
+ return false;
+ }
+
+ // get media objects
+ std::vector<Json::Value> mlines = ReadValues(value, "media");
+ if (mlines.empty()) {
+ // no m-lines found
+ return false;
+ }
+
+ *sdp = new cricket::SessionDescription();
+
+ // get codec information
+ for (size_t i = 0; i < mlines.size(); ++i) {
+ if (mlines[i]["label"].asInt() == 1) {
+ cricket::AudioContentDescription* audio_content =
+ new cricket::AudioContentDescription();
+ ParseAudioCodec(mlines[i], audio_content);
+ audio_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+ audio_content->SortCodecs();
+ (*sdp)->AddContent(cricket::CN_AUDIO,
+ cricket::NS_JINGLE_RTP, audio_content);
+ // crypto
+ if (!ParseCrypto(mlines[i], audio_content))
+ return false;
+ ParseIceCandidates(mlines[i], candidates);
+ } else {
+ cricket::VideoContentDescription* video_content =
+ new cricket::VideoContentDescription();
+ ParseVideoCodec(mlines[i], video_content);
+
+ video_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+ video_content->SortCodecs();
+ (*sdp)->AddContent(cricket::CN_VIDEO,
+ cricket::NS_JINGLE_RTP, video_content);
+ if (!ParseCrypto(mlines[i], video_content))
+ return false;
+ ParseIceCandidates(mlines[i], candidates);
+ }
+ }
+ return true;
+}
+
+bool ParseRtcpMux(const Json::Value& value) {
+ Json::Value rtcp_mux(ReadValue(value, "rtcp_mux"));
+ if (!rtcp_mux.empty()) {
+ if (rtcp_mux.asBool()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ParseAudioCodec(const Json::Value& value,
+ cricket::AudioContentDescription* content) {
+ std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+ if (rtpmap.empty())
+ return false;
+
+ std::vector<Json::Value>::const_iterator iter =
+ rtpmap.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ rtpmap.end();
+ for (; iter != iter_end; ++iter) {
+ cricket::AudioCodec codec;
+ std::string pltype(iter->begin().memberName());
+ talk_base::FromString(pltype, &codec.id);
+ Json::Value codec_info((*iter)[pltype]);
+ std::string codec_name(ReadString(codec_info, "codec"));
+ std::vector<std::string> tokens;
+ talk_base::split(codec_name, '/', &tokens);
+ codec.name = tokens[1];
+ codec.clockrate = ReadUInt(codec_info, "clockrate");
+ content->AddCodec(codec);
+ }
+
+ return true;
+}
+
+bool ParseVideoCodec(const Json::Value& value,
+ cricket::VideoContentDescription* content) {
+ std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+ if (rtpmap.empty())
+ return false;
+
+ std::vector<Json::Value>::const_iterator iter =
+ rtpmap.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ rtpmap.end();
+ for (; iter != iter_end; ++iter) {
+ cricket::VideoCodec codec;
+ std::string pltype(iter->begin().memberName());
+ talk_base::FromString(pltype, &codec.id);
+ Json::Value codec_info((*iter)[pltype]);
+ std::vector<std::string> tokens;
+ talk_base::split(codec_info["codec"].asString(), '/', &tokens);
+ codec.name = tokens[1];
+ // TODO: Remove once we can get size from signaling message.
+ codec.width = WebRtcSession::kDefaultVideoCodecWidth;
+ codec.height = WebRtcSession::kDefaultVideoCodecHeight;
+ content->AddCodec(codec);
+ }
+ return true;
+}
+
+bool ParseIceCandidates(const Json::Value& value,
+ std::vector<cricket::Candidate>* candidates) {
+ Json::Value attributes(ReadValue(value, "attributes"));
+ std::string ice_pwd(ReadString(attributes, "ice-pwd"));
+ std::string ice_ufrag(ReadString(attributes, "ice-ufrag"));
+
+ std::vector<Json::Value> jcandidates(ReadValues(attributes, "candidate"));
+
+ std::vector<Json::Value>::const_iterator iter =
+ jcandidates.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ jcandidates.end();
+ for (; iter != iter_end; ++iter) {
+ cricket::Candidate cand;
+
+ unsigned int generation;
+ if (!GetUIntFromJsonObject(*iter, "generation", &generation))
+ return false;
+ cand.set_generation_str(talk_base::ToString(generation));
+
+ std::string proto;
+ if (!GetStringFromJsonObject(*iter, "proto", &proto))
+ return false;
+ cand.set_protocol(proto);
+
+ std::string priority;
+ if (!GetStringFromJsonObject(*iter, "priority", &priority))
+ return false;
+ cand.set_preference_str(priority);
+
+ std::string str;
+ talk_base::SocketAddress addr;
+ if (!GetStringFromJsonObject(*iter, "ip", &str))
+ return false;
+ addr.SetIP(str);
+ if (!GetStringFromJsonObject(*iter, "port", &str))
+ return false;
+ int port;
+ if (!talk_base::FromString(str, &port))
+ return false;
+ addr.SetPort(port);
+ cand.set_address(addr);
+
+ if (!GetStringFromJsonObject(*iter, "type", &str))
+ return false;
+ cand.set_type(str);
+
+ if (!GetStringFromJsonObject(*iter, "name", &str))
+ return false;
+ cand.set_name(str);
+
+ if (!GetStringFromJsonObject(*iter, "network_name", &str))
+ return false;
+ cand.set_network_name(str);
+
+ if (!GetStringFromJsonObject(*iter, "username", &str))
+ return false;
+ cand.set_username(str);
+
+ if (!GetStringFromJsonObject(*iter, "password", &str))
+ return false;
+ cand.set_password(str);
+
+ candidates->push_back(cand);
+ }
+ return true;
+}
+
+bool ParseCrypto(const Json::Value& content,
+ cricket::MediaContentDescription* desc) {
+ std::vector<Json::Value> jcryptos(ReadValues(content, "crypto"));
+ std::vector<Json::Value>::const_iterator iter =
+ jcryptos.begin();
+ std::vector<Json::Value>::const_iterator iter_end =
+ jcryptos.end();
+ for (; iter != iter_end; ++iter) {
+ cricket::CryptoParams crypto;
+
+ std::string cipher_suite;
+ if (!GetStringFromJsonObject(*iter, "cipher_suite", &cipher_suite))
+ return false;
+ crypto.cipher_suite = cipher_suite;
+
+ std::string key_params;
+ if (!GetStringFromJsonObject(*iter, "key_params", &key_params))
+ return false;
+ crypto.key_params= key_params;
+
+ desc->AddCrypto(crypto);
+ }
+ return true;
+}
+
+std::vector<Json::Value> ReadValues(
+ const Json::Value& value, const std::string& key) {
+ std::vector<Json::Value> objects;
+ for (Json::Value::ArrayIndex i = 0; i < value[key].size(); ++i) {
+ objects.push_back(value[key][i]);
+ }
+ return objects;
+}
+
+Json::Value ReadValue(const Json::Value& value, const std::string& key) {
+ return value[key];
+}
+
+std::string ReadString(const Json::Value& value, const std::string& key) {
+ return value[key].asString();
+}
+
+uint32 ReadUInt(const Json::Value& value, const std::string& key) {
+ return value[key].asUInt();
+}
+
+void Append(Json::Value* object, const std::string& key, bool value) {
+ (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key, int value) {
+ (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key,
+ const std::string& value) {
+ (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key, uint32 value) {
+ (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key,
+ const Json::Value& value) {
+ (*object)[key] = value;
+}
+
+void Append(Json::Value* object,
+ const std::string & key,
+ const std::vector<Json::Value>& values) {
+ for (std::vector<Json::Value>::const_iterator iter = values.begin();
+ iter != values.end(); ++iter) {
+ (*object)[key].append(*iter);
+ }
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtcv1/webrtcjson.h b/talk/app/webrtcv1/webrtcjson.h
new file mode 100644
index 0000000..d923f83
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcjson.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_WEBRTCJSON_H_
+#define TALK_APP_WEBRTC_WEBRTCJSON_H_
+
+#include <string>
+
+#include "talk/p2p/base/candidate.h"
+#include "talk/session/phone/codec.h"
+
+namespace Json {
+class Value;
+}
+
+namespace cricket {
+class AudioContentDescription;
+class VideoContentDescription;
+struct ContentInfo;
+class SessionDescription;
+}
+
+namespace webrtc {
+
+bool GetJsonSignalingMessage(
+ const cricket::SessionDescription* sdp,
+ const std::vector<cricket::Candidate>& candidates,
+ std::string* signaling_message);
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+ cricket::SessionDescription** sdp,
+ std::vector<cricket::Candidate>* candidates);
+}
+
+#endif // TALK_APP_WEBRTC_WEBRTCJSON_H_
diff --git a/talk/app/webrtcv1/webrtcsession.cc b/talk/app/webrtcv1/webrtcsession.cc
new file mode 100644
index 0000000..d30ee43
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession.cc
@@ -0,0 +1,543 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtcv1/webrtcsession.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/voicechannel.h"
+
+namespace webrtc {
+
+enum {
+ MSG_CANDIDATE_TIMEOUT = 101,
+};
+
+static const int kAudioMonitorPollFrequency = 100;
+static const int kMonitorPollFrequency = 1000;
+
+// We allow 30 seconds to establish a connection; beyond that we consider
+// it an error
+static const int kCallSetupTimeout = 30 * 1000;
+// A loss of connectivity is probably due to the Internet connection going
+// down, and it might take a while to come back on wireless networks, so we
+// use a longer timeout for that.
+static const int kCallLostTimeout = 60 * 1000;
+
+static const char kVideoStream[] = "video_rtp";
+static const char kAudioStream[] = "rtp";
+
+static const int kDefaultVideoCodecId = 100;
+static const int kDefaultVideoCodecFramerate = 30;
+static const char kDefaultVideoCodecName[] = "VP8";
+
+WebRtcSession::WebRtcSession(const std::string& id,
+ bool incoming,
+ cricket::PortAllocator* allocator,
+ cricket::ChannelManager* channelmgr,
+ talk_base::Thread* signaling_thread)
+ : BaseSession(signaling_thread, channelmgr->worker_thread(),
+ allocator, id, "", !incoming),
+ transport_(NULL),
+ channel_manager_(channelmgr),
+ transports_writable_(false),
+ muted_(false),
+ camera_muted_(false),
+ setup_timeout_(kCallSetupTimeout),
+ signaling_thread_(signaling_thread),
+ incoming_(incoming),
+ port_allocator_(allocator),
+ desc_factory_(channel_manager_) {
+}
+
+WebRtcSession::~WebRtcSession() {
+ RemoveAllStreams();
+ // TODO: Do we still need Terminate?
+ // if (state_ != STATE_RECEIVEDTERMINATE) {
+ // Terminate();
+ // }
+ if (transport_) {
+ delete transport_;
+ transport_ = NULL;
+ }
+}
+
+bool WebRtcSession::Initiate() {
+ const cricket::VideoCodec default_codec(kDefaultVideoCodecId,
+ kDefaultVideoCodecName, kDefaultVideoCodecWidth, kDefaultVideoCodecHeight,
+ kDefaultVideoCodecFramerate, 0);
+ channel_manager_->SetDefaultVideoEncoderConfig(
+ cricket::VideoEncoderConfig(default_codec));
+
+ if (signaling_thread_ == NULL)
+ return false;
+
+ transport_ = CreateTransport();
+
+ if (transport_ == NULL)
+ return false;
+
+ transport_->set_allow_local_ips(true);
+
+ // start transports
+ transport_->SignalRequestSignaling.connect(
+ this, &WebRtcSession::OnRequestSignaling);
+ transport_->SignalCandidatesReady.connect(
+ this, &WebRtcSession::OnCandidatesReady);
+ transport_->SignalWritableState.connect(
+ this, &WebRtcSession::OnWritableState);
+ // Limit the amount of time that setting up a call may take.
+ StartTransportTimeout(kCallSetupTimeout);
+ // Set default secure option to SEC_REQUIRED.
+ desc_factory_.set_secure(cricket::SEC_REQUIRED);
+ return true;
+}
+
+cricket::Transport* WebRtcSession::CreateTransport() {
+ ASSERT(signaling_thread()->IsCurrent());
+ return new cricket::P2PTransport(
+ talk_base::Thread::Current(),
+ channel_manager_->worker_thread(), port_allocator());
+}
+
+bool WebRtcSession::CreateVoiceChannel(const std::string& stream_id) {
+ // RTCP disabled
+ cricket::VoiceChannel* voice_channel =
+ channel_manager_->CreateVoiceChannel(this, stream_id, true);
+ if (voice_channel == NULL) {
+ LOG(LERROR) << "Unable to create voice channel.";
+ return false;
+ }
+ StreamInfo* stream_info = new StreamInfo(stream_id);
+ stream_info->channel = voice_channel;
+ stream_info->video = false;
+ streams_.push_back(stream_info);
+ return true;
+}
+
+bool WebRtcSession::CreateVideoChannel(const std::string& stream_id) {
+ // RTCP disabled
+ cricket::VideoChannel* video_channel =
+ channel_manager_->CreateVideoChannel(this, stream_id, true, NULL);
+ if (video_channel == NULL) {
+ LOG(LERROR) << "Unable to create video channel.";
+ return false;
+ }
+ StreamInfo* stream_info = new StreamInfo(stream_id);
+ stream_info->channel = video_channel;
+ stream_info->video = true;
+ streams_.push_back(stream_info);
+ return true;
+}
+
+cricket::TransportChannel* WebRtcSession::CreateChannel(
+ const std::string& content_name,
+ const std::string& name) {
+ if (!transport_) {
+ return NULL;
+ }
+ std::string type;
+ if (content_name.compare(kVideoStream) == 0) {
+ type = cricket::NS_GINGLE_VIDEO;
+ } else {
+ type = cricket::NS_GINGLE_AUDIO;
+ }
+ cricket::TransportChannel* transport_channel =
+ transport_->CreateChannel(name, type);
+ ASSERT(transport_channel != NULL);
+ return transport_channel;
+}
+
+cricket::TransportChannel* WebRtcSession::GetChannel(
+ const std::string& content_name, const std::string& name) {
+ if (!transport_)
+ return NULL;
+
+ return transport_->GetChannel(name);
+}
+
+void WebRtcSession::DestroyChannel(
+ const std::string& content_name, const std::string& name) {
+ if (!transport_)
+ return;
+
+ transport_->DestroyChannel(name);
+}
+
+void WebRtcSession::OnMessage(talk_base::Message* message) {
+ switch (message->message_id) {
+ case MSG_CANDIDATE_TIMEOUT:
+ if (transport_->writable()) {
+ // This should never happen: The timout triggered even
+ // though a call was successfully set up.
+ ASSERT(false);
+ }
+ SignalFailedCall();
+ break;
+ default:
+ cricket::BaseSession::OnMessage(message);
+ break;
+ }
+}
+
+bool WebRtcSession::Connect() {
+ if (streams_.empty()) {
+ // nothing to initiate
+ return false;
+ }
+ // lets connect all the transport channels created before for this session
+ transport_->ConnectChannels();
+
+ // create an offer now. This is to call SetState
+ // Actual offer will be send when OnCandidatesReady callback received
+ cricket::SessionDescription* offer = CreateOffer();
+ set_local_description(offer);
+ SetState((incoming()) ? STATE_SENTACCEPT : STATE_SENTINITIATE);
+
+ // Enable all the channels
+ EnableAllStreams();
+ return SetVideoCapture(true);
+}
+
+bool WebRtcSession::SetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer) {
+ bool ret = false;
+ StreamMap::iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ StreamInfo* stream_info = (*iter);
+ if (stream_info->stream_id.compare(stream_id) == 0) {
+ ASSERT(stream_info->channel != NULL);
+ ASSERT(stream_info->video);
+ cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*>(
+ stream_info->channel);
+ ret = channel->SetRenderer(0, renderer);
+ break;
+ }
+ }
+ return ret;
+}
+
+bool WebRtcSession::SetVideoCapture(bool capture) {
+ cricket::CaptureResult ret = channel_manager_->SetVideoCapture(capture);
+ if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+ LOG(LS_ERROR) << "Failed to SetVideoCapture(" << capture << ").";
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcSession::RemoveStream(const std::string& stream_id) {
+ bool ret = false;
+ StreamMap::iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ StreamInfo* sinfo = (*iter);
+ if (sinfo->stream_id.compare(stream_id) == 0) {
+ if (!sinfo->video) {
+ cricket::VoiceChannel* channel = static_cast<cricket::VoiceChannel*> (
+ sinfo->channel);
+ channel->Enable(false);
+ // Note: If later the channel is used by multiple streams, then we
+ // should not destroy the channel until all the streams are removed.
+ channel_manager_->DestroyVoiceChannel(channel);
+ } else {
+ cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*> (
+ sinfo->channel);
+ channel->Enable(false);
+ // Note: If later the channel is used by multiple streams, then we
+ // should not destroy the channel until all the streams are removed.
+ channel_manager_->DestroyVideoChannel(channel);
+ }
+ // channel and transport will be deleted in
+ // DestroyVoiceChannel/DestroyVideoChannel
+ streams_.erase(iter);
+ ret = true;
+ break;
+ }
+ }
+ if (!ret) {
+ LOG(LERROR) << "No streams found for stream id " << stream_id;
+ // TODO: trigger onError callback
+ }
+ return ret;
+}
+
+void WebRtcSession::EnableAllStreams() {
+ StreamMap::const_iterator i;
+ for (i = streams_.begin(); i != streams_.end(); ++i) {
+ cricket::BaseChannel* channel = (*i)->channel;
+ if (channel)
+ channel->Enable(true);
+ }
+}
+
+void WebRtcSession::RemoveAllStreams() {
+ SetState(STATE_RECEIVEDTERMINATE);
+
+ // signaling_thread_->Post(this, MSG_RTC_REMOVEALLSTREAMS);
+ // First build a list of streams to remove and then remove them.
+ // The reason we do this is that if we remove the streams inside the
+ // loop, a stream might get removed while we're enumerating and the iterator
+ // will become invalid (and we crash).
+ // streams_ entry will be removed from ChannelManager callback method
+ // DestroyChannel
+ std::vector<std::string> streams_to_remove;
+ StreamMap::iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter)
+ streams_to_remove.push_back((*iter)->stream_id);
+
+ for (std::vector<std::string>::iterator i = streams_to_remove.begin();
+ i != streams_to_remove.end(); ++i) {
+ RemoveStream(*i);
+ }
+}
+
+bool WebRtcSession::HasStream(const std::string& stream_id) const {
+ StreamMap::const_iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ StreamInfo* sinfo = (*iter);
+ if (stream_id.compare(sinfo->stream_id) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WebRtcSession::HasChannel(bool video) const {
+ StreamMap::const_iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ StreamInfo* sinfo = (*iter);
+ if (sinfo->video == video) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WebRtcSession::HasAudioChannel() const {
+ return HasChannel(false);
+}
+
+bool WebRtcSession::HasVideoChannel() const {
+ return HasChannel(true);
+}
+
+void WebRtcSession::OnRequestSignaling(cricket::Transport* transport) {
+ transport->OnSignalingReady();
+}
+
+void WebRtcSession::OnWritableState(cricket::Transport* transport) {
+ ASSERT(transport == transport_);
+ const bool transports_writable = transport_->writable();
+ if (transports_writable) {
+ if (transports_writable != transports_writable_) {
+ signaling_thread_->Clear(this, MSG_CANDIDATE_TIMEOUT);
+ } else {
+ // At one point all channels were writable and we had full connectivity,
+ // but then we lost it. Start the timeout again to kill the call if it
+ // doesn't come back.
+ StartTransportTimeout(kCallLostTimeout);
+ }
+ transports_writable_ = transports_writable;
+ }
+ NotifyTransportState();
+ return;
+}
+
+void WebRtcSession::StartTransportTimeout(int timeout) {
+ talk_base::Thread::Current()->PostDelayed(timeout, this,
+ MSG_CANDIDATE_TIMEOUT,
+ NULL);
+}
+
+void WebRtcSession::NotifyTransportState() {
+}
+
+bool WebRtcSession::OnInitiateMessage(
+ cricket::SessionDescription* offer,
+ const std::vector<cricket::Candidate>& candidates) {
+ if (!offer) {
+ LOG(LERROR) << "No SessionDescription from peer";
+ return false;
+ }
+
+ // Get capabilities from offer before generating an answer to it.
+ cricket::MediaSessionOptions options;
+ if (GetFirstAudioContent(offer))
+ options.has_audio = true;
+ if (GetFirstVideoContent(offer))
+ options.has_video = true;
+
+ talk_base::scoped_ptr<cricket::SessionDescription> answer;
+ answer.reset(CreateAnswer(offer, options));
+
+ if (!answer.get()) {
+ return false;
+ }
+
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(
+ answer.get());
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(
+ answer.get());
+
+ if (!audio_content && !video_content) {
+ return false;
+ }
+
+ bool ret = true;
+ if (audio_content) {
+ ret = !HasAudioChannel() &&
+ CreateVoiceChannel(audio_content->name);
+ if (!ret) {
+ LOG(LERROR) << "Failed to create voice channel for "
+ << audio_content->name;
+ return false;
+ }
+ }
+
+ if (video_content) {
+ ret = !HasVideoChannel() &&
+ CreateVideoChannel(video_content->name);
+ if (!ret) {
+ LOG(LERROR) << "Failed to create video channel for "
+ << video_content->name;
+ return false;
+ }
+ }
+ // Provide remote candidates to the transport
+ transport_->OnRemoteCandidates(candidates);
+
+ set_remote_description(offer);
+ SetState(STATE_RECEIVEDINITIATE);
+
+ transport_->ConnectChannels();
+ EnableAllStreams();
+ SetVideoCapture(true);
+
+ set_local_description(answer.release());
+
+ // AddStream called only once with Video label
+ if (video_content) {
+ SignalAddStream(video_content->name, true);
+ } else {
+ SignalAddStream(audio_content->name, false);
+ }
+ SetState(STATE_SENTACCEPT);
+ return true;
+}
+
+bool WebRtcSession::OnRemoteDescription(
+ cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates) {
+ if (state() == STATE_SENTACCEPT ||
+ state() == STATE_RECEIVEDACCEPT ||
+ state() == STATE_INPROGRESS) {
+ transport_->OnRemoteCandidates(candidates);
+ return true;
+ }
+ // Session description is always accepted.
+ set_remote_description(desc);
+ SetState(STATE_RECEIVEDACCEPT);
+ // Will trigger OnWritableState() if successful.
+ transport_->OnRemoteCandidates(candidates);
+
+ if (!incoming()) {
+ // Trigger OnAddStream callback at the initiator
+ const cricket::ContentInfo* video_content = GetFirstVideoContent(desc);
+ if (video_content && !SendSignalAddStream(true)) {
+ LOG(LERROR) << "Video stream unexpected in answer.";
+ return false;
+ } else {
+ const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc);
+ if (audio_content && !SendSignalAddStream(false)) {
+ LOG(LERROR) << "Audio stream unexpected in answer.";
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Send the SignalAddStream with the stream_id based on the content type.
+bool WebRtcSession::SendSignalAddStream(bool video) {
+ StreamMap::const_iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ StreamInfo* sinfo = (*iter);
+ if (sinfo->video == video) {
+ SignalAddStream(sinfo->stream_id, video);
+ return true;
+ }
+ }
+ return false;
+}
+
+cricket::SessionDescription* WebRtcSession::CreateOffer() {
+ cricket::MediaSessionOptions options;
+ options.has_audio = false; // disable default option
+ StreamMap::const_iterator iter;
+ for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+ if ((*iter)->video) {
+ options.has_video = true;
+ } else {
+ options.has_audio = true;
+ }
+ }
+ // We didn't save the previous offer.
+ const cricket::SessionDescription* previous_offer = NULL;
+ return desc_factory_.CreateOffer(options, previous_offer);
+}
+
+cricket::SessionDescription* WebRtcSession::CreateAnswer(
+ const cricket::SessionDescription* offer,
+ const cricket::MediaSessionOptions& options) {
+ // We didn't save the previous answer.
+ const cricket::SessionDescription* previous_answer = NULL;
+ return desc_factory_.CreateAnswer(offer, options, previous_answer);
+}
+
+void WebRtcSession::SetError(Error error) {
+ BaseSession::SetError(error);
+}
+
+void WebRtcSession::OnCandidatesReady(
+ cricket::Transport* transport,
+ const std::vector<cricket::Candidate>& candidates) {
+ std::vector<cricket::Candidate>::const_iterator iter;
+ for (iter = candidates.begin(); iter != candidates.end(); ++iter) {
+ local_candidates_.push_back(*iter);
+ }
+ SignalLocalDescription(local_description(), candidates);
+}
+} /* namespace webrtc */
diff --git a/talk/app/webrtcv1/webrtcsession.h b/talk/app/webrtcv1/webrtcsession.h
new file mode 100644
index 0000000..8f6c6ad
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession.h
@@ -0,0 +1,210 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_WEBRTCSESSION_H_
+#define TALK_APP_WEBRTC_WEBRTCSESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediasession.h"
+
+namespace cricket {
+class ChannelManager;
+class Transport;
+class TransportChannel;
+class VoiceChannel;
+class VideoChannel;
+struct ConnectionInfo;
+}
+
+namespace Json {
+class Value;
+}
+
+namespace webrtc {
+
+typedef std::vector<cricket::AudioCodec> AudioCodecs;
+typedef std::vector<cricket::VideoCodec> VideoCodecs;
+
+class WebRtcSession : public cricket::BaseSession {
+ public:
+ WebRtcSession(const std::string& id,
+ bool incoming,
+ cricket::PortAllocator* allocator,
+ cricket::ChannelManager* channelmgr,
+ talk_base::Thread* signaling_thread);
+
+ ~WebRtcSession();
+
+ bool Initiate();
+ bool Connect();
+ bool OnRemoteDescription(cricket::SessionDescription* sdp,
+ const std::vector<cricket::Candidate>& candidates);
+ bool OnInitiateMessage(cricket::SessionDescription* sdp,
+ const std::vector<cricket::Candidate>& candidates);
+ bool CreateVoiceChannel(const std::string& stream_id);
+ bool CreateVideoChannel(const std::string& stream_id);
+ bool RemoveStream(const std::string& stream_id);
+ void RemoveAllStreams();
+
+ // Returns true if we have either a voice or video stream matching this label.
+ bool HasStream(const std::string& label) const;
+ bool HasChannel(bool video) const;
+
+ // Returns true if there's one or more audio channels in the session.
+ bool HasAudioChannel() const;
+
+ // Returns true if there's one or more video channels in the session.
+ bool HasVideoChannel() const;
+
+ bool SetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer);
+
+ // This signal occurs when all the streams have been removed.
+ // It is triggered by a successful call to the RemoveAllStream or
+ // the OnRemoteDescription with stream deleted signaling message with the
+ // candidates port equal to 0.
+ sigslot::signal1<WebRtcSession*> SignalRemoveStreamMessage;
+
+ // This signal indicates a stream has been added properly.
+ // It is triggered by a successful call to the OnInitiateMessage or
+ // the OnRemoteDescription and if it's going to the STATE_RECEIVEDACCEPT.
+ sigslot::signal2<const std::string&, bool> SignalAddStream;
+
+ // This signal occurs when one stream is removed with the signaling
+ // message from the remote peer with the candidates port equal to 0.
+ sigslot::signal2<const std::string&, bool> SignalRemoveStream;
+
+ // This signal occurs when the local candidate is ready
+ sigslot::signal2<const cricket::SessionDescription*,
+ const std::vector<cricket::Candidate>&> SignalLocalDescription;
+
+ // This signal triggers when setting up or resuming a call has not been
+ // successful before a certain time out.
+ sigslot::signal0<> SignalFailedCall;
+
+ bool muted() const { return muted_; }
+ bool camera_muted() const { return camera_muted_; }
+ const std::vector<cricket::Candidate>& local_candidates() {
+ return local_candidates_;
+ }
+ void set_incoming(bool incoming) { incoming_ = incoming; }
+ bool incoming() const { return incoming_; }
+ cricket::PortAllocator* port_allocator() const { return port_allocator_; }
+ talk_base::Thread* signaling_thread() const { return signaling_thread_; }
+
+ static const int kDefaultVideoCodecWidth = 640;
+ static const int kDefaultVideoCodecHeight = 480;
+
+ protected:
+ // methods from cricket::BaseSession
+ virtual void SetError(cricket::BaseSession::Error error);
+ virtual cricket::TransportChannel* CreateChannel(
+ const std::string& content_name, const std::string& name);
+ virtual cricket::TransportChannel* GetChannel(
+ const std::string& content_name, const std::string& name);
+ virtual void DestroyChannel(
+ const std::string& content_name, const std::string& name);
+
+ private:
+ struct StreamInfo {
+ explicit StreamInfo(const std::string stream_id)
+ : channel(NULL),
+ video(false),
+ stream_id(stream_id) {}
+
+ StreamInfo()
+ : channel(NULL),
+ video(false) {}
+ cricket::BaseChannel* channel;
+ bool video;
+ std::string stream_id;
+ };
+ // Not really a map (vector).
+ typedef std::vector<StreamInfo*> StreamMap;
+
+ // methods signaled by the transport
+ void OnRequestSignaling(cricket::Transport* transport);
+ void OnCandidatesReady(cricket::Transport* transport,
+ const std::vector<cricket::Candidate>& candidates);
+ void OnWritableState(cricket::Transport* transport);
+ void OnTransportError(cricket::Transport* transport);
+ void OnChannelGone(cricket::Transport* transport);
+
+ bool CheckForStreamDeleteMessage(
+ const std::vector<cricket::Candidate>& candidates);
+ void ProcessTerminateAccept(cricket::SessionDescription* desc);
+
+ void UpdateTransportWritableState();
+ bool CheckAllTransportsWritable();
+ void StartTransportTimeout(int timeout);
+ void NotifyTransportState();
+
+ cricket::SessionDescription* CreateOffer();
+ cricket::SessionDescription* CreateAnswer(
+ const cricket::SessionDescription* answer,
+ const cricket::MediaSessionOptions& options);
+
+ // from MessageHandler
+ virtual void OnMessage(talk_base::Message* message);
+
+ virtual cricket::Transport* CreateTransport();
+ cricket::Transport* GetTransport();
+
+ typedef std::map<std::string, cricket::TransportChannel*> TransportChannelMap;
+
+ bool SetVideoCapture(bool capture);
+ void EnableAllStreams();
+ bool SendSignalAddStream(bool video);
+
+ cricket::Transport* transport_;
+ cricket::ChannelManager* channel_manager_;
+ std::vector<StreamInfo*> streams_;
+ TransportChannelMap transport_channels_;
+ bool transports_writable_;
+ bool muted_;
+ bool camera_muted_;
+ int setup_timeout_;
+ std::vector<cricket::Candidate> local_candidates_;
+
+ talk_base::Thread* signaling_thread_;
+ bool incoming_;
+ cricket::PortAllocator* port_allocator_;
+ cricket::MediaSessionDescriptionFactory desc_factory_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_WEBRTCSESSION_H_
diff --git a/talk/app/webrtcv1/webrtcsession_unittest.cc b/talk/app/webrtcv1/webrtcsession_unittest.cc
new file mode 100644
index 0000000..6f27fb4
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcsession_unittest.cc
@@ -0,0 +1,475 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include <list>
+
+#include "talk/app/webrtcv1/unittest_utilities.h"
+#include "talk/app/webrtcv1/webrtcsession.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/client/fakeportallocator.h"
+#include "talk/session/phone/dummydevicemanager.h"
+#include "talk/session/phone/fakewebrtcvcmfactory.h"
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/webrtcmediaengine.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+class WebRtcSessionTest
+ : public sigslot::has_slots<>,
+ public testing::Test {
+ public:
+ enum CallbackId {
+ kNone,
+ kOnAddStream,
+ kOnRemoveStream,
+ kOnLocalDescription,
+ kOnFailedCall,
+ };
+
+ WebRtcSessionTest()
+ : last_was_video_(false),
+ last_description_ptr_(NULL),
+ session_(NULL),
+ receiving_(false),
+ allocator_(NULL),
+ channel_manager_(NULL),
+ video_capturer_(NULL),
+ worker_thread_(NULL),
+ signaling_thread_(NULL) {
+ }
+
+ ~WebRtcSessionTest() {
+ session_.reset();
+ // Ensure the VideoCapturer be unregistered before destroyed.
+ channel_manager_->SetVideoCapturer(NULL, 0);
+ }
+
+ void OnAddStream(const std::string& stream_id, bool video) {
+ callback_ids_.push_back(kOnAddStream);
+ last_stream_id_ = stream_id;
+ last_was_video_ = video;
+ }
+ void OnRemoveStream(const std::string& stream_id, bool video) {
+ callback_ids_.push_back(kOnRemoveStream);
+ last_stream_id_ = stream_id;
+ last_was_video_ = video;
+ }
+ void OnLocalDescription(
+ const cricket::SessionDescription* desc,
+ const std::vector<cricket::Candidate>& candidates) {
+ callback_ids_.push_back(kOnLocalDescription);
+ last_description_ptr_.reset(CopySessionDescription(desc));
+ CopyCandidates(candidates, &last_candidates_);
+ }
+ cricket::SessionDescription* GetLocalDescription(
+ std::vector<cricket::Candidate>* candidates) {
+ if (last_candidates_.empty()) {
+ return NULL;
+ }
+ if (!last_description_ptr_.get()) {
+ return NULL;
+ }
+ CopyCandidates(last_candidates_, candidates);
+ return CopySessionDescription(last_description_ptr_.get());
+ }
+
+ void OnFailedCall() {
+ callback_ids_.push_back(kOnFailedCall);
+ }
+
+ CallbackId PopOldestCallback() {
+ if (callback_ids_.empty()) {
+ return kNone;
+ }
+ const CallbackId return_value = callback_ids_.front();
+ callback_ids_.pop_front();
+ return return_value;
+ }
+
+ CallbackId PeekOldestCallback() {
+ if (callback_ids_.empty()) {
+ return kNone;
+ }
+ const CallbackId return_value = callback_ids_.front();
+ return return_value;
+ }
+
+ void Reset() {
+ callback_ids_.clear();
+ last_stream_id_ = "";
+ last_was_video_ = false;
+ last_description_ptr_.reset();
+ last_candidates_.clear();
+ }
+
+ bool WaitForCallback(CallbackId id, int timeout_ms) {
+ bool success = false;
+ for (int ms = 0; ms < timeout_ms; ms++) {
+ const CallbackId peek_id = PeekOldestCallback();
+ if (peek_id == id) {
+ PopOldestCallback();
+ success = true;
+ break;
+ } else if (peek_id != kNone) {
+ success = false;
+ break;
+ }
+ talk_base::Thread::Current()->ProcessMessages(1);
+ }
+ return success;
+ }
+
+ bool Init(bool receiving) {
+ if (signaling_thread_ != NULL)
+ return false;
+ signaling_thread_ = talk_base::Thread::Current();
+ receiving_ = receiving;
+
+ if (worker_thread_!= NULL)
+ return false;
+ worker_thread_ = talk_base::Thread::Current();
+
+ cricket::FakePortAllocator* fake_port_allocator =
+ new cricket::FakePortAllocator(worker_thread_, NULL);
+
+ allocator_.reset(static_cast<cricket::PortAllocator*>(fake_port_allocator));
+
+ cricket::DummyDeviceManager* device_manager(
+ new cricket::DummyDeviceManager());
+ cricket::WebRtcMediaEngine* webrtc_media_engine(
+ new cricket::WebRtcMediaEngine(NULL, NULL, NULL));
+ channel_manager_.reset(new cricket::ChannelManager(webrtc_media_engine,
+ device_manager,
+ worker_thread_));
+ if (!channel_manager_->Init())
+ return false;
+
+ FakeWebRtcVideoCaptureModule* vcm =
+ new FakeWebRtcVideoCaptureModule(NULL, 123);
+ video_capturer_.reset(new cricket::WebRtcVideoCapturer);
+ if (!video_capturer_->Init(vcm)) {
+ return false;
+ }
+ // The SetVideoCapturer call doesn't transfer ownership.
+ if (!channel_manager_->SetVideoCapturer(video_capturer_.get(), 0))
+ return false;
+
+ talk_base::CreateRandomString(8, &id_);
+
+ session_.reset(new webrtc::WebRtcSession(
+ id_, receiving_ , allocator_.get(),
+ channel_manager_.get(),
+ signaling_thread_));
+ session_->SignalAddStream.connect(this, &WebRtcSessionTest::OnAddStream);
+ session_->SignalRemoveStream.connect(this,
+ &WebRtcSessionTest::OnRemoveStream);
+ session_->SignalLocalDescription.connect(this,
+ &WebRtcSessionTest::OnLocalDescription);
+ session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
+ return true;
+ }
+
+ // All session APIs must be called from the signaling thread.
+ bool CallInitiate() {
+ return session_->Initiate();
+ }
+
+ bool CallConnect() {
+ if (!session_->Connect())
+ return false;
+ // This callback does not happen with FakeTransport!
+ if (!WaitForCallback(kOnLocalDescription, 1000)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool CallOnRemoteDescription(
+ cricket::SessionDescription* description,
+ std::vector<cricket::Candidate> candidates) {
+ if (!session_->OnRemoteDescription(description, candidates)) {
+ return false;
+ }
+ if (!WaitForCallback(kOnAddStream, 1000)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool CallOnInitiateMessage(
+ cricket::SessionDescription* description,
+ const std::vector<cricket::Candidate>& candidates) {
+ if (!session_->OnInitiateMessage(description, candidates)) {
+ return false;
+ }
+ if (!WaitForCallback(kOnAddStream, 1000)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool CallCreateVoiceChannel(const std::string& stream_id) {
+ if (!session_->CreateVoiceChannel(stream_id)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool CallCreateVideoChannel(const std::string& stream_id) {
+ if (!session_->CreateVideoChannel(stream_id)) {
+ return false;
+ }
+ return true;
+ }
+
+ bool CallRemoveStream(const std::string& stream_id) {
+ return session_->RemoveStream(stream_id);
+ }
+
+ void CallRemoveAllStreams() {
+ session_->RemoveAllStreams();
+ }
+
+ bool CallHasChannel(const std::string& label) {
+ return session_->HasStream(label);
+ }
+
+ bool CallHasChannel(bool video) {
+ return session_->HasChannel(video);
+ }
+
+ bool CallHasAudioChannel() {
+ return session_->HasAudioChannel();
+ }
+
+ bool CallHasVideoChannel() {
+ return session_->HasVideoChannel();
+ }
+
+ bool CallSetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer) {
+ return session_->SetVideoRenderer(stream_id, renderer);
+ }
+
+ const std::vector<cricket::Candidate>& CallLocalCandidates() {
+ return session_->local_candidates();
+ }
+ cricket::ChannelManager* channel_manager() const {
+ return channel_manager_.get();
+ }
+ const cricket::SessionDescription* local_description() const {
+ return session_->local_description();
+ }
+ cricket::SessionDescription* remote_description() const {
+ return session_->remote_description();
+ }
+
+ void VerifyCryptoParams(const cricket::SessionDescription* sdp,
+ bool offer) {
+ const cricket::ContentInfo* content = cricket::GetFirstAudioContent(sdp);
+ if (content) {
+ const cricket::AudioContentDescription* audio_content =
+ static_cast<const cricket::AudioContentDescription*>(
+ content->description);
+ ASSERT_TRUE(audio_content != NULL);
+ ASSERT_EQ(offer ? 2U : 1U, audio_content->cryptos().size());
+ // key(40) + inline string
+ ASSERT_EQ(47U, audio_content->cryptos()[0].key_params.size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32",
+ audio_content->cryptos()[0].cipher_suite);
+ if (offer) {
+ ASSERT_EQ(47U, audio_content->cryptos()[1].key_params.size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+ audio_content->cryptos()[1].cipher_suite);
+ }
+ }
+ content = cricket::GetFirstVideoContent(sdp);
+ if (content) {
+ ASSERT_TRUE(content != NULL);
+ const cricket::VideoContentDescription* video_content =
+ static_cast<const cricket::VideoContentDescription*>(
+ content->description);
+ ASSERT_TRUE(video_content != NULL);
+ ASSERT_EQ(1U, video_content->cryptos().size());
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80",
+ video_content->cryptos()[0].cipher_suite);
+ ASSERT_EQ(47U, video_content->cryptos()[0].key_params.size());
+ }
+ }
+
+ private:
+ std::list<CallbackId> callback_ids_;
+
+ std::string last_stream_id_;
+ bool last_was_video_;
+ talk_base::scoped_ptr<cricket::SessionDescription> last_description_ptr_;
+ std::vector<cricket::Candidate> last_candidates_;
+
+ talk_base::scoped_ptr<webrtc::WebRtcSession> session_;
+ std::string id_;
+ bool receiving_;
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
+
+ talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+ talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> video_capturer_;
+
+ talk_base::Thread* worker_thread_;
+ talk_base::Thread* signaling_thread_;
+};
+
+bool CallbackReceived(WebRtcSessionTest* session, int timeout) {
+ EXPECT_EQ_WAIT(WebRtcSessionTest::kNone, session->PeekOldestCallback(),
+ timeout);
+ const WebRtcSessionTest::CallbackId peek_id =
+ session->PeekOldestCallback();
+ return peek_id != WebRtcSessionTest::kNone;
+}
+
+TEST_F(WebRtcSessionTest, InitializationReceiveSanity) {
+ const bool kReceiving = true;
+ ASSERT_TRUE(Init(kReceiving));
+ ASSERT_TRUE(CallInitiate());
+
+ // Should return false because no stream has been set up yet.
+ EXPECT_FALSE(CallConnect());
+ const bool kVideo = true;
+ EXPECT_FALSE(CallHasChannel(kVideo));
+ EXPECT_FALSE(CallHasChannel(!kVideo));
+
+ EXPECT_EQ(kNone, PopOldestCallback());
+}
+
+TEST_F(WebRtcSessionTest, AudioSendCallSetUp) {
+ const bool kReceiving = false;
+ ASSERT_TRUE(Init(kReceiving));
+
+ ASSERT_TRUE(CallInitiate());
+
+ ASSERT_TRUE(CallCreateVoiceChannel("Audio"));
+ ASSERT_TRUE(CallConnect());
+
+ std::vector<cricket::Candidate> candidates;
+ cricket::SessionDescription* local_session = GetLocalDescription(
+ &candidates);
+ ASSERT_FALSE(candidates.empty());
+ ASSERT_FALSE(local_session == NULL);
+ if (!CallOnRemoteDescription(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
+
+ // All callbacks should be caught. Assert it.
+ ASSERT_FALSE(CallbackReceived(this, 1000));
+ ASSERT_TRUE(CallHasAudioChannel() &&
+ !CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, VideoSendCallSetUp) {
+ const bool kReceiving = false;
+ ASSERT_TRUE(Init(kReceiving));
+
+ ASSERT_TRUE(CallInitiate());
+
+ ASSERT_TRUE(CallCreateVideoChannel("Video"));
+ ASSERT_TRUE(CallConnect());
+
+ std::vector<cricket::Candidate> candidates;
+ cricket::SessionDescription* local_session = GetLocalDescription(
+ &candidates);
+ ASSERT_FALSE(candidates.empty());
+ ASSERT_FALSE(local_session == NULL);
+
+ if (!CallOnRemoteDescription(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
+
+ // All callbacks should be caught. Assert it.
+ ASSERT_FALSE(CallbackReceived(this, 1000));
+ ASSERT_TRUE(!CallHasAudioChannel() &&
+ CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, AudioReceiveCallSetUp) {
+ const bool kReceiving = true;
+ const bool video = false;
+
+ ASSERT_TRUE(Init(kReceiving));
+
+ std::vector<cricket::Candidate> candidates;
+ cricket::SessionDescription* local_session =
+ GenerateFakeSession(channel_manager(), video, &candidates);
+ ASSERT_FALSE(candidates.empty());
+ ASSERT_FALSE(local_session == NULL);
+ ASSERT_TRUE(CallInitiate());
+ if (!CallOnInitiateMessage(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
+
+ ASSERT_FALSE(CallbackReceived(this, 1000));
+ ASSERT_TRUE(CallHasAudioChannel() &&
+ !CallHasVideoChannel());
+ // Incoming call - local desc has the answer.
+ VerifyCryptoParams(local_description(), false);
+ VerifyCryptoParams(remote_description(), true);
+}
+
+TEST_F(WebRtcSessionTest, VideoReceiveCallSetUp) {
+ const bool kReceiving = true;
+ const bool video = true;
+
+ ASSERT_TRUE(Init(kReceiving));
+
+ std::vector<cricket::Candidate> candidates;
+ cricket::SessionDescription* local_session =
+ GenerateFakeSession(channel_manager(), video, &candidates);
+ ASSERT_FALSE(candidates.empty());
+ ASSERT_FALSE(local_session == NULL);
+ ASSERT_TRUE(CallInitiate());
+ if (!CallOnInitiateMessage(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
+
+ ASSERT_FALSE(CallbackReceived(this, 1000));
+ ASSERT_TRUE(!CallHasAudioChannel() &&
+ CallHasVideoChannel());
+ // Incoming call - local desc has the answer.
+ VerifyCryptoParams(local_description(), false);
+ VerifyCryptoParams(remote_description(), true);
+}
diff --git a/talk/app/webrtcv1/webrtcv1.scons b/talk/app/webrtcv1/webrtcv1.scons
new file mode 100644
index 0000000..a648422
--- /dev/null
+++ b/talk/app/webrtcv1/webrtcv1.scons
@@ -0,0 +1,61 @@
+# -*- Python -*-
+import talk
+
+Import('env')
+
+if env.Bit('have_webrtc_voice') and env.Bit('have_webrtc_video'):
+ # local sources
+ talk.Library(
+ env,
+ name = 'webrtcv1',
+ srcs = [
+ 'peerconnectionimpl.cc',
+ 'peerconnectionproxy.cc',
+ 'peerconnectionfactory.cc',
+ 'webrtcjson.cc',
+ 'webrtcsession.cc',
+ ],
+ )
+
+ talk.Unittest(
+ env,
+ name = 'webrtcv1',
+ srcs = [
+ 'peerconnection_unittest.cc',
+ 'unittest_utilities.cc',
+ 'webrtcsession_unittest.cc',
+ ],
+ libs = [
+ 'base',
+ 'expat',
+ 'jpeg',
+ 'json',
+ 'p2p',
+ 'phone',
+ 'srtp',
+ 'webrtcv1',
+ 'xmpp',
+ 'xmllite',
+ 'yuvscaler'
+ ],
+ include_talk_media_libs = True,
+ mac_libs = [
+ 'crypto',
+ 'ssl',
+ ],
+ mac_FRAMEWORKS = [
+ 'Foundation',
+ 'IOKit',
+ 'QTKit',
+ ],
+ win_link_flags = [('', '/nodefaultlib:libcmt')[env.Bit('debug')]],
+ lin_libs = [
+ 'rt',
+ 'dl',
+ 'sound',
+ 'X11',
+ 'Xext',
+ 'Xfixes',
+ 'Xrandr'
+ ],
+ )
diff --git a/talk/base/asynchttprequest.h b/talk/base/asynchttprequest.h
index a1e8aa0..13edf61 100644
--- a/talk/base/asynchttprequest.h
+++ b/talk/base/asynchttprequest.h
@@ -55,6 +55,7 @@
int start_delay() const { return start_delay_; }
void set_start_delay(int delay) { start_delay_ = delay; }
+ const ProxyInfo& proxy() const { return proxy_; }
void set_proxy(const ProxyInfo& proxy) {
proxy_ = proxy;
}
diff --git a/talk/base/atomicops.h b/talk/base/atomicops.h
new file mode 100644
index 0000000..94ade69
--- /dev/null
+++ b/talk/base/atomicops.h
@@ -0,0 +1,166 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ATOMICOPS_H_
+#define TALK_BASE_ATOMICOPS_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+// A single-producer, single-consumer, fixed-size queue.
+// All methods not ending in Unsafe can be safely called without locking,
+// provided that calls to consumer methods (Peek/Pop) or producer methods (Push)
+// only happen on a single thread per method type. If multiple threads need to
+// read simultaneously or write simultaneously, other synchronization is
+// necessary. Synchronization is also required if a call into any Unsafe method
+// could happen at the same time as a call to any other method.
+template <typename T>
+class FixedSizeLockFreeQueue {
+ private:
+// Atomic primitives and memory barrier
+#if defined(__arm__)
+ typedef uint32 Atomic32;
+
+ // Copied from google3/base/atomicops-internals-arm-v6plus.h
+ static inline void MemoryBarrier() {
+ asm volatile("dmb":::"memory");
+ }
+
+ // Adapted from google3/base/atomicops-internals-arm-v6plus.h
+ static inline void AtomicIncrement(volatile Atomic32* ptr) {
+ Atomic32 str_success, value;
+ asm volatile (
+ "1:\n"
+ "ldrex %1, [%2]\n"
+ "add %1, %1, #1\n"
+ "strex %0, %1, [%2]\n"
+ "teq %0, #0\n"
+ "bne 1b"
+ : "=&r"(str_success), "=&r"(value)
+ : "r" (ptr)
+ : "cc", "memory");
+ }
+#elif !defined(SKIP_ATOMIC_CHECK)
+#error "No atomic operations defined for the given architecture."
+#endif
+
+ public:
+ // Constructs an empty queue, with capacity 0.
+ FixedSizeLockFreeQueue() : pushed_count_(0),
+ popped_count_(0),
+ capacity_(0),
+ data_(NULL) {}
+ // Constructs an empty queue with the given capacity.
+ FixedSizeLockFreeQueue(size_t capacity) : pushed_count_(0),
+ popped_count_(0),
+ capacity_(capacity),
+ data_(new T[capacity]) {}
+
+ // Pushes a value onto the queue. Returns true if the value was successfully
+ // pushed (there was space in the queue). This method can be safely called at
+ // the same time as PeekFront/PopFront.
+ bool PushBack(T value) {
+ if (capacity_ == 0) {
+ LOG(LS_WARNING) << "Queue capacity is 0.";
+ return false;
+ }
+ if (IsFull()) {
+ return false;
+ }
+
+ data_[pushed_count_ % capacity_] = value;
+ // Make sure the data is written before the count is incremented, so other
+ // threads can't see the value exists before being able to read it.
+ MemoryBarrier();
+ AtomicIncrement(&pushed_count_);
+ return true;
+ }
+
+ // Retrieves the oldest value pushed onto the queue. Returns true if there was
+ // an item to peek (the queue was non-empty). This method can be safely called
+ // at the same time as PushBack.
+ bool PeekFront(T* value_out) {
+ if (capacity_ == 0) {
+ LOG(LS_WARNING) << "Queue capacity is 0.";
+ return false;
+ }
+ if (IsEmpty()) {
+ return false;
+ }
+
+ *value_out = data_[popped_count_ % capacity_];
+ return true;
+ }
+
+ // Retrieves the oldest value pushed onto the queue and removes it from the
+ // queue. Returns true if there was an item to pop (the queue was non-empty).
+ // This method can be safely called at the same time as PushBack.
+ bool PopFront(T* value_out) {
+ if (PeekFront(value_out)) {
+ AtomicIncrement(&popped_count_);
+ return true;
+ }
+ return false;
+ }
+
+ // Clears the current items in the queue and sets the new (fixed) size. This
+ // method cannot be called at the same time as any other method.
+ void ClearAndResizeUnsafe(int new_capacity) {
+ capacity_ = new_capacity;
+ data_.reset(new T[new_capacity]);
+ pushed_count_ = 0;
+ popped_count_ = 0;
+ }
+
+ // Returns true if there is no space left in the queue for new elements.
+ int IsFull() const { return pushed_count_ == popped_count_ + capacity_; }
+ // Returns true if there are no elements in the queue.
+ int IsEmpty() const { return pushed_count_ == popped_count_; }
+ // Returns the current number of elements in the queue. This is always in the
+ // range [0, capacity]
+ size_t Size() const { return pushed_count_ - popped_count_; }
+
+ // Returns the capacity of the queue (max size).
+ size_t capacity() const { return capacity_; }
+
+ private:
+ volatile Atomic32 pushed_count_;
+ volatile Atomic32 popped_count_;
+ size_t capacity_;
+ talk_base::scoped_array<T> data_;
+ DISALLOW_COPY_AND_ASSIGN(FixedSizeLockFreeQueue);
+};
+
+}
+
+#endif // TALK_BASE_ATOMICOPS_H_
diff --git a/talk/base/atomicops_unittest.cc b/talk/base/atomicops_unittest.cc
new file mode 100644
index 0000000..24804c6
--- /dev/null
+++ b/talk/base/atomicops_unittest.cc
@@ -0,0 +1,96 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if !defined(__arm__)
+// For testing purposes, define faked versions of the atomic operations
+#include "talk/base/basictypes.h"
+namespace talk_base {
+typedef uint32 Atomic32;
+static inline void MemoryBarrier() { }
+static inline void AtomicIncrement(volatile Atomic32* ptr) {
+ *ptr = *ptr + 1;
+}
+}
+#define SKIP_ATOMIC_CHECK
+#endif
+
+#include "talk/base/atomicops.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+TEST(FixedSizeLockFreeQueueTest, TestDefaultConstruct) {
+ talk_base::FixedSizeLockFreeQueue<int> queue;
+ EXPECT_EQ(0u, queue.capacity());
+ EXPECT_EQ(0u, queue.Size());
+ EXPECT_FALSE(queue.PushBack(1));
+ int val;
+ EXPECT_FALSE(queue.PopFront(&val));
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestConstruct) {
+ talk_base::FixedSizeLockFreeQueue<int> queue(5);
+ EXPECT_EQ(5u, queue.capacity());
+ EXPECT_EQ(0u, queue.Size());
+ int val;
+ EXPECT_FALSE(queue.PopFront(&val));
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestPushPop) {
+ talk_base::FixedSizeLockFreeQueue<int> queue(2);
+ EXPECT_EQ(2u, queue.capacity());
+ EXPECT_EQ(0u, queue.Size());
+ EXPECT_TRUE(queue.PushBack(1));
+ EXPECT_EQ(1u, queue.Size());
+ EXPECT_TRUE(queue.PushBack(2));
+ EXPECT_EQ(2u, queue.Size());
+ EXPECT_FALSE(queue.PushBack(3));
+ EXPECT_EQ(2u, queue.Size());
+ int val;
+ EXPECT_TRUE(queue.PopFront(&val));
+ EXPECT_EQ(1, val);
+ EXPECT_EQ(1u, queue.Size());
+ EXPECT_TRUE(queue.PopFront(&val));
+ EXPECT_EQ(2, val);
+ EXPECT_EQ(0u, queue.Size());
+ EXPECT_FALSE(queue.PopFront(&val));
+ EXPECT_EQ(0u, queue.Size());
+}
+
+TEST(FixedSizeLockFreeQueueTest, TestResize) {
+ talk_base::FixedSizeLockFreeQueue<int> queue(2);
+ EXPECT_EQ(2u, queue.capacity());
+ EXPECT_EQ(0u, queue.Size());
+ EXPECT_TRUE(queue.PushBack(1));
+ EXPECT_EQ(1u, queue.Size());
+
+ queue.ClearAndResizeUnsafe(5);
+ EXPECT_EQ(5u, queue.capacity());
+ EXPECT_EQ(0u, queue.Size());
+ int val;
+ EXPECT_FALSE(queue.PopFront(&val));
+}
diff --git a/talk/base/autodetectproxy.cc b/talk/base/autodetectproxy.cc
index f79926d..37faa4d 100644
--- a/talk/base/autodetectproxy.cc
+++ b/talk/base/autodetectproxy.cc
@@ -107,7 +107,7 @@
proxy().address.hostname(),
sizeof address_hostname);
- uint32 address_ip = proxy().address.ip();
+ IPAddress address_ip = proxy().address.ipaddr();
uint16 address_port = proxy().address.port();
diff --git a/talk/base/bandwidthsmoother.cc b/talk/base/bandwidthsmoother.cc
new file mode 100644
index 0000000..130f2a8
--- /dev/null
+++ b/talk/base/bandwidthsmoother.cc
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/bandwidthsmoother.h"
+
+#include <limits.h>
+
+namespace talk_base {
+
+BandwidthSmoother::BandwidthSmoother(int initial_bandwidth_guess,
+ uint32 time_between_increase,
+ double percent_increase,
+ size_t samples_count_to_average)
+ : time_between_increase_(time_between_increase),
+ percent_increase_(talk_base::_max(1.0, percent_increase)),
+ time_at_last_change_(0),
+ bandwidth_estimation_(initial_bandwidth_guess),
+ accumulator_(samples_count_to_average) {
+}
+
+// Samples a new bandwidth measurement
+// returns true if the bandwidth estimation changed
+bool BandwidthSmoother::Sample(uint32 sample_time, int bandwidth) {
+ if (bandwidth < 0) {
+ return false;
+ }
+
+ accumulator_.AddSample(bandwidth);
+
+ // Replace bandwidth with the mean of sampled bandwidths.
+ bandwidth = accumulator_.ComputeMean();
+
+ if (bandwidth < bandwidth_estimation_) {
+ time_at_last_change_ = sample_time;
+ bandwidth_estimation_ = bandwidth;
+ return true;
+ }
+
+ const int old_bandwidth_estimation = bandwidth_estimation_;
+ const double increase_threshold_d = percent_increase_ * bandwidth_estimation_;
+ if (increase_threshold_d > INT_MAX) {
+ // If bandwidth goes any higher we would overflow.
+ return false;
+ }
+
+ const int increase_threshold = static_cast<int>(increase_threshold_d);
+ if (bandwidth < increase_threshold) {
+ time_at_last_change_ = sample_time;
+ // The value of bandwidth_estimation remains the same if we don't exceed
+ // percent_increase_ * bandwidth_estimation_ for at least
+ // time_between_increase_ time.
+ } else if (sample_time >= time_at_last_change_ + time_between_increase_) {
+ time_at_last_change_ = sample_time;
+ if (increase_threshold == 0) {
+ bandwidth_estimation_ = bandwidth / 2;
+ } else {
+ bandwidth_estimation_ = increase_threshold;
+ }
+ }
+ // Else don't make a change.
+
+ return old_bandwidth_estimation != bandwidth_estimation_;
+}
+
+} // namespace talk_base
+
diff --git a/talk/base/bandwidthsmoother.h b/talk/base/bandwidthsmoother.h
new file mode 100644
index 0000000..afd1e52
--- /dev/null
+++ b/talk/base/bandwidthsmoother.h
@@ -0,0 +1,71 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_BANDWIDTHSMOOTHER_H_
+#define TALK_BASE_BANDWIDTHSMOOTHER_H_
+
+#include "talk/base/rollingaccumulator.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+// The purpose of BandwidthSmoother is to smooth out bandwidth
+// estimations so that 'trstate' messages can be triggered when we
+// are "sure" there is sufficient bandwidth. To avoid frequent fluctuations,
+// we take a slightly pessimistic view of our bandwidth. We only increase
+// our estimation when we have sampled bandwidth measurements of values
+// at least as large as the current estimation * percent_increase
+// for at least time_between_increase time. If a sampled bandwidth
+// is less than our current estimation we immediately decrease our estimation
+// to that sampled value.
+class BandwidthSmoother {
+ public:
+ BandwidthSmoother(int initial_bandwidth_guess,
+ uint32 time_between_increase,
+ double percent_increase,
+ size_t samples_count_to_average);
+
+ // Samples a new bandwidth measurement.
+ // bandwidth is expected to be non-negative.
+ // returns true if the bandwidth estimation changed
+ bool Sample(uint32 sample_time, int bandwidth);
+
+ int get_bandwidth_estimation() const {
+ return bandwidth_estimation_;
+ }
+
+ private:
+ uint32 time_between_increase_;
+ double percent_increase_;
+ uint32 time_at_last_change_;
+ int bandwidth_estimation_;
+ RollingAccumulator<int> accumulator_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BANDWIDTHSMOOTHER_H_
diff --git a/talk/base/bandwidthsmoother_unittest.cc b/talk/base/bandwidthsmoother_unittest.cc
new file mode 100644
index 0000000..1eb79bd
--- /dev/null
+++ b/talk/base/bandwidthsmoother_unittest.cc
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+
+#include "talk/base/bandwidthsmoother.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+class BandwidthSmootherTest : public testing::Test {
+ public:
+};
+
+static const int kTimeBetweenIncrease = 10;
+static const double kPercentIncrease = 1.1;
+static const size_t kSamplesCountToAverage = 1;
+
+TEST_F(BandwidthSmootherTest, TestSampleIncrease) {
+ BandwidthSmoother mon(1000, // initial_bandwidth_guess
+ kTimeBetweenIncrease,
+ kPercentIncrease,
+ kSamplesCountToAverage);
+
+ int bandwidth_sample = 1000;
+ EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+ bandwidth_sample =
+ static_cast<int>(bandwidth_sample * kPercentIncrease);
+ EXPECT_FALSE(mon.Sample(9, bandwidth_sample));
+ EXPECT_TRUE(mon.Sample(10, bandwidth_sample));
+ EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+ int next_expected_est =
+ static_cast<int>(bandwidth_sample * kPercentIncrease);
+ bandwidth_sample *= 2;
+ EXPECT_TRUE(mon.Sample(20, bandwidth_sample));
+ EXPECT_EQ(next_expected_est, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleIncreaseFromZero) {
+ BandwidthSmoother mon(0, // initial_bandwidth_guess
+ kTimeBetweenIncrease,
+ kPercentIncrease,
+ kSamplesCountToAverage);
+
+ int bandwidth_sample = 1000;
+ EXPECT_FALSE(mon.Sample(9, bandwidth_sample));
+ EXPECT_TRUE(mon.Sample(10, bandwidth_sample));
+ EXPECT_EQ(bandwidth_sample/2, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleDecrease) {
+ BandwidthSmoother mon(1000, // initial_bandwidth_guess
+ kTimeBetweenIncrease,
+ kPercentIncrease,
+ kSamplesCountToAverage);
+
+ int bandwidth_sample = 999;
+ EXPECT_TRUE(mon.Sample(1, bandwidth_sample));
+ EXPECT_EQ(bandwidth_sample, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleRollover) {
+ const int initial_bandwidth_guess = 2000000000; // > INT_MAX/1.1
+ BandwidthSmoother mon(initial_bandwidth_guess,
+ kTimeBetweenIncrease,
+ kPercentIncrease,
+ kSamplesCountToAverage);
+
+ EXPECT_FALSE(mon.Sample(10, INT_MAX));
+ EXPECT_EQ(initial_bandwidth_guess, mon.get_bandwidth_estimation());
+}
+
+TEST_F(BandwidthSmootherTest, TestSampleNegative) {
+ BandwidthSmoother mon(1000, // initial_bandwidth_guess
+ kTimeBetweenIncrease,
+ kPercentIncrease,
+ kSamplesCountToAverage);
+
+ EXPECT_FALSE(mon.Sample(10, -1));
+}
+
+} // namespace talk_base
diff --git a/talk/base/base64_unittest.cc b/talk/base/base64_unittest.cc
new file mode 100644
index 0000000..644a5e0
--- /dev/null
+++ b/talk/base/base64_unittest.cc
@@ -0,0 +1,1004 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/base64.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+#include "talk/base/testbase64.h"
+
+using namespace std;
+using namespace talk_base;
+
+static struct {
+ size_t plain_length;
+ const char* plaintext;
+ const char* cyphertext;
+} base64_tests[] = {
+
+ // Basic bit patterns;
+ // values obtained with "echo -n '...' | uuencode -m test"
+
+ { 1, "\000", "AA==" },
+ { 1, "\001", "AQ==" },
+ { 1, "\002", "Ag==" },
+ { 1, "\004", "BA==" },
+ { 1, "\010", "CA==" },
+ { 1, "\020", "EA==" },
+ { 1, "\040", "IA==" },
+ { 1, "\100", "QA==" },
+ { 1, "\200", "gA==" },
+
+ { 1, "\377", "/w==" },
+ { 1, "\376", "/g==" },
+ { 1, "\375", "/Q==" },
+ { 1, "\373", "+w==" },
+ { 1, "\367", "9w==" },
+ { 1, "\357", "7w==" },
+ { 1, "\337", "3w==" },
+ { 1, "\277", "vw==" },
+ { 1, "\177", "fw==" },
+ { 2, "\000\000", "AAA=" },
+ { 2, "\000\001", "AAE=" },
+ { 2, "\000\002", "AAI=" },
+ { 2, "\000\004", "AAQ=" },
+ { 2, "\000\010", "AAg=" },
+ { 2, "\000\020", "ABA=" },
+ { 2, "\000\040", "ACA=" },
+ { 2, "\000\100", "AEA=" },
+ { 2, "\000\200", "AIA=" },
+ { 2, "\001\000", "AQA=" },
+ { 2, "\002\000", "AgA=" },
+ { 2, "\004\000", "BAA=" },
+ { 2, "\010\000", "CAA=" },
+ { 2, "\020\000", "EAA=" },
+ { 2, "\040\000", "IAA=" },
+ { 2, "\100\000", "QAA=" },
+ { 2, "\200\000", "gAA=" },
+
+ { 2, "\377\377", "//8=" },
+ { 2, "\377\376", "//4=" },
+ { 2, "\377\375", "//0=" },
+ { 2, "\377\373", "//s=" },
+ { 2, "\377\367", "//c=" },
+ { 2, "\377\357", "/+8=" },
+ { 2, "\377\337", "/98=" },
+ { 2, "\377\277", "/78=" },
+ { 2, "\377\177", "/38=" },
+ { 2, "\376\377", "/v8=" },
+ { 2, "\375\377", "/f8=" },
+ { 2, "\373\377", "+/8=" },
+ { 2, "\367\377", "9/8=" },
+ { 2, "\357\377", "7/8=" },
+ { 2, "\337\377", "3/8=" },
+ { 2, "\277\377", "v/8=" },
+ { 2, "\177\377", "f/8=" },
+
+ { 3, "\000\000\000", "AAAA" },
+ { 3, "\000\000\001", "AAAB" },
+ { 3, "\000\000\002", "AAAC" },
+ { 3, "\000\000\004", "AAAE" },
+ { 3, "\000\000\010", "AAAI" },
+ { 3, "\000\000\020", "AAAQ" },
+ { 3, "\000\000\040", "AAAg" },
+ { 3, "\000\000\100", "AABA" },
+ { 3, "\000\000\200", "AACA" },
+ { 3, "\000\001\000", "AAEA" },
+ { 3, "\000\002\000", "AAIA" },
+ { 3, "\000\004\000", "AAQA" },
+ { 3, "\000\010\000", "AAgA" },
+ { 3, "\000\020\000", "ABAA" },
+ { 3, "\000\040\000", "ACAA" },
+ { 3, "\000\100\000", "AEAA" },
+ { 3, "\000\200\000", "AIAA" },
+ { 3, "\001\000\000", "AQAA" },
+ { 3, "\002\000\000", "AgAA" },
+ { 3, "\004\000\000", "BAAA" },
+ { 3, "\010\000\000", "CAAA" },
+ { 3, "\020\000\000", "EAAA" },
+ { 3, "\040\000\000", "IAAA" },
+ { 3, "\100\000\000", "QAAA" },
+ { 3, "\200\000\000", "gAAA" },
+
+ { 3, "\377\377\377", "////" },
+ { 3, "\377\377\376", "///+" },
+ { 3, "\377\377\375", "///9" },
+ { 3, "\377\377\373", "///7" },
+ { 3, "\377\377\367", "///3" },
+ { 3, "\377\377\357", "///v" },
+ { 3, "\377\377\337", "///f" },
+ { 3, "\377\377\277", "//+/" },
+ { 3, "\377\377\177", "//9/" },
+ { 3, "\377\376\377", "//7/" },
+ { 3, "\377\375\377", "//3/" },
+ { 3, "\377\373\377", "//v/" },
+ { 3, "\377\367\377", "//f/" },
+ { 3, "\377\357\377", "/+//" },
+ { 3, "\377\337\377", "/9//" },
+ { 3, "\377\277\377", "/7//" },
+ { 3, "\377\177\377", "/3//" },
+ { 3, "\376\377\377", "/v//" },
+ { 3, "\375\377\377", "/f//" },
+ { 3, "\373\377\377", "+///" },
+ { 3, "\367\377\377", "9///" },
+ { 3, "\357\377\377", "7///" },
+ { 3, "\337\377\377", "3///" },
+ { 3, "\277\377\377", "v///" },
+ { 3, "\177\377\377", "f///" },
+
+ // Random numbers: values obtained with
+ //
+ // #! /bin/bash
+ // dd bs=$1 count=1 if=/dev/random of=/tmp/bar.random
+ // od -N $1 -t o1 /tmp/bar.random
+ // uuencode -m test < /tmp/bar.random
+ //
+ // where $1 is the number of bytes (2, 3)
+
+ { 2, "\243\361", "o/E=" },
+ { 2, "\024\167", "FHc=" },
+ { 2, "\313\252", "y6o=" },
+ { 2, "\046\041", "JiE=" },
+ { 2, "\145\236", "ZZ4=" },
+ { 2, "\254\325", "rNU=" },
+ { 2, "\061\330", "Mdg=" },
+ { 2, "\245\032", "pRo=" },
+ { 2, "\006\000", "BgA=" },
+ { 2, "\375\131", "/Vk=" },
+ { 2, "\303\210", "w4g=" },
+ { 2, "\040\037", "IB8=" },
+ { 2, "\261\372", "sfo=" },
+ { 2, "\335\014", "3Qw=" },
+ { 2, "\233\217", "m48=" },
+ { 2, "\373\056", "+y4=" },
+ { 2, "\247\232", "p5o=" },
+ { 2, "\107\053", "Rys=" },
+ { 2, "\204\077", "hD8=" },
+ { 2, "\276\211", "vok=" },
+ { 2, "\313\110", "y0g=" },
+ { 2, "\363\376", "8/4=" },
+ { 2, "\251\234", "qZw=" },
+ { 2, "\103\262", "Q7I=" },
+ { 2, "\142\312", "Yso=" },
+ { 2, "\067\211", "N4k=" },
+ { 2, "\220\001", "kAE=" },
+ { 2, "\152\240", "aqA=" },
+ { 2, "\367\061", "9zE=" },
+ { 2, "\133\255", "W60=" },
+ { 2, "\176\035", "fh0=" },
+ { 2, "\032\231", "Gpk=" },
+
+ { 3, "\013\007\144", "Cwdk" },
+ { 3, "\030\112\106", "GEpG" },
+ { 3, "\047\325\046", "J9Um" },
+ { 3, "\310\160\022", "yHAS" },
+ { 3, "\131\100\237", "WUCf" },
+ { 3, "\064\342\134", "NOJc" },
+ { 3, "\010\177\004", "CH8E" },
+ { 3, "\345\147\205", "5WeF" },
+ { 3, "\300\343\360", "wOPw" },
+ { 3, "\061\240\201", "MaCB" },
+ { 3, "\225\333\044", "ldsk" },
+ { 3, "\215\137\352", "jV/q" },
+ { 3, "\371\147\160", "+Wdw" },
+ { 3, "\030\320\051", "GNAp" },
+ { 3, "\044\174\241", "JHyh" },
+ { 3, "\260\127\037", "sFcf" },
+ { 3, "\111\045\033", "SSUb" },
+ { 3, "\202\114\107", "gkxH" },
+ { 3, "\057\371\042", "L/ki" },
+ { 3, "\223\247\244", "k6ek" },
+ { 3, "\047\216\144", "J45k" },
+ { 3, "\203\070\327", "gzjX" },
+ { 3, "\247\140\072", "p2A6" },
+ { 3, "\124\115\116", "VE1O" },
+ { 3, "\157\162\050", "b3Io" },
+ { 3, "\357\223\004", "75ME" },
+ { 3, "\052\117\156", "Kk9u" },
+ { 3, "\347\154\000", "52wA" },
+ { 3, "\303\012\142", "wwpi" },
+ { 3, "\060\035\362", "MB3y" },
+ { 3, "\130\226\361", "WJbx" },
+ { 3, "\173\013\071", "ews5" },
+ { 3, "\336\004\027", "3gQX" },
+ { 3, "\357\366\234", "7/ac" },
+ { 3, "\353\304\111", "68RJ" },
+ { 3, "\024\264\131", "FLRZ" },
+ { 3, "\075\114\251", "PUyp" },
+ { 3, "\315\031\225", "zRmV" },
+ { 3, "\154\201\276", "bIG+" },
+ { 3, "\200\066\072", "gDY6" },
+ { 3, "\142\350\267", "Yui3" },
+ { 3, "\033\000\166", "GwB2" },
+ { 3, "\210\055\077", "iC0/" },
+ { 3, "\341\037\124", "4R9U" },
+ { 3, "\161\103\152", "cUNq" },
+ { 3, "\270\142\131", "uGJZ" },
+ { 3, "\337\076\074", "3z48" },
+ { 3, "\375\106\362", "/Uby" },
+ { 3, "\227\301\127", "l8FX" },
+ { 3, "\340\002\234", "4AKc" },
+ { 3, "\121\064\033", "UTQb" },
+ { 3, "\157\134\143", "b1xj" },
+ { 3, "\247\055\327", "py3X" },
+ { 3, "\340\142\005", "4GIF" },
+ { 3, "\060\260\143", "MLBj" },
+ { 3, "\075\203\170", "PYN4" },
+ { 3, "\143\160\016", "Y3AO" },
+ { 3, "\313\013\063", "ywsz" },
+ { 3, "\174\236\135", "fJ5d" },
+ { 3, "\103\047\026", "QycW" },
+ { 3, "\365\005\343", "9QXj" },
+ { 3, "\271\160\223", "uXCT" },
+ { 3, "\362\255\172", "8q16" },
+ { 3, "\113\012\015", "SwoN" },
+
+ // various lengths, generated by this python script:
+ //
+ // from string import lowercase as lc
+ // for i in range(27):
+ // print '{ %2d, "%s",%s "%s" },' % (i, lc[:i], ' ' * (26-i),
+ // lc[:i].encode('base64').strip())
+
+ { 0, "abcdefghijklmnopqrstuvwxyz", "" },
+ { 1, "abcdefghijklmnopqrstuvwxyz", "YQ==" },
+ { 2, "abcdefghijklmnopqrstuvwxyz", "YWI=" },
+ { 3, "abcdefghijklmnopqrstuvwxyz", "YWJj" },
+ { 4, "abcdefghijklmnopqrstuvwxyz", "YWJjZA==" },
+ { 5, "abcdefghijklmnopqrstuvwxyz", "YWJjZGU=" },
+ { 6, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVm" },
+ { 7, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZw==" },
+ { 8, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2g=" },
+ { 9, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hp" },
+ { 10, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpag==" },
+ { 11, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpams=" },
+ { 12, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamts" },
+ { 13, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbQ==" },
+ { 14, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW4=" },
+ { 15, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5v" },
+ { 16, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcA==" },
+ { 17, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHE=" },
+ { 18, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFy" },
+ { 19, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFycw==" },
+ { 20, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3Q=" },
+ { 21, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1" },
+ { 22, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dg==" },
+ { 23, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnc=" },
+ { 24, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4" },
+ { 25, "abcdefghijklmnopqrstuvwxy", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eQ==" },
+ { 26, "abcdefghijklmnopqrstuvwxyz", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=" },
+};
+#if 0
+static struct {
+ const char* plaintext;
+ const char* cyphertext;
+} base64_strings[] = {
+
+ // The first few Google quotes
+ // Cyphertext created with "uuencode - GNU sharutils 4.2.1"
+ {
+ "Everyone! We're teetering on the brink of disaster."
+ " - Sergey Brin, 6/24/99, regarding the company's state "
+ "after the unleashing of Netscape/Google search",
+
+ "RXZlcnlvbmUhICBXZSdyZSB0ZWV0ZXJpbmcgb24gdGhlIGJyaW5rIG9mIGRp"
+ "c2FzdGVyLiAtIFNlcmdleSBCcmluLCA2LzI0Lzk5LCByZWdhcmRpbmcgdGhl"
+ "IGNvbXBhbnkncyBzdGF0ZSBhZnRlciB0aGUgdW5sZWFzaGluZyBvZiBOZXRz"
+ "Y2FwZS9Hb29nbGUgc2VhcmNo" },
+
+ {
+ "I'm not sure why we're still alive, but we seem to be."
+ " - Larry Page, 6/24/99, while hiding in the kitchenette "
+ "during the Netscape traffic overflow",
+
+ "SSdtIG5vdCBzdXJlIHdoeSB3ZSdyZSBzdGlsbCBhbGl2ZSwgYnV0IHdlIHNl"
+ "ZW0gdG8gYmUuIC0gTGFycnkgUGFnZSwgNi8yNC85OSwgd2hpbGUgaGlkaW5n"
+ "IGluIHRoZSBraXRjaGVuZXR0ZSBkdXJpbmcgdGhlIE5ldHNjYXBlIHRyYWZm"
+ "aWMgb3ZlcmZsb3c" },
+
+ {
+ "I think kids want porn."
+ " - Sergey Brin, 6/99, on why Google shouldn't prioritize a "
+ "filtered search for children and families",
+
+ "SSB0aGluayBraWRzIHdhbnQgcG9ybi4gLSBTZXJnZXkgQnJpbiwgNi85OSwg"
+ "b24gd2h5IEdvb2dsZSBzaG91bGRuJ3QgcHJpb3JpdGl6ZSBhIGZpbHRlcmVk"
+ "IHNlYXJjaCBmb3IgY2hpbGRyZW4gYW5kIGZhbWlsaWVz" },
+};
+#endif
+// Compare bytes 0..len-1 of x and y. If not equal, abort with verbose error
+// message showing position and numeric value that differed.
+// Handles embedded nulls just like any other byte.
+// Only added because string.compare() in gcc-3.3.3 seems to misbehave with
+// embedded nulls.
+// TODO: switch back to string.compare() if/when gcc is fixed
+#define EXPECT_EQ_ARRAY(len, x, y, msg) \
+ for (size_t j = 0; j < len; ++j) { \
+ if (x[j] != y[j]) { \
+ LOG(LS_ERROR) << "" # x << " != " # y \
+ << " byte " << j << " msg: " << msg; \
+ } \
+ }
+
+size_t Base64Escape(const unsigned char *src, size_t szsrc, char *dest,
+ size_t szdest) {
+ std::string escaped;
+ Base64::EncodeFromArray((const char *)src, szsrc, &escaped);
+ memcpy(dest, escaped.data(), min(escaped.size(), szdest));
+ return escaped.size();
+}
+
+size_t Base64Unescape(const char *src, size_t szsrc, char *dest,
+ size_t szdest) {
+ std::string unescaped;
+ EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, &unescaped,
+ NULL));
+ memcpy(dest, unescaped.data(), min(unescaped.size(), szdest));
+ return unescaped.size();
+}
+
+size_t Base64Unescape(const char *src, size_t szsrc, string *s) {
+ EXPECT_TRUE(Base64::DecodeFromArray(src, szsrc, Base64::DO_LAX, s, NULL));
+ return s->size();
+}
+
+TEST(Base64, EncodeDecodeBattery) {
+ LOG(LS_VERBOSE) << "Testing base-64";
+
+ size_t i;
+
+ // Check the short strings; this tests the math (and boundaries)
+ for( i = 0; i < sizeof(base64_tests) / sizeof(base64_tests[0]); ++i ) {
+ char encode_buffer[100];
+ size_t encode_length;
+ char decode_buffer[100];
+ size_t decode_length;
+ size_t cypher_length;
+
+ LOG(LS_VERBOSE) << "B64: " << base64_tests[i].cyphertext;
+
+ const unsigned char* unsigned_plaintext =
+ reinterpret_cast<const unsigned char*>(base64_tests[i].plaintext);
+
+ cypher_length = strlen(base64_tests[i].cyphertext);
+
+ // The basic escape function:
+ memset(encode_buffer, 0, sizeof(encode_buffer));
+ encode_length = Base64Escape(unsigned_plaintext,
+ base64_tests[i].plain_length,
+ encode_buffer,
+ sizeof(encode_buffer));
+ // Is it of the expected length?
+ EXPECT_EQ(encode_length, cypher_length);
+
+ // Is it the expected encoded value?
+ EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext);
+
+ // If we encode it into a buffer of exactly the right length...
+ memset(encode_buffer, 0, sizeof(encode_buffer));
+ encode_length = Base64Escape(unsigned_plaintext,
+ base64_tests[i].plain_length,
+ encode_buffer,
+ cypher_length);
+ // Is it still of the expected length?
+ EXPECT_EQ(encode_length, cypher_length);
+
+ // And is the value still correct? (i.e., not losing the last byte)
+ EXPECT_STREQ(encode_buffer, base64_tests[i].cyphertext);
+
+ // If we decode it back:
+ memset(decode_buffer, 0, sizeof(decode_buffer));
+ decode_length = Base64Unescape(encode_buffer,
+ cypher_length,
+ decode_buffer,
+ sizeof(decode_buffer));
+
+ // Is it of the expected length?
+ EXPECT_EQ(decode_length, base64_tests[i].plain_length);
+
+ // Is it the expected decoded value?
+ EXPECT_EQ(0, memcmp(decode_buffer, base64_tests[i].plaintext, decode_length));
+
+ // Our decoder treats the padding '=' characters at the end as
+ // optional. If encode_buffer has any, run some additional
+ // tests that fiddle with them.
+ char* first_equals = strchr(encode_buffer, '=');
+ if (first_equals) {
+ // How many equals signs does the string start with?
+ int equals = (*(first_equals+1) == '=') ? 2 : 1;
+
+ // Try chopping off the equals sign(s) entirely. The decoder
+ // should still be okay with this.
+ string decoded2("this junk should also be ignored");
+ *first_equals = '\0';
+ EXPECT_NE(0U, Base64Unescape(encode_buffer, first_equals-encode_buffer,
+ &decoded2));
+ EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length);
+ EXPECT_EQ_ARRAY(decoded2.size(), decoded2.data(), base64_tests[i].plaintext, i);
+
+ int len;
+
+ // try putting some extra stuff after the equals signs, or in between them
+ if (equals == 2) {
+ sprintfn(first_equals, 6, " = = ");
+ len = first_equals - encode_buffer + 5;
+ } else {
+ sprintfn(first_equals, 6, " = ");
+ len = first_equals - encode_buffer + 3;
+ }
+ decoded2.assign("this junk should be ignored");
+ EXPECT_NE(0U, Base64Unescape(encode_buffer, len, &decoded2));
+ EXPECT_EQ(decoded2.size(), base64_tests[i].plain_length);
+ EXPECT_EQ_ARRAY(decoded2.size(), decoded2, base64_tests[i].plaintext, i);
+ }
+ }
+}
+
+// here's a weird case: a giant base64 encoded stream which broke our base64
+// decoding. Let's test it explicitly.
+const char SpecificTest[] =
+ "/9j/4AAQSkZJRgABAgEASABIAAD/4Q0HRXhpZgAATU0AKgAAAAgADAEOAAIAAAAgAAAAngEPAAI\n"
+ "AAAAFAAAAvgEQAAIAAAAJAAAAwwESAAMAAAABAAEAAAEaAAUAAAABAAAAzAEbAAUAAAABAAAA1A\n"
+ "EoAAMAAAABAAIAAAExAAIAAAAUAAAA3AEyAAIAAAAUAAAA8AE8AAIAAAAQAAABBAITAAMAAAABA\n"
+ "AIAAIdpAAQAAAABAAABFAAAAsQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAFNPTlkA\n"
+ "RFNDLVAyMDAAAAAASAAAAAEAAABIAAAAAUFkb2JlIFBob3Rvc2hvcCA3LjAAMjAwNzowMTozMCA\n"
+ "yMzoxMDowNABNYWMgT1MgWCAxMC40LjgAAByCmgAFAAAAAQAAAmqCnQAFAAAAAQAAAnKIIgADAA\n"
+ "AAAQACAACIJwADAAAAAQBkAACQAAAHAAAABDAyMjCQAwACAAAAFAAAAnqQBAACAAAAFAAAAo6RA\n"
+ "QAHAAAABAECAwCRAgAFAAAAAQAAAqKSBAAKAAAAAQAAAqqSBQAFAAAAAQAAArKSBwADAAAAAQAF\n"
+ "AACSCAADAAAAAQAAAACSCQADAAAAAQAPAACSCgAFAAAAAQAAArqgAAAHAAAABDAxMDCgAQADAAA\n"
+ "AAf//AACgAgAEAAAAAQAAAGSgAwAEAAAAAQAAAGSjAAAHAAAAAQMAAACjAQAHAAAAAQEAAACkAQ\n"
+ "ADAAAAAQAAAACkAgADAAAAAQAAAACkAwADAAAAAQAAAACkBgADAAAAAQAAAACkCAADAAAAAQAAA\n"
+ "ACkCQADAAAAAQAAAACkCgADAAAAAQAAAAAAAAAAAAAACgAAAZAAAAAcAAAACjIwMDc6MDE6MjAg\n"
+ "MjM6MDU6NTIAMjAwNzowMToyMCAyMzowNTo1MgAAAAAIAAAAAQAAAAAAAAAKAAAAMAAAABAAAAB\n"
+ "PAAAACgAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAAxIBGwAFAAAAAQAAAxoBKAADAAAAAQACAA\n"
+ "ACAQAEAAAAAQAAAyICAgAEAAAAAQAACd0AAAAAAAAASAAAAAEAAABIAAAAAf/Y/+AAEEpGSUYAA\n"
+ "QIBAEgASAAA/+0ADEFkb2JlX0NNAAL/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsK\n"
+ "CxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0\n"
+ "ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA\n"
+ "wMDAz/wAARCABkAGQDASIAAhEBAxEB/90ABAAH/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFB\n"
+ "gcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhED\n"
+ "BCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0Nhf\n"
+ "SVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAg\n"
+ "IBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJ\n"
+ "QYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm\n"
+ "9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwDy7bKNTUXNLz9EaJPDWMjxH4ozhtpYwaACT8ShaaW\n"
+ "bW0uEc9/JFfjj0Q4Hk/PRDxwX7y47W9z/AN9Cv4+O3ILK2DcRqT2CaSvEbcl1Jbz37KG1dBldLo\n"
+ "qaS4l9xGjG9v6yoDAdYIaIjUk+AREgo4y5sapirb8Yl0NHHdKvBNm4yA1o5Pc+SPEFvCWqB3HZF\n"
+ "Hj2SbWQ/afGFP0bHP8ATY0uc4w1o1JPkkimGiS2KvqlnmBkOZQTyydzgPMM9v8A0lp4v1Nx9gF1\n"
+ "tpdqJaGtH/S3I0i3lISXW/8AMqnd/O2bfg2eUkqVYf/Q8zuncO4Bj7lZ+n7f5Mj5KsJcY8NUZ4d\n"
+ "uEDVo1HkeU0rg3Om4H2rabCWUN7DQuK1n5FWKW4uCwG92gDRJBS6exhxmMboQI+Cv4WFTQ42Bs2\n"
+ "fvnkkqEmy2YxoMMbpVzaz6jt+RbpHZs8lzkHqrasKkYOKP0jgDfZ4N/wDM1tNrcWfSPmRyq9uNV\n"
+ "DnFg2s97i7UkjxKVrq0eVz3spZsja+ASDzwsh9jnOk/JFzb3XZD3v1c4yT8UACTCniKDUnKz5Nj\n"
+ "G33XV1DV73BrT8dF23SejV4zg9g33cOsPb+SxVvqv9ViwNy8vS0iWs/daf8A0Y5dpTi1sADGxCR\n"
+ "K1o0YBEmInlXWYbDBcDLdPJXa8f71Yrx2jnUoAqLnfZK5hJaW2vdwEk5a/wD/0fN6Ia/e76IiVf\n"
+ "xavUL7CPpnT4LNbYXAVjuQt/AqDmNYO/Kjnoy4hr5J8SwMhrRMaeSvbsxrfUazcOw4UX0Cisem2\n"
+ "SBoD4+Kz8nC6llbSLCRrubJA8kwUWbUDa29X1PMa7aQWjuDC0MXMdbDbhI7eazBiUfZ6GOYRe1s\n"
+ "WvGgJ8Vbw2+m4Bx9s6JpNHuuGo1FF53r/SHYua61gLse0lzXeBP5rkvqx0o5vVWz7WY49QkiQSP\n"
+ "oN/tLoevW/ogxv0HA7tJ0AnhT+pdDGYVl/wCdcTPkGn2NU0JWNWvlgAbHV6fEqdu2gR/r2WlWwt\n"
+ "AA5VXAEsLXTqJafArQY5rRr9LiPBJiZsZCI1pJjxCi0j4oncSICSkWwzwkjeaSch//0vO7sP7Lm\n"
+ "enO9ogtd5FbPT3Q5pCpZVc4ld3Lmn3O8j9EI2BYdunKjOobMQIyI+rusc2wx4d0eutwGnHh/uQc\n"
+ "Ha7ladj6mVANGvcqOgz0Go7HJ12/GEHcwvB/dPY6ImbbaMaASGuIBjkN7qofs9Ubg9g7OI9p/t/\n"
+ "RTSmhTHr0v6eSz6UgCPP2/wAVu9Ex2V49dVY2iACB4BZeVXQ/AJ3gzGnnOi2+kACpru8flUsNmt\n"
+ "zHRf6xfWCnoeAfTh2ZaQKazx/Ke7+QxcKz61fWA2uuObaC4zGhaPJrXBL64ZFmR124O09ENraPK\n"
+ "N3/AH5GqxIrZVUyp2K2vfdkENsDnxuex9m4Ox9n82xSgNd9D+p/XR1npgseR9ppOy4Dx/NfH/CL\n"
+ "oQJGunmvMv8AFq3KHVcq3HkYQbD2nuSf0I/rMavSg6TLjLigQhJ7Z58v9QkmlsTOqSCn/9PzL7R\n"
+ "d6Qq3n0wZ2zotXpT9xLfFYvkr/S7jXeB8E0jRkhKpC3q8LcJ/kmCrTnkuAPCq4do9Q/ytVbuAeY\n"
+ "Gg5lQybQK+82GBqEQUA1kOHPYf3LLsoyN36G5w8iUfHxepbXE2l0cApALgLHzBq9UxhTXU5hMC1\n"
+ "ktnSCup6S4Ctk+C5XqVGcaHPfuiuHkeTTuWz0+9zaKiH6CC0/yXBSQ2a/MxojV57634rq+v2PLY\n"
+ "be1r2nsYG13/AFKxbfCBMcr0brGAzrGEwCG31ncx0SfBzf7S4+zoHUWWsJq3hz9oLfcBH77R9H+\n"
+ "0pA13u/qPgDp/Q6ri39JlfpXkDx+h/msWn1L6wdO6bSbcrIbU2Q0xLnSe21kuVejJspbVS5+4bd\n"
+ "ocBAkD/orG+tP1ar67Wy7GtZTm1SCXfRsb+a18fRe38x6SG3/44H1Z3f0y2I+l6DoSXD/8xPrDs\n"
+ "3enVu3bdnqN3R+//USSVo//1PLohhce+gRWS0Nsby3lRgFkKxQyW7SgUh3em5Tbq2uB9wWw1wey\n"
+ "J1XGV2XYdm5k7e4WzidXY9oMwo5RZ4T6Hd1ixwfp96PWbAJBVTHzK7O6Ky5oJB1HZMqmUEFlkGy\n"
+ "xpa4zI1Hkq31dy7bMN9BAc3HeWAnnbyxEycmuup1jiAGglZ31PyrmZ9tQg1WtNj54EHR3/S2qTH\n"
+ "1Yc5GgD1FFtzPdWGkd2AyflogZmRmsz6PSrbXbdo+txOrP337f3fzVo15DK2uyrTtqpBOnBKx6b\n"
+ "7MjJsz7tHWOAYP3WD6LU6cqGjFCNl1MmvLcxv6YtDTLSAqP27LrdtYHXFnJZI+Tp3MWg68OpDPv\n"
+ "UMUM2lkQBoouKQ6swjE9Nml+1sz1PW+z6xt27zuj+skrX2ZvqR5z8kkuOfdPt43/1fMm/grFG6f\n"
+ "Lss9JA7JG7tnZs/SfJUrfS3foJ9TvHCopJsV8nWx/t24bJn8Fo/5TjWJXMJIS+i+G36TsZ/7Q9P\n"
+ "8ATfzfeOFofVSZv2/zvt+O3X/v65dJPjt/BiyfN1/wn0zre79nVej/ADG8ep4x2/6Srjd6TdviF\n"
+ "52ko8m6/Ht9X1KnftEo+POwxzK8mSTF46vrH6T1/OEl5Okkl//Z/+0uHFBob3Rvc2hvcCAzLjAA\n"
+ "OEJJTQQEAAAAAAArHAIAAAIAAhwCeAAfICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAA\n"
+ "4QklNBCUAAAAAABD7Caa9B0wqNp2P4sxXqayFOEJJTQPqAAAAAB2wPD94bWwgdmVyc2lvbj0iMS\n"
+ "4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUgQ\n"
+ "29tcHV0ZXIvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Q\n"
+ "cm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk\n"
+ "+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1Ib3Jpem9udGFsUmVzPC9rZXk+Cgk8ZGljdD\n"
+ "4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCTxzdHJpbmc+Y\n"
+ "29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCTxrZXk+Y29tLmFwcGxlLnByaW50\n"
+ "LnRpY2tldC5pdGVtQXJyYXk8L2tleT4KCQk8YXJyYXk+CgkJCTxkaWN0PgoJCQkJPGtleT5jb20\n"
+ "uYXBwbGUucHJpbnQuUGFnZUZvcm1hdC5QTUhvcml6b250YWxSZXM8L2tleT4KCQkJCTxyZWFsPj\n"
+ "cyPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJC\n"
+ "QkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJpbmc+CgkJCQk8a2V5PmNv\n"
+ "bS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGRhdGU+MjAwNy0wMS0zMFQ\n"
+ "yMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuc3RhdGVGbG\n"
+ "FnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY3Q+CgkJPC9hcnJheT4KC\n"
+ "TwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwv\n"
+ "a2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4\n"
+ "KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS\n"
+ "5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KC\n"
+ "QkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1PcmllbnRhdGlvbjwva2V5PgoJ\n"
+ "CQkJPGludGVnZXI+MTwvaW50ZWdlcj4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5\n"
+ "jbGllbnQ8L2tleT4KCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW\n"
+ "5nPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lm1vZERhdGU8L2tleT4KCQkJCTxkY\n"
+ "XRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQu\n"
+ "dGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJPGludGVnZXI+MDwvaW50ZWdlcj4KCQkJPC9kaWN\n"
+ "0PgoJCTwvYXJyYXk+Cgk8L2RpY3Q+Cgk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0Ll\n"
+ "BNU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZ\n"
+ "WF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4K\n"
+ "CQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFycmF5Pgo\n"
+ "JCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1TY2FsaW5nPC\n"
+ "9rZXk+CgkJCQk8cmVhbD4xPC9yZWFsPgoJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0L\n"
+ "mNsaWVudDwva2V5PgoJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnRpbmdtYW5hZ2VyPC9zdHJp\n"
+ "bmc+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQkJPGR\n"
+ "hdGU+MjAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC\n"
+ "50aWNrZXQuc3RhdGVGbGFnPC9rZXk+CgkJCQk8aW50ZWdlcj4wPC9pbnRlZ2VyPgoJCQk8L2RpY\n"
+ "3Q+CgkJPC9hcnJheT4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQu\n"
+ "UE1WZXJ0aWNhbFJlczwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n"
+ "0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cm\n"
+ "luZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJPGFyc\n"
+ "mF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYXQuUE1WZXJ0\n"
+ "aWNhbFJlczwva2V5PgoJCQkJPHJlYWw+NzI8L3JlYWw+CgkJCQk8a2V5PmNvbS5hcHBsZS5wcml\n"
+ "udC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbm\n"
+ "FnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZ\n"
+ "Xk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT4KCQkJCTxrZXk+Y29tLmFw\n"
+ "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI\n"
+ "+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5jb20uYXBwbGUucHJpbnQuUG\n"
+ "FnZUZvcm1hdC5QTVZlcnRpY2FsU2NhbGluZzwva2V5PgoJPGRpY3Q+CgkJPGtleT5jb20uYXBwb\n"
+ "GUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGlu\n"
+ "Z21hbmFnZXI8L3N0cmluZz4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF\n"
+ "5PC9rZXk+CgkJPGFycmF5PgoJCQk8ZGljdD4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2\n"
+ "VGb3JtYXQuUE1WZXJ0aWNhbFNjYWxpbmc8L2tleT4KCQkJCTxyZWFsPjE8L3JlYWw+CgkJCQk8a\n"
+ "2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50PC9rZXk+CgkJCQk8c3RyaW5nPmNvbS5h\n"
+ "cHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n"
+ "pY2tldC5tb2REYXRlPC9rZXk+CgkJCQk8ZGF0ZT4yMDA3LTAxLTMwVDIyOjA4OjQxWjwvZGF0ZT\n"
+ "4KCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCTxpb\n"
+ "nRlZ2VyPjA8L2ludGVnZXI+CgkJCTwvZGljdD4KCQk8L2FycmF5PgoJPC9kaWN0PgoJPGtleT5j\n"
+ "b20uYXBwbGUucHJpbnQuc3ViVGlja2V0LnBhcGVyX2luZm9fdGlja2V0PC9rZXk+Cgk8ZGljdD4\n"
+ "KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUmVjdDwva2\n"
+ "V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY3JlYXRvcjwva2V5P\n"
+ "goJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJPGtleT5j\n"
+ "b20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGl\n"
+ "jdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYWdlRm9ybWF0LlBNQWRqdXN0ZWRQYWdlUm\n"
+ "VjdDwva2V5PgoJCQkJCTxhcnJheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhb\n"
+ "D4wLjA8L3JlYWw+CgkJCQkJCTxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFs\n"
+ "PgoJCQkJCTwvYXJyYXk+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDw\n"
+ "va2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQ\n"
+ "kJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+M\n"
+ "jAwNy0wMS0zMFQyMjowODo0MVo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlj\n"
+ "a2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q\n"
+ "+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhZ2VGb3JtYX\n"
+ "QuUE1BZGp1c3RlZFBhcGVyUmVjdDwva2V5PgoJCTxkaWN0PgoJCQk8a2V5PmNvbS5hcHBsZS5wc\n"
+ "mludC50aWNrZXQuY3JlYXRvcjwva2V5PgoJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21h\n"
+ "bmFnZXI8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0Lml0ZW1BcnJheTw\n"
+ "va2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC5QYW\n"
+ "dlRm9ybWF0LlBNQWRqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZ\n"
+ "WFsPi0xODwvcmVhbD4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3Jl\n"
+ "YWw+CgkJCQkJCTxyZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmF\n"
+ "wcGxlLnByaW50LnRpY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcm\n"
+ "ludGluZ21hbmFnZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQub\n"
+ "W9kRGF0ZTwva2V5PgoJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJ\n"
+ "CTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWd\n"
+ "lcj4wPC9pbnRlZ2VyPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5Pm\n"
+ "NvbS5hcHBsZS5wcmludC5QYXBlckluZm8uUE1QYXBlck5hbWU8L2tleT4KCQk8ZGljdD4KCQkJP\n"
+ "GtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20u\n"
+ "YXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcml\n"
+ "udC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZX\n"
+ "k+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVBhcGVyTmFtZTwva2V5PgoJCQkJCTxzdHJpb\n"
+ "mc+bmEtbGV0dGVyPC9zdHJpbmc+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNs\n"
+ "aWVudDwva2V5PgoJCQkJCTxzdHJpbmc+Y29tLmFwcGxlLnByaW50LnBtLlBvc3RTY3JpcHQ8L3N\n"
+ "0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5PgoJCQ\n"
+ "kJCTxkYXRlPjIwMDMtMDctMDFUMTc6NDk6MzZaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFwcGxlL\n"
+ "nByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4xPC9pbnRlZ2VyPgoJ\n"
+ "CQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcmludC5\n"
+ "QYXBlckluZm8uUE1VbmFkanVzdGVkUGFnZVJlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb2\n"
+ "0uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUuc\n"
+ "HJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5nPgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNr\n"
+ "ZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJheT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmF\n"
+ "wcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYWdlUmVjdDwva2V5PgoJCQkJCTxhcn\n"
+ "JheT4KCQkJCQkJPHJlYWw+MC4wPC9yZWFsPgoJCQkJCQk8cmVhbD4wLjA8L3JlYWw+CgkJCQkJC\n"
+ "TxyZWFsPjczNDwvcmVhbD4KCQkJCQkJPHJlYWw+NTc2PC9yZWFsPgoJCQkJCTwvYXJyYXk+CgkJ\n"
+ "CQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNsaWVudDwva2V5PgoJCQkJCTxzdHJpbmc\n"
+ "+Y29tLmFwcGxlLnByaW50aW5nbWFuYWdlcjwvc3RyaW5nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLn\n"
+ "ByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPGRhdGU+MjAwNy0wMS0zMFQyMjowODo0M\n"
+ "Vo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LnN0YXRlRmxhZzwva2V5\n"
+ "PgoJCQkJCTxpbnRlZ2VyPjA8L2ludGVnZXI+CgkJCQk8L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9\n"
+ "kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYWRqdXN0ZWRQYXBlcl\n"
+ "JlY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V0LmNyZWF0b\n"
+ "3I8L2tleT4KCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5n\n"
+ "PgoJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuaXRlbUFycmF5PC9rZXk+CgkJCTxhcnJ\n"
+ "heT4KCQkJCTxkaWN0PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LlBhcGVySW5mby5QTVVuYW\n"
+ "RqdXN0ZWRQYXBlclJlY3Q8L2tleT4KCQkJCQk8YXJyYXk+CgkJCQkJCTxyZWFsPi0xODwvcmVhb\n"
+ "D4KCQkJCQkJPHJlYWw+LTE4PC9yZWFsPgoJCQkJCQk8cmVhbD43NzQ8L3JlYWw+CgkJCQkJCTxy\n"
+ "ZWFsPjU5NDwvcmVhbD4KCQkJCQk8L2FycmF5PgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnR\n"
+ "pY2tldC5jbGllbnQ8L2tleT4KCQkJCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmludGluZ21hbmFnZX\n"
+ "I8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQubW9kRGF0ZTwva2V5P\n"
+ "goJCQkJCTxkYXRlPjIwMDctMDEtMzBUMjI6MDg6NDFaPC9kYXRlPgoJCQkJCTxrZXk+Y29tLmFw\n"
+ "cGxlLnByaW50LnRpY2tldC5zdGF0ZUZsYWc8L2tleT4KCQkJCQk8aW50ZWdlcj4wPC9pbnRlZ2V\n"
+ "yPgoJCQkJPC9kaWN0PgoJCQk8L2FycmF5PgoJCTwvZGljdD4KCQk8a2V5PmNvbS5hcHBsZS5wcm\n"
+ "ludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJPGRpY3Q+CgkJCTxrZXk+Y29tL\n"
+ "mFwcGxlLnByaW50LnRpY2tldC5jcmVhdG9yPC9rZXk+CgkJCTxzdHJpbmc+Y29tLmFwcGxlLnBy\n"
+ "aW50LnBtLlBvc3RTY3JpcHQ8L3N0cmluZz4KCQkJPGtleT5jb20uYXBwbGUucHJpbnQudGlja2V\n"
+ "0Lml0ZW1BcnJheTwva2V5PgoJCQk8YXJyYXk+CgkJCQk8ZGljdD4KCQkJCQk8a2V5PmNvbS5hcH\n"
+ "BsZS5wcmludC5QYXBlckluZm8ucHBkLlBNUGFwZXJOYW1lPC9rZXk+CgkJCQkJPHN0cmluZz5VU\n"
+ "yBMZXR0ZXI8L3N0cmluZz4KCQkJCQk8a2V5PmNvbS5hcHBsZS5wcmludC50aWNrZXQuY2xpZW50\n"
+ "PC9rZXk+CgkJCQkJPHN0cmluZz5jb20uYXBwbGUucHJpbnQucG0uUG9zdFNjcmlwdDwvc3RyaW5\n"
+ "nPgoJCQkJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2tldC5tb2REYXRlPC9rZXk+CgkJCQkJPG\n"
+ "RhdGU+MjAwMy0wNy0wMVQxNzo0OTozNlo8L2RhdGU+CgkJCQkJPGtleT5jb20uYXBwbGUucHJpb\n"
+ "nQudGlja2V0LnN0YXRlRmxhZzwva2V5PgoJCQkJCTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8\n"
+ "L2RpY3Q+CgkJCTwvYXJyYXk+CgkJPC9kaWN0PgoJCTxrZXk+Y29tLmFwcGxlLnByaW50LnRpY2t\n"
+ "ldC5BUElWZXJzaW9uPC9rZXk+CgkJPHN0cmluZz4wMC4yMDwvc3RyaW5nPgoJCTxrZXk+Y29tLm\n"
+ "FwcGxlLnByaW50LnRpY2tldC5wcml2YXRlTG9jazwva2V5PgoJCTxmYWxzZS8+CgkJPGtleT5jb\n"
+ "20uYXBwbGUucHJpbnQudGlja2V0LnR5cGU8L2tleT4KCQk8c3RyaW5nPmNvbS5hcHBsZS5wcmlu\n"
+ "dC5QYXBlckluZm9UaWNrZXQ8L3N0cmluZz4KCTwvZGljdD4KCTxrZXk+Y29tLmFwcGxlLnByaW5\n"
+ "0LnRpY2tldC5BUElWZXJzaW9uPC9rZXk+Cgk8c3RyaW5nPjAwLjIwPC9zdHJpbmc+Cgk8a2V5Pm\n"
+ "NvbS5hcHBsZS5wcmludC50aWNrZXQucHJpdmF0ZUxvY2s8L2tleT4KCTxmYWxzZS8+Cgk8a2V5P\n"
+ "mNvbS5hcHBsZS5wcmludC50aWNrZXQudHlwZTwva2V5PgoJPHN0cmluZz5jb20uYXBwbGUucHJp\n"
+ "bnQuUGFnZUZvcm1hdFRpY2tldDwvc3RyaW5nPgo8L2RpY3Q+CjwvcGxpc3Q+CjhCSU0D6QAAAAA\n"
+ "AeAADAAAASABIAAAAAALeAkD/7v/uAwYCUgNnBSgD/AACAAAASABIAAAAAALYAigAAQAAAGQAAA\n"
+ "ABAAMDAwAAAAF//wABAAEAAAAAAAAAAAAAAABoCAAZAZAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA\n"
+ "AAAAAAAAAAAADhCSU0D7QAAAAAAEABIAAAAAQABAEgAAAABAAE4QklNBCYAAAAAAA4AAAAAAAAA\n"
+ "AAAAP4AAADhCSU0EDQAAAAAABAAAAB44QklNBBkAAAAAAAQAAAAeOEJJTQPzAAAAAAAJAAAAAAA\n"
+ "AAAABADhCSU0ECgAAAAAAAQAAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZm\n"
+ "YAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAA\n"
+ "QAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP//\n"
+ "//////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA///\n"
+ "//////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQ\n"
+ "QeAAAAAAAEAAAAADhCSU0EGgAAAAADRQAAAAYAAAAAAAAAAAAAAGQAAABkAAAACABEAFMAQwAwA\n"
+ "DIAMwAyADUAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAGQAAABkAAAAAAAAAAAA\n"
+ "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHN\n"
+ "PYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAA\n"
+ "AAQnRvbWxvbmcAAABkAAAAAFJnaHRsb25nAAAAZAAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABA\n"
+ "AAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAG\n"
+ "b3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQA\n"
+ "AAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAA\n"
+ "BUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAZAAAAABSZ2h0bG9uZ\n"
+ "wAAAGQAAAADdXJsVEVYVAAAAAEAAAAAAABudWxsVEVYVAAAAAEAAAAAAABNc2dlVEVYVAAAAAEA\n"
+ "AAAAAAZhbHRUYWdURVhUAAAAAQAAAAAADmNlbGxUZXh0SXNIVE1MYm9vbAEAAAAIY2VsbFRleHR\n"
+ "URVhUAAAAAQAAAAAACWhvcnpBbGlnbmVudW0AAAAPRVNsaWNlSG9yekFsaWduAAAAB2RlZmF1bH\n"
+ "QAAAAJdmVydEFsaWduZW51bQAAAA9FU2xpY2VWZXJ0QWxpZ24AAAAHZGVmYXVsdAAAAAtiZ0Nvb\n"
+ "G9yVHlwZWVudW0AAAARRVNsaWNlQkdDb2xvclR5cGUAAAAATm9uZQAAAAl0b3BPdXRzZXRsb25n\n"
+ "AAAAAAAAAApsZWZ0T3V0c2V0bG9uZwAAAAAAAAAMYm90dG9tT3V0c2V0bG9uZwAAAAAAAAALcml\n"
+ "naHRPdXRzZXRsb25nAAAAAAA4QklNBBEAAAAAAAEBADhCSU0EFAAAAAAABAAAAAE4QklNBAwAAA\n"
+ "AACfkAAAABAAAAZAAAAGQAAAEsAAB1MAAACd0AGAAB/9j/4AAQSkZJRgABAgEASABIAAD/7QAMQ\n"
+ "WRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUT\n"
+ "ExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4\n"
+ "ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZA\n"
+ "MBIgACEQEDEQH/3QAEAAf/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBA\n"
+ "QEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEy\n"
+ "BhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80Y\n"
+ "nlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBT\n"
+ "UBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTV\n"
+ "KMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/\n"
+ "2gAMAwEAAhEDEQA/APLtso1NRc0vP0Rok8NYyPEfijOG2ljBoAJPxKFppZtbS4Rz38kV+OPRDge\n"
+ "T89EPHBfvLjtb3P8A30K/j47cgsrYNxGpPYJpK8RtyXUlvPfsobV0GV0uippLiX3EaMb2/rKgMB\n"
+ "1ghoiNST4BESCjjLmxqmKtvxiXQ0cd0q8E2bjIDWjk9z5I8QW8JaoHcdkUePZJtZD9p8YU/Rsc/\n"
+ "wBNjS5zjDWjUk+SSKYaJLYq+qWeYGQ5lBPLJ3OA8wz2/wDSWni/U3H2AXW2l2oloa0f9LcjSLeU\n"
+ "hJdb/wAyqd387Zt+DZ5SSpVh/9DzO6dw7gGPuVn6ft/kyPkqwlxjw1Rnh24QNWjUeR5TSuDc6bg\n"
+ "fatpsJZQ3sNC4rWfkVYpbi4LAb3aANEkFLp7GHGYxuhAj4K/hYVNDjYGzZ++eSSoSbLZjGgwxul\n"
+ "XNrPqO35FukdmzyXOQeqtqwqRg4o/SOAN9ng3/AMzW02txZ9I+ZHKr241UOcWDaz3uLtSSPEpWu\n"
+ "rR5XPeylmyNr4BIPPCyH2Oc6T8kXNvddkPe/VzjJPxQAJMKeIoNScrPk2MbfddXUNXvcGtPx0Xb\n"
+ "dJ6NXjOD2Dfdw6w9v5LFW+q/1WLA3Ly9LSJaz91p/wDRjl2lOLWwAMbEJErWjRgESYieVdZhsMF\n"
+ "wMt08ldrx/vVivHaOdSgCoud9krmElpba93ASTlr/AP/R83ohr97voiJV/Fq9QvsI+mdPgs1thc\n"
+ "BWO5C38CoOY1g78qOejLiGvknxLAyGtExp5K9uzGt9RrNw7DhRfQKKx6bZIGgPj4rPycLqWVtIs\n"
+ "JGu5skDyTBRZtQNrb1fU8xrtpBaO4MLQxcx1sNuEjt5rMGJR9noY5hF7Wxa8aAnxVvDb6bgHH2z\n"
+ "omk0e64ajUUXnev9Idi5rrWAux7SXNd4E/muS+rHSjm9VbPtZjj1CSJBI+g3+0uh69b+iDG/QcD\n"
+ "u0nQCeFP6l0MZhWX/AJ1xM+QafY1TQlY1a+WABsdXp8Sp27aBH+vZaVbC0ADlVcASwtdOolp8Ct\n"
+ "BjmtGv0uI8EmJmxkIjWkmPEKLSPiidxIgJKRbDPCSN5pJyH//S87uw/suZ6c72iC13kVs9PdDmk\n"
+ "KllVziV3cuafc7yP0QjYFh26cqM6hsxAjIj6u6xzbDHh3R663AaceH+5BwdruVp2PqZUA0a9yo6\n"
+ "DPQajscnXb8YQdzC8H909joiZttoxoBIa4gGOQ3uqh+z1RuD2Ds4j2n+39FNKaFMevS/p5LPpSA\n"
+ "I8/b/ABW70THZXj11VjaIAIHgFl5VdD8AneDMaec6Lb6QAKmu7x+VSw2a3MdF/rF9YKeh4B9OHZ\n"
+ "lpAprPH8p7v5DFwrPrV9YDa645toLjMaFo8mtcEvrhkWZHXbg7T0Q2to8o3f8AfkarEitlVTKnY\n"
+ "ra992QQ2wOfG57H2bg7H2fzbFKA130P6n9dHWemCx5H2mk7LgPH818f8IuhAka6ea8y/wAWrcod\n"
+ "VyrceRhBsPae5J/Qj+sxq9KDpMuMuKBCEntnny/1CSaWxM6pIKf/0/MvtF3pCrefTBnbOi1elP3\n"
+ "Et8Vi+Sv9LuNd4HwTSNGSEqkLerwtwn+SYKtOeS4A8Krh2j1D/K1Vu4B5gaDmVDJtAr7zYYGoRB\n"
+ "QDWQ4c9h/csuyjI3fobnDyJR8fF6ltcTaXRwCkAuAsfMGr1TGFNdTmEwLWS2dIK6npLgK2T4Lle\n"
+ "pUZxoc9+6K4eR5NO5bPT73NoqIfoILT/JcFJDZr8zGiNXnvrfiur6/Y8tht7WvaexgbXf8AUrFt\n"
+ "8IExyvRusYDOsYTAIbfWdzHRJ8HN/tLj7OgdRZawmreHP2gt9wEfvtH0f7SkDXe7+o+AOn9DquL\n"
+ "f0mV+leQPH6H+axafUvrB07ptJtyshtTZDTEudJ7bWS5V6MmyltVLn7ht2hwECQP+isb60/Vqvr\n"
+ "tbLsa1lObVIJd9Gxv5rXx9F7fzHpIbf/jgfVnd/TLYj6XoOhJcP/zE+sOzd6dW7dt2eo3dH7/9R\n"
+ "JJWj//U8uiGFx76BFZLQ2xvLeVGAWQrFDJbtKBSHd6blNura4H3BbDXB7InVcZXZdh2bmTt7hbO\n"
+ "J1dj2gzCjlFnhPod3WLHB+n3o9ZsAkFVMfMrs7orLmgkHUdkyqZQQWWQbLGlrjMjUeSrfV3Ltsw\n"
+ "30EBzcd5YCedvLETJya66nWOIAaCVnfU/KuZn21CDVa02PngQdHf9LapMfVhzkaAPUUW3M91YaR\n"
+ "3YDJ+WiBmZGazPo9Kttdt2j63E6s/fft/d/NWjXkMra7KtO2qkE6cErHpvsyMmzPu0dY4Bg/dYP\n"
+ "otTpyoaMUI2XUya8tzG/pi0NMtICo/bsut21gdcWclkj5OncxaDrw6kM+9QxQzaWRAGii4pDqzC\n"
+ "MT02aX7WzPU9b7PrG3bvO6P6yStfZm+pHnPySS4590+3jf/V8yb+CsUbp8uyz0kDskbu2dmz9J8\n"
+ "lSt9Ld+gn1O8cKikmxXydbH+3bhsmfwWj/lONYlcwkhL6L4bfpOxn/tD0/wBN/N944Wh9VJm/b/\n"
+ "O+347df+/rl0k+O38GLJ83X/CfTOt7v2dV6P8AMbx6njHb/pKuN3pN2+IXnaSjybr8e31fUqd+0\n"
+ "Sj487DHMryZJMXjq+sfpPX84SXk6SSX/9kAOEJJTQQhAAAAAABVAAAAAQEAAAAPAEEAZABvAGIA\n"
+ "ZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAEwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAA\n"
+ "gADcALgAwAAAAAQA4QklNBAYAAAAAAAcABQAAAAEBAP/hFWdodHRwOi8vbnMuYWRvYmUuY29tL3\n"
+ "hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prY\n"
+ "zlkJz8+Cjw/YWRvYmUteGFwLWZpbHRlcnMgZXNjPSJDUiI/Pgo8eDp4YXBtZXRhIHhtbG5zOng9\n"
+ "J2Fkb2JlOm5zOm1ldGEvJyB4OnhhcHRrPSdYTVAgdG9vbGtpdCAyLjguMi0zMywgZnJhbWV3b3J\n"
+ "rIDEuNSc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi\n"
+ "1yZGYtc3ludGF4LW5zIycgeG1sbnM6aVg9J2h0dHA6Ly9ucy5hZG9iZS5jb20vaVgvMS4wLyc+C\n"
+ "gogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXVpZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05\n"
+ "MWQ1NDAzZjkyZjknCiAgeG1sbnM6cGRmPSdodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvJz4\n"
+ "KICA8IS0tIHBkZjpTdWJqZWN0IGlzIGFsaWFzZWQgLS0+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCi\n"
+ "A8cmRmOkRlc2NyaXB0aW9uIGFib3V0PSd1dWlkOjIyZDAyYjBhLWIyNDktMTFkYi04YWY4LTkxZ\n"
+ "DU0MDNmOTJmOScKICB4bWxuczpwaG90b3Nob3A9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9z\n"
+ "aG9wLzEuMC8nPgogIDwhLS0gcGhvdG9zaG9wOkNhcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwvcmR\n"
+ "mOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGEtYj\n"
+ "I0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcD0naHR0cDovL25zLmFkb2JlL\n"
+ "mNvbS94YXAvMS4wLyc+CiAgPCEtLSB4YXA6RGVzY3JpcHRpb24gaXMgYWxpYXNlZCAtLT4KIDwv\n"
+ "cmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gYWJvdXQ9J3V1aWQ6MjJkMDJiMGE\n"
+ "tYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5JwogIHhtbG5zOnhhcE1NPSdodHRwOi8vbnMuYW\n"
+ "RvYmUuY29tL3hhcC8xLjAvbW0vJz4KICA8eGFwTU06RG9jdW1lbnRJRD5hZG9iZTpkb2NpZDpwa\n"
+ "G90b3Nob3A6MjJkMDJiMDYtYjI0OS0xMWRiLThhZjgtOTFkNTQwM2Y5MmY5PC94YXBNTTpEb2N1\n"
+ "bWVudElEPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiBhYm91dD0ndXV\n"
+ "pZDoyMmQwMmIwYS1iMjQ5LTExZGItOGFmOC05MWQ1NDAzZjkyZjknCiAgeG1sbnM6ZGM9J2h0dH\n"
+ "A6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvJz4KICA8ZGM6ZGVzY3JpcHRpb24+CiAgIDxyZ\n"
+ "GY6QWx0PgogICAgPHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz4gICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOmRlc2NyaXB0aW9\n"
+ "uPgogPC9yZGY6RGVzY3JpcHRpb24+Cgo8L3JkZjpSREY+CjwveDp4YXBtZXRhPgogICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA\n"
+ "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg\n"
+ "ICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI\n"
+ "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAg\n"
+ "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA\n"
+ "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgIC\n"
+ "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0ndyc/P\n"
+ "v/uAA5BZG9iZQBkQAAAAAH/2wCEAAQDAwMDAwQDAwQGBAMEBgcFBAQFBwgGBgcGBggKCAkJCQkI\n"
+ "CgoMDAwMDAoMDAwMDAwMDAwMDAwMDAwMDAwMDAwBBAUFCAcIDwoKDxQODg4UFA4ODg4UEQwMDAw\n"
+ "MEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAGQAZAMBEQACEQEDEQ\n"
+ "H/3QAEAA3/xAGiAAAABwEBAQEBAAAAAAAAAAAEBQMCBgEABwgJCgsBAAICAwEBAQEBAAAAAAAAA\n"
+ "AEAAgMEBQYHCAkKCxAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIj\n"
+ "wVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uP\n"
+ "zxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaX\n"
+ "mJmam5ydnp+So6SlpqeoqaqrrK2ur6EQACAgECAwUFBAUGBAgDA20BAAIRAwQhEjFBBVETYSIGc\n"
+ "YGRMqGx8BTB0eEjQhVSYnLxMyQ0Q4IWklMlomOywgdz0jXiRIMXVJMICQoYGSY2RRonZHRVN/Kj\n"
+ "s8MoKdPj84SUpLTE1OT0ZXWFlaW1xdXl9UZWZnaGlqa2xtbm9kdXZ3eHl6e3x9fn9zhIWGh4iJi\n"
+ "ouMjY6Pg5SVlpeYmZqbnJ2en5KjpKWmp6ipqqusra6vr/2gAMAwEAAhEDEQA/APBnplwPAdR+GB\n"
+ "KY6dYtNG1w39yh4+xb+zIksgEfFaRSSoIx8f7RPRRkSWQimM+lRmwWVXFWYigHxUUVoMiJM+Fj0\n"
+ "tg0RBegLE0Wu+3c+GTBazFCGI7HtSp9slbFYYzyoBsegw2hY1Afl3wqqRqahk+0tDgKpgu4DAUU\n"
+ "+HY+GRS2ePiMKtUB3G+KGuONq//Q8OzpFbW5WnxMop4k9crG5ZnZNJkEOn21utVRYw7HxZtz+OR\n"
+ "vdsrZ2lRtci4aVxFEQA0neg/ZXxJpTITNNuOFss0vSotYNvZ2qGRkPKSTqiU8Sdqk5SZU5Ix8XJ\n"
+ "NNZ8k6bp8TtM73OputUtYq0Unux/hkRkJOzZLCAN2KR+VpbtSkCBaDnIzdlWu59u+XeJTjeASk8\n"
+ "+juZOESEAVqx8BvU/PJibScTrTy09560hkWOGFd2YgFnPQKD19zhOSkxw2l8Vm6XAiYb8gg+k5O\n"
+ "9mnhoon9H3cs5s7WF5pp29OGGMFndyaAKBuTiEEPQLD8h/NDmNdYlttNkYjlbFjcXCr3LLH8II8\n"
+ "C2WUGviZvon/OPWkm3RNSv72SYllMkKxQRV67CQMSKYQAxMkR/wBC56d61P0heel4cYuVOXWvTp\n"
+ "h4Qjjf/9Hw5qBYyISaqjBV+QpvkAzKcki4HomnIxck/wBhtlR2bhunvlDywddMUl4zW+kQ9FQ8X\n"
+ "nfuSewrtmPkycPvc/DhMhvyegXOrWWhmLQPKlsj6xIAiLCoZkY96nv7npmJvI2XOjQFMl0fyRqM\n"
+ "NoxvZvrGt33wlATwiMnVnY1LEdSfuyXF3KIDmUu88w2XlnTl8raAlb2ZFfVL0jdYRtQnxc7BfDC\n"
+ "OaJR7nm3me5tdOtjbMvp3ZRXkV6chVQRX79hmVjgZG+jgZ5jHGhzecXF5LPL6jEjstSSaDM51Ka\n"
+ "6MZ9S1C0sEBe8uZo4YCBXdjxGw60wEWyEqfUHkT8vLXRJFuLdTcaqfhlvWUErtukZ3ABPUjIXTE\n"
+ "m3rGmeV2Tk5UKz/AG/E/wAcgZKya20C3b02kjYtH8AqCygbkUH0nLYlgUb+gbWtPbpXt/n2ybB/\n"
+ "/9Lw4oaVxGd+PxH3qBkGaY3KyiSP01IkiUclH8sg+LKydm6INvZvKsFu+kWtvD8LRoFNRup6moO\n"
+ "aqd277HsGW+XPLmn6XM17FF6l7vW4fd2Zuu+RFls2tmUNrLJb7TSBertGQGqetDkxE0na0pvtHs\n"
+ "QkszWyiGAG5laYlnkeMVHJj8sA5rPk+SvMepTalqlxd3B5zTOXdj/MxqafLpm5xioh5nPK5kpRG\n"
+ "pkcKAST0A6k5NpfUP5K/ki1ssHmHzF+71KRQ8Nud/Qibb/kYw6/yjbrXISlSH07YaHbWyxx2kXE\n"
+ "KACB2zHJtLI7XSelBRvH2xCpvaaTDHXkOTVBPcUG2479RlsdmJVPRtvV+ylenQ0y62FP/9PxRpo\n"
+ "WG5FxKKxKFDA+GVS5NsebLdFsRePc3siVW4f4QR0QVAGYeSXR2unhtZ6s60K6jt+MMSFwtF2+xX\n"
+ "wr7eGUGLlRPQMsE2vxQm7itxKg3VCfT2+nb8cDYaCDtfOXmCCcROrQrUhkkCHYn6emRMqZxjbLd\n"
+ "F1+W/4xajHzjNCtQKMffETWUdngX5p+QZ9A8xS6hbo0ui37NNDPT7DOalHpsCD08Rmyw5ARTpdV\n"
+ "gIPEF35MeRn80ed4S5EdrpKm9kZ15K0iH92hB7Me/tmS60vt/QrCYyekiBdgSTXcjqV9q9MokFD\n"
+ "N7S3aFVVR8RoK9zldqndvAY6nffr/AGYQqLhjdpCoIAZW22HavU/LJBUP9WblX0xTw7fOmWsX/9\n"
+ "Tw7FdvMqWkQ3Z1qfED+mQIbI77PX/LFis9vBajZm2Y+x65rMh3t30Bsze400aVaIbSLk6r8CMRT\n"
+ "l/NmOcllnGDD9Y8uecNfEEiXrMgDGWAyGOOu5WlB+vMrHODTlxZCdjsyFdB006VpVtLasurQxBL\n"
+ "64WiLI4/aFT1ANOXemV5piR2b9NiljB4yyHy9CLOVI5GJhB+CvXY9R8xmINzs5HNZ+Z96BZpbxA\n"
+ "fVJo39UFefwopYgL4nMiMd2qZoIn/AJx00u3t/Lt7qpp9Yv5GLf5MUTERqfbvmzBeezjd9H+VlL\n"
+ "wSQzBqsvOGQD7L12rXsemPNxmXQSxxIPU2nFV4HYqR1xEUWj4ZAxBryr2G+J2VGDZlLrxUH6KZA\n"
+ "Fkqb15VFelfwy+2FP8A/9Xxlf6AdA182Yk9eFeLxSjoVfcfSMo4uIOfkweFOnpvlWYrLEwNFAA+\n"
+ "nMOYdrhFvQLeSO7coBXiK8iKiv07Zj8Ac4QtNrW1njUcKcT+yAR/xGmR4WcsStLpTuPU9IFaEsV\n"
+ "BP3k4m2AgBzSwyQNcIwNTE1aI3wnam9O2Ug7s5Ckk/NDndeVXa2H78MqqV6jmeBp9+ZWKXqDjZ4\n"
+ "+gvVvy30qCy0qzsLRBCnBI2VdgUTqPvOZ7y+Q7pz+bn5q6d+VflZxZlJ/NN4ypptk5qtB9qRwDX\n"
+ "gn/AAx2y2ItpfKFv+eH5qNeTajJ5ovVaVywSqvEtTUKqupAA6D2y0BNPtv/AJx//M5PzL8mJeXT\n"
+ "L+ndPf6rqarSpkAqsnEAAeoN6DpkJRYci9lROSgSUUH9o9K5Tw0ztfSHnXkOtK9q+PHwydq//9b\n"
+ "yxrVoZNBtNSA5zRMPXmH8j0CLXuBmHE+qneamHpEuqYeV7pzFVTRgQK5XMNmnlb1vyyY5QA1OwJ\n"
+ "+eUF2seTOLu5s7azVIVAkpVn/hhnIALG73Yz5jvb1dICqzpDNIqyFD8SxH7R28cxibZCiWOsdJs\n"
+ "PTM6XNstPhnkjIhcHuJBVfvOCiUSn0TfWrTTLjyw8guA/PifTO3xcxxA8a5ZAbimvJP0m3p/kFF\n"
+ "WxhmpWQJ9NW3zZPHz5vlb/nIDVbrWfzO1RJhxGnpDaRL/khA1T7ktmSOTAJhZaAUtLawsbayl8v\n"
+ "xWi3Gpay0cF3HPcFRJJHJMXVrcJ8UaAFG5LWjF8tAYW9H/wCcOo9bTzxrt/owkTyksZW5gkIKvI\n"
+ "7k26nvyReRJHyyBWT7dWQyOWlbnK2526e1O1MqIUFE84uPLkOdK9RXI0E2/wD/1/DA1bURZLY/W\n"
+ "ZDZqwb0eXw7dMgIi7bjllVXsz7yNcfWC0Vd3Ip92Y2UOz0cnsPlwyx8xQ/u24sMxCadoJp9LOXk\n"
+ "VX/uwRUE0BI8cokbLMyoKouHu2MaKGXw7fLDwgoGSkbHpaNZyLLHRSKcFFQQRvUdMlwUFOQyLzr\n"
+ "ztpCaba6fPau4ijv4OURY8AjVFKV7ZZiO+7Vnh6XvXkSWNbW2WTb92KDxIFMzwHlZc3zX+fuizW\n"
+ "f5p3ty8XGDU4YLmCQiisyII3+4rvl8UB5ffEghRGvOm7AbnvWvjk1fen/ONPldPKP5aWOpPCfr2\n"
+ "uE31y6q2wbaMEn+VAMDSdyzrzj+avlHyTp0l/r2rxWFuHWJuIeacu4qFCRgsajfBwsty89/6Gr/\n"
+ "ACa9an+JL/hSnrfoubhXwpXpjwhaL//Q8E1AqtcAZMs8l6i1nqMa1oSVP0VynKLDmaWdSfQXl69\n"
+ "jF1Jv8MhDb5rpB3AO7INRRLhhGp4R05FgaGvTMU8200xS70zVDMRp2pTIOvBmB3PgQP15kxIcnD\n"
+ "LH/EEz0rRvOJhldr9pQtCqyd6VrShGTqw5d4ARv9jHfOGl+ZJNMluLkyenaFbiRdqFYW5nrWuwO\n"
+ "MKB5MdSMRxnhlu9N8p6lLFpti63FUjCtFJTrDKvse2bEDZ4XJ9RZB+YPli2/Mjy5bxoUi1a0YS2\n"
+ "85UOwIXiy9jRu+TBppfOF1+V3m22vrdpNPM8cs/oo0VJlUqQPjValR3+IZNNvtLS9Yu9Mi0/TJr\n"
+ "kyp6QhWVVCIWRATsKBemwwFrDzT87fybs/wA1bW21PRb+DTvNlgGSRp6iC8i3KJJx+y6n7D0Pwm\n"
+ "hxBZXT55/6Fi/Nf0PW+qWXq+t6X1X67F6vD/ftK04V/wBl344U8b//0fBapxheVh9ocV+nviqY2\n"
+ "/qQJDew/bioWHiuQ8m0bbvaPKGtQ6jaxSo9JloCK75gZI0Xb4sgkHo8MouoAvP94BsRmGY7uWJU\n"
+ "gzbypOQpNOvIdK4Nw2WCE2tXulTkjEEbdafgclxMhFBas93dwyQzsWDghlJFONKHJCZtjOFBJfy\n"
+ "j1y9vPL9zpbIs0WkXL2sUjA8hDXlGCRXtt07ZuYvL5KJeo6bfajbkzWkcToR8dqshZ6in2fhNK/\n"
+ "PDTUlXmHVvMdr5o0v9H2kdrqGpfu7m0nkY87Uf7tkKAU4/s03ynLkEBbfihx7dGT6va67LbRMNR\n"
+ "aKOBuUTKgIBXoK1BOYR1M3aQ0mOt9yxUeZNdtJhFapLqMluSXkg5oxJrUMW5KevQ9MmNXXNqOiH\n"
+ "Rr/Hmv8A1r9I/oj95w+r+j9Yf1+NP5+nXtTD+dF8tkfkOlv/0vC3ph7f0/alcVTbS4A8QibuKb5\n"
+ "RI05EBYRFpdX3ly79a2qYCavH/EY7TCYyMD5PSdD8+wXUSn1ArDqOhBzFlipz4ZwWbaV5htbsgF\n"
+ "qg9crMXKErGyYwajFGzxyHlGSePbbwyqg5UZlCaxrFpaWU95LIqrEjMAT4Dp9OShGy1ZslBhv/A\n"
+ "Dj9rd/a+aL+xUK+m38L3d0HrxRo2HFtu5D8c27y8t30raarbWkU+u6g4gsNORn+EcUaSh2Pc0/4\n"
+ "lgtAjezzbT9SutY1i782al8Nxdyotqh6xWybIg+jc5q8s+I27bFDgFPQp9RE+nrag70+L6crrZu\n"
+ "4jajokdv6LW/Dii1Wo61PXKQN3KPK0L+h4/rnD/K5V78a5LhXxd3/0/DMXXtwxVNtL9Xkaf3f7N\n"
+ "etfbKMjdjtkZ9D6ufrlK0+HpX8coF9HJ26sXvfqXrf7i/U+uften/d/wCyrmQL6uOav0pvpP8Ai\n"
+ "b1F+rV59+vH6a5XLhcjH4nRmY/xpxHP0/UptWvT6Mx/RbmjxWK+aP8AFf1M/pCv1Kvxen9inavf\n"
+ "MrFwXtzcLUeLXq5Mv/I3nz1b0v8AjofuKVry9KrUpTanOlf9jmQ68va/zH9b/COn/o7/AI431mP\n"
+ "65SvLh+zWvbl9rMfNfC34K4kmj9T6lD6FKclp/DNYXZx5srsPrHor6nXvkgxTPS/U+rv6dPU5mt\n"
+ "fngFN5ulv+l/pL/Lp/scerHo//2Q==\n";
+
+static std::string gCommandLine;
+
+TEST(Base64, LargeSample) {
+ LOG(LS_VERBOSE) << "Testing specific base64 file";
+
+ char unescaped[64 * 1024];
+
+ // unescape that massive blob above
+ size_t size = Base64Unescape(SpecificTest,
+ sizeof(SpecificTest),
+ unescaped,
+ sizeof(unescaped));
+
+ EXPECT_EQ(size, sizeof(testbase64));
+ EXPECT_EQ(0, memcmp(testbase64, unescaped, sizeof(testbase64)));
+}
+
+bool DecodeTest(const char* encoded, size_t expect_unparsed,
+ const char* decoded, Base64::DecodeFlags flags)
+{
+ std::string result;
+ size_t consumed = 0, encoded_len = strlen(encoded);
+ bool success = Base64::DecodeFromArray(encoded, encoded_len, flags,
+ &result, &consumed);
+ size_t unparsed = encoded_len - consumed;
+ EXPECT_EQ(expect_unparsed, unparsed) << "\"" << encoded
+ << "\" -> \"" << decoded
+ << "\"";
+ EXPECT_STREQ(decoded, result.c_str());
+ return success;
+}
+
+#define Flags(x,y,z) \
+ Base64::DO_PARSE_##x | Base64::DO_PAD_##y | Base64::DO_TERM_##z
+
+TEST(Base64, DecodeParseOptions) {
+ // Trailing whitespace
+ EXPECT_TRUE (DecodeTest("YWJjZA== ", 1, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(WHITE, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA== ", 0, "abcd", Flags(ANY, YES, CHAR)));
+
+ // Embedded whitespace
+ EXPECT_FALSE(DecodeTest("YWJjZA= =", 3, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(WHITE, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA= =", 0, "abcd", Flags(ANY, YES, CHAR)));
+
+ // Embedded non-base64 characters
+ EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_FALSE(DecodeTest("YWJjZA=*=", 3, "abcd", Flags(WHITE, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA=*=", 0, "abcd", Flags(ANY, YES, CHAR)));
+
+ // Unexpected padding characters
+ EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(STRICT, YES, CHAR)));
+ EXPECT_FALSE(DecodeTest("YW=JjZA==", 7, "a", Flags(WHITE, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YW=JjZA==", 0, "abcd", Flags(ANY, YES, CHAR)));
+}
+
+TEST(Base64, DecodePadOptions) {
+ // Padding
+ EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA==", 0, "abcd", Flags(STRICT, ANY, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA==", 2, "abcd", Flags(STRICT, NO, CHAR)));
+
+ // Incomplete padding
+ EXPECT_FALSE(DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, ANY, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA=", 1, "abcd", Flags(STRICT, NO, CHAR)));
+
+ // No padding
+ EXPECT_FALSE(DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, YES, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, ANY, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJjZA", 0, "abcd", Flags(STRICT, NO, CHAR)));
+}
+
+TEST(Base64, DecodeTerminateOptions) {
+ // Complete quantum
+ EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, BUFFER)));
+ EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJj", 0, "abc", Flags(STRICT, NO, ANY)));
+
+ // Complete quantum with trailing data
+ EXPECT_FALSE(DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, BUFFER)));
+ EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJj*", 1, "abc", Flags(STRICT, NO, ANY)));
+
+ // Incomplete quantum
+ EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, BUFFER)));
+ EXPECT_FALSE(DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, CHAR)));
+ EXPECT_TRUE (DecodeTest("YWJ", 0, "ab", Flags(STRICT, NO, ANY)));
+}
diff --git a/talk/base/basicpacketsocketfactory.cc b/talk/base/basicpacketsocketfactory.cc
index 42721ba..d1da08e 100644
--- a/talk/base/basicpacketsocketfactory.cc
+++ b/talk/base/basicpacketsocketfactory.cc
@@ -157,7 +157,8 @@
} else {
// Otherwise, try to find a port in the provided range.
for (int port = min_port; ret < 0 && port <= max_port; ++port) {
- ret = socket->Bind(talk_base::SocketAddress(local_address.ip(), port));
+ ret = socket->Bind(talk_base::SocketAddress(local_address.ipaddr(),
+ port));
}
}
return ret;
diff --git a/talk/base/basictypes.h b/talk/base/basictypes.h
index 4193ede..4840631 100644
--- a/talk/base/basictypes.h
+++ b/talk/base/basictypes.h
@@ -25,12 +25,12 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef TALK_BASE_BASICTYPES_H__
-#define TALK_BASE_BASICTYPES_H__
+#ifndef TALK_BASE_BASICTYPES_H_
+#define TALK_BASE_BASICTYPES_H_
#include <stddef.h> // for NULL, size_t
-#ifndef WIN32
+#if !(defined(_MSC_VER) && (_MSC_VER < 1600))
#include <stdint.h> // for uintptr_t
#endif
@@ -40,7 +40,7 @@
#include "talk/base/constructormagic.h"
-#ifndef INT_TYPES_DEFINED
+#if !defined(INT_TYPES_DEFINED)
#define INT_TYPES_DEFINED
#ifdef COMPILER_MSVC
typedef unsigned __int64 uint64;
@@ -53,7 +53,18 @@
#endif
#define INT64_F "I64"
#else // COMPILER_MSVC
-#ifdef __LP64__
+// On Mac OS X, cssmconfig.h defines uint64 as uint64_t
+#if defined(OSX)
+typedef uint64_t uint64;
+typedef int64_t int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## L
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UL
+#endif
+#define INT64_F "l"
+#elif defined(__LP64__)
typedef unsigned long uint64;
typedef long int64;
#ifndef INT64_C
@@ -80,7 +91,7 @@
typedef unsigned short uint16;
typedef short int16;
typedef unsigned char uint8;
-typedef char int8;
+typedef signed char int8;
#endif // INT_TYPES_DEFINED
#ifdef WIN32
@@ -102,15 +113,10 @@
#define CPU_X86 1
#endif
-#ifdef WIN32
-#define alignof(t) __alignof(t)
-#else // !WIN32
-#define alignof(t) __alignof__(t)
-#endif // !WIN32
-#define IS_ALIGNED(p, a) (0==(reinterpret_cast<uintptr_t>(p) & ((a)-1)))
#define ALIGNP(p, t) \
(reinterpret_cast<uint8*>(((reinterpret_cast<uintptr_t>(p) + \
((t)-1)) & ~((t)-1))))
+#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1)))
#ifndef UNUSED
#define UNUSED(x) Unused(static_cast<const void *>(&x))
@@ -127,4 +133,9 @@
#define GCC_ATTR(x)
#endif // !__GNUC__
-#endif // TALK_BASE_BASICTYPES_H__
+// Use these to declare and define a static local variable (static T;) so that
+// it is leaked so that its destructors are not called at exit.
+#define LIBJINGLE_DEFINE_STATIC_LOCAL(type, name, arguments) \
+ static type& name = *new type arguments
+
+#endif // TALK_BASE_BASICTYPES_H_
diff --git a/talk/base/buffer_unittest.cc b/talk/base/buffer_unittest.cc
new file mode 100644
index 0000000..b0aa243
--- /dev/null
+++ b/talk/base/buffer_unittest.cc
@@ -0,0 +1,160 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/buffer.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+static const char kTestData[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
+};
+
+TEST(BufferTest, TestConstructDefault) {
+ Buffer buf;
+ EXPECT_EQ(0U, buf.length());
+ EXPECT_EQ(0U, buf.capacity());
+ EXPECT_EQ(Buffer(), buf);
+}
+
+TEST(BufferTest, TestConstructEmptyWithCapacity) {
+ Buffer buf(NULL, 0, 256U);
+ EXPECT_EQ(0U, buf.length());
+ EXPECT_EQ(256U, buf.capacity());
+ EXPECT_EQ(Buffer(), buf);
+}
+
+TEST(BufferTest, TestConstructData) {
+ Buffer buf(kTestData, sizeof(kTestData));
+ EXPECT_EQ(sizeof(kTestData), buf.length());
+ EXPECT_EQ(sizeof(kTestData), buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf);
+}
+
+TEST(BufferTest, TestConstructDataWithCapacity) {
+ Buffer buf(kTestData, sizeof(kTestData), 256U);
+ EXPECT_EQ(sizeof(kTestData), buf.length());
+ EXPECT_EQ(256U, buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(Buffer(kTestData, sizeof(kTestData)), buf);
+}
+
+TEST(BufferTest, TestConstructCopy) {
+ Buffer buf1(kTestData, sizeof(kTestData), 256), buf2(buf1);
+ EXPECT_EQ(sizeof(kTestData), buf2.length());
+ EXPECT_EQ(sizeof(kTestData), buf2.capacity()); // capacity isn't copied
+ EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(buf1, buf2);
+}
+
+TEST(BufferTest, TestAssign) {
+ Buffer buf1, buf2(kTestData, sizeof(kTestData), 256);
+ EXPECT_NE(buf1, buf2);
+ buf1 = buf2;
+ EXPECT_EQ(sizeof(kTestData), buf1.length());
+ EXPECT_EQ(sizeof(kTestData), buf1.capacity()); // capacity isn't copied
+ EXPECT_EQ(0, memcmp(buf1.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(buf1, buf2);
+}
+
+TEST(BufferTest, TestSetData) {
+ Buffer buf;
+ buf.SetData(kTestData, sizeof(kTestData));
+ EXPECT_EQ(sizeof(kTestData), buf.length());
+ EXPECT_EQ(sizeof(kTestData), buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestAppendData) {
+ Buffer buf(kTestData, sizeof(kTestData));
+ buf.AppendData(kTestData, sizeof(kTestData));
+ EXPECT_EQ(2 * sizeof(kTestData), buf.length());
+ EXPECT_EQ(2 * sizeof(kTestData), buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData),
+ kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetLengthSmaller) {
+ Buffer buf;
+ buf.SetData(kTestData, sizeof(kTestData));
+ buf.SetLength(sizeof(kTestData) / 2);
+ EXPECT_EQ(sizeof(kTestData) / 2, buf.length());
+ EXPECT_EQ(sizeof(kTestData), buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData) / 2));
+}
+
+TEST(BufferTest, TestSetLengthLarger) {
+ Buffer buf;
+ buf.SetData(kTestData, sizeof(kTestData));
+ buf.SetLength(sizeof(kTestData) * 2);
+ EXPECT_EQ(sizeof(kTestData) * 2, buf.length());
+ EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacitySmaller) {
+ Buffer buf;
+ buf.SetData(kTestData, sizeof(kTestData));
+ buf.SetCapacity(sizeof(kTestData) / 2); // should be ignored
+ EXPECT_EQ(sizeof(kTestData), buf.length());
+ EXPECT_EQ(sizeof(kTestData), buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacityLarger) {
+ Buffer buf(kTestData, sizeof(kTestData));
+ buf.SetCapacity(sizeof(kTestData) * 2);
+ EXPECT_EQ(sizeof(kTestData), buf.length());
+ EXPECT_EQ(sizeof(kTestData) * 2, buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestSetCapacityThenSetLength) {
+ Buffer buf(kTestData, sizeof(kTestData));
+ buf.SetCapacity(sizeof(kTestData) * 4);
+ memcpy(buf.data() + sizeof(kTestData), kTestData, sizeof(kTestData));
+ buf.SetLength(sizeof(kTestData) * 2);
+ EXPECT_EQ(sizeof(kTestData) * 2, buf.length());
+ EXPECT_EQ(sizeof(kTestData) * 4, buf.capacity());
+ EXPECT_EQ(0, memcmp(buf.data(), kTestData, sizeof(kTestData)));
+ EXPECT_EQ(0, memcmp(buf.data() + sizeof(kTestData),
+ kTestData, sizeof(kTestData)));
+}
+
+TEST(BufferTest, TestTransfer) {
+ Buffer buf1(kTestData, sizeof(kTestData), 256U), buf2;
+ buf1.TransferTo(&buf2);
+ EXPECT_EQ(0U, buf1.length());
+ EXPECT_EQ(0U, buf1.capacity());
+ EXPECT_EQ(sizeof(kTestData), buf2.length());
+ EXPECT_EQ(256U, buf2.capacity()); // capacity does transfer
+ EXPECT_EQ(0, memcmp(buf2.data(), kTestData, sizeof(kTestData)));
+}
+
+} // namespace talk_base
diff --git a/talk/base/checks.h b/talk/base/checks.h
index 12adc25..83ad372 100644
--- a/talk/base/checks.h
+++ b/talk/base/checks.h
@@ -26,10 +26,10 @@
*/
// This module contains some basic debugging facilities.
+// Originally comes from shared/commandlineflags/checks.h
-
-#ifndef SHARED_COMMANDLINEFLAGS_CHECKS_H__
-#define SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#ifndef TALK_BASE_CHECKS_H_
+#define TALK_BASE_CHECKS_H_
#include <string.h>
@@ -41,4 +41,4 @@
#define UNREACHABLE() \
Fatal(__FILE__, __LINE__, "unreachable code")
-#endif // SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#endif // TALK_BASE_CHECKS_H_
diff --git a/talk/base/cpuid.cc b/talk/base/cpuid.cc
new file mode 100644
index 0000000..7f0326a
--- /dev/null
+++ b/talk/base/cpuid.cc
@@ -0,0 +1,140 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/cpuid.h"
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#elif defined(__ANDROID__)
+#include <cpu-features.h>
+#elif defined(LINUX)
+#include "talk/base/linux.h"
+#endif
+
+// TODO: Use cpuid.h when gcc 4.4 is used on OSX and Linux.
+#if (defined(__pic__) || defined(__APPLE__)) && defined(__i386__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+ __asm__ volatile (
+ "mov %%ebx, %%edi\n"
+ "cpuid\n"
+ "xchg %%edi, %%ebx\n"
+ : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
+ : "a"(info_type)
+ );
+}
+#elif defined(__i386__) || defined(__x86_64__)
+static inline void __cpuid(int cpu_info[4], int info_type) {
+ __asm__ volatile (
+ "cpuid\n"
+ : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3])
+ : "a"(info_type)
+ );
+}
+#endif
+
+namespace talk_base {
+
+// CPU detect function for SIMD instruction sets.
+bool CpuInfo::cpu_info_initialized_ = false;
+int CpuInfo::cpu_info_ = 0;
+// Global lock for cpu initialization.
+CriticalSection CpuInfo::crit_;
+
+#ifdef CPU_X86
+void cpuid(int cpu_info[4], int info_type) {
+ __cpuid(cpu_info, info_type);
+}
+#endif
+
+void CpuInfo::InitCpuFlags() {
+#ifdef CPU_X86
+ int cpu_info[4];
+ __cpuid(cpu_info, 1);
+ cpu_info_ = (cpu_info[2] & 0x00000200 ? kCpuHasSSSE3 : 0) |
+ (cpu_info[3] & 0x04000000 ? kCpuHasSSE2 : 0);
+#elif defined(__ANDROID__) && defined(__arm__)
+ uint64_t features = android_getCpuFeatures();
+ cpu_info_ = ((features & ANDROID_CPU_ARM_FEATURE_NEON) ? kCpuHasNEON : 0);
+#elif defined(LINUX) && defined(__arm__)
+ cpu_info_ = 0;
+ // Look for NEON support in /proc/cpuinfo
+ ProcCpuInfo proc_info;
+ size_t section_count;
+ if (proc_info.LoadFromSystem() &&
+ proc_info.GetSectionCount(§ion_count)) {
+ for (size_t i = 0; i < section_count; ++i) {
+ std::string out_features;
+ if (proc_info.GetSectionStringValue(i, "Features", &out_features)) {
+ if (out_features.find("neon") != std::string::npos) {
+ cpu_info_ |= kCpuHasNEON;
+ }
+ break;
+ }
+ }
+ }
+#elif defined(__ARM_NEON__)
+ // gcc -mfpu=neon defines __ARM_NEON__
+ // if code is specifically built for Neon-only, enable the flag.
+ cpu_info_ |= kCpuHasNEON;
+#else
+ cpu_info_ = 0;
+#endif
+ cpu_info_initialized_ = true;
+}
+
+void CpuInfo::MaskCpuFlagsForTest(int enable_flags) {
+ CritScope cs(&crit_);
+ InitCpuFlags();
+ cpu_info_ &= enable_flags;
+}
+
+bool CpuInfo::TestCpuFlag(int flag) {
+ if (!cpu_info_initialized_) {
+ CritScope cs(&crit_);
+ InitCpuFlags();
+ }
+ return cpu_info_ & flag ? true : false;
+}
+
+// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
+// See "Intel Processor Identification and the CPUID Instruction"
+// (Intel document number: 241618)
+std::string CpuInfo::GetCpuVendor() {
+#ifdef CPU_X86
+ int cpu_info[4];
+ cpuid(cpu_info, 0);
+ cpu_info[0] = cpu_info[1]; // Reorder output
+ cpu_info[1] = cpu_info[3];
+ cpu_info[2] = cpu_info[2];
+ cpu_info[3] = 0;
+ return std::string(reinterpret_cast<char *>(&cpu_info[0]));
+#else
+ return std::string("Undefined");
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/cpuid.h b/talk/base/cpuid.h
new file mode 100644
index 0000000..3688668
--- /dev/null
+++ b/talk/base/cpuid.h
@@ -0,0 +1,73 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_CPUID_H_
+#define TALK_BASE_CPUID_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+#ifdef CPU_X86
+void cpuid(int cpu_info[4], int info_type);
+#endif
+
+class CpuInfo {
+ public:
+ // These flags are only valid on x86 processors
+ static const int kCpuHasSSE2 = 1;
+ static const int kCpuHasSSSE3 = 2;
+
+ // SIMD support on ARM processors
+ static const int kCpuHasNEON = 4;
+
+ // Detect CPU has SSE2 etc.
+ static bool TestCpuFlag(int flag);
+
+ // Detect CPU vendor: "GenuineIntel" or "AuthenticAMD"
+ static std::string GetCpuVendor();
+
+ // For testing, allow CPU flags to be disabled.
+ static void MaskCpuFlagsForTest(int enable_flags);
+
+ private:
+ // Global lock for the cpu initialization
+ static CriticalSection crit_;
+ static bool cpu_info_initialized_;
+ static int cpu_info_;
+
+ static void InitCpuFlags();
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(CpuInfo);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_CPUID_H_
diff --git a/talk/base/cpuid_unittest.cc b/talk/base/cpuid_unittest.cc
new file mode 100644
index 0000000..f06178f
--- /dev/null
+++ b/talk/base/cpuid_unittest.cc
@@ -0,0 +1,86 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+
+#include "talk/base/cpuid.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+
+
+// Tests CPUID instruction for Vendor identification.
+TEST(CpuInfoTest, CpuVendorNonEmpty) {
+ EXPECT_FALSE(talk_base::CpuInfo::GetCpuVendor().empty());
+}
+
+#ifdef CPU_X86
+
+// Tests Vendor identification is Intel or AMD.
+// See Also http://en.wikipedia.org/wiki/CPUID
+TEST(CpuInfoTest, CpuVendorIntelAMD) {
+ const std::string vendor = talk_base::CpuInfo::GetCpuVendor();
+ LOG(LS_INFO) << "CpuVendor: " << vendor;
+ EXPECT_TRUE(talk_base::string_match(vendor.c_str(),
+ "GenuineIntel") ||
+ talk_base::string_match(vendor.c_str(),
+ "AuthenticAMD"));
+}
+
+// Tests CPUID maximum function number.
+// Modern CPU has 11, but we expect at least 3.
+TEST(CpuInfoTest, CpuIdMax) {
+ int cpu_info[4] = { 0 };
+ talk_base::cpuid(cpu_info, 0);
+ LOG(LS_INFO) << "CpuId Max Function: " << cpu_info[0];
+ EXPECT_GE(cpu_info[0], 1);
+}
+
+// Tests CPUID functions 0 and 1 return different values.
+TEST(CpuInfoTest, CpuId) {
+ int cpu_info0[4] = { 0 };
+ int cpu_info1[4] = { 0 };
+ talk_base::cpuid(cpu_info0, 0);
+ talk_base::cpuid(cpu_info1, 1);
+ LOG(LS_INFO) << "CpuId Function 0: " << std::hex
+ << std::setfill('0') << std::setw(8) << cpu_info0[0] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info0[1] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info0[2] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info0[3];
+ LOG(LS_INFO) << "CpuId Function 1: " << std::hex
+ << std::setfill('0') << std::setw(8) << cpu_info1[0] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info1[1] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info1[2] << " "
+ << std::setfill('0') << std::setw(8) << cpu_info1[3];
+ EXPECT_NE(memcmp(cpu_info0, cpu_info1, sizeof(cpu_info0)), 0);
+
+ LOG(LS_INFO) << "SSE2: "
+ << talk_base::CpuInfo::TestCpuFlag(talk_base::CpuInfo::kCpuHasSSE2);
+ LOG(LS_INFO) << "SSSE3: "
+ << talk_base::CpuInfo::TestCpuFlag(talk_base::CpuInfo::kCpuHasSSSE3);
+}
+#endif
+
diff --git a/talk/base/cpumonitor.cc b/talk/base/cpumonitor.cc
new file mode 100644
index 0000000..64f6147
--- /dev/null
+++ b/talk/base/cpumonitor.cc
@@ -0,0 +1,418 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/cpumonitor.h"
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/systeminfo.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <winternl.h>
+#endif
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#if defined(IOS) || defined(OSX)
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/host_info.h>
+#include <mach/task.h>
+#endif // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+#include <sys/resource.h>
+#include <errno.h>
+#include <stdio.h>
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#endif // defined(LINUX) || defined(ANDROID)
+
+#if defined(IOS) || defined(OSX)
+static uint64 TimeValueTToInt64(const time_value_t &time_value) {
+ return talk_base::kNumMicrosecsPerSec * time_value.seconds +
+ time_value.microseconds;
+}
+#endif // defined(IOS) || defined(OSX)
+
+// How CpuSampler works
+// When threads switch, the time they spent is accumulated to system counters.
+// The time can be treated as user, kernel or idle.
+// user time is applications.
+// kernel time is the OS, including the thread switching code itself.
+// typically kernel time indicates IO.
+// idle time is a process that wastes time when nothing is ready to run.
+//
+// User time is broken down by process (application). One of the applications
+// is the current process. When you add up all application times, this is
+// system time. If only your application is running, system time should be the
+// same as process time.
+//
+// All cores contribute to these accumulators. A dual core process is able to
+// process twice as many cycles as a single core. The actual code efficiency
+// may be worse, due to contention, but the available cycles is exactly twice
+// as many, and the cpu load will reflect the efficiency. Hyperthreads behave
+// the same way. The load will reflect 200%, but the actual amount of work
+// completed will be much less than a true dual core.
+//
+// Total available performance is the sum of all accumulators.
+// If you tracked this for 1 second, it would essentially give you the clock
+// rate - number of cycles per second.
+// Speed step / Turbo Boost is not considered, so infact more processing time
+// may be available.
+
+namespace talk_base {
+
+// Note Tests on Windows show 600 ms is minimum stable interval for Windows 7.
+static const int32 kDefaultInterval = 950; // Slightly under 1 second.
+
+CpuSampler::CpuSampler()
+ : min_load_interval_(kDefaultInterval)
+#ifdef WIN32
+ , get_system_times_(NULL),
+ nt_query_system_information_(NULL),
+ force_fallback_(false)
+#endif
+ {
+}
+
+CpuSampler::~CpuSampler() {
+}
+
+// Set minimum interval in ms between computing new load values. Default 950.
+void CpuSampler::set_load_interval(int min_load_interval) {
+ min_load_interval_ = min_load_interval;
+}
+
+bool CpuSampler::Init() {
+ sysinfo_.reset(new SystemInfo);
+ cpus_ = sysinfo_->GetMaxCpus();
+ if (cpus_ == 0) {
+ return false;
+ }
+#ifdef WIN32
+ // Note that GetSystemTimes is available in Windows XP SP1 or later.
+ // http://msdn.microsoft.com/en-us/library/ms724400.aspx
+ // NtQuerySystemInformation is used as a fallback.
+ if (!force_fallback_) {
+ get_system_times_ = GetProcAddress(GetModuleHandle(L"kernel32.dll"),
+ "GetSystemTimes");
+ }
+ nt_query_system_information_ = GetProcAddress(GetModuleHandle(L"ntdll.dll"),
+ "NtQuerySystemInformation");
+ if ((get_system_times_ == NULL) && (nt_query_system_information_ == NULL)) {
+ return false;
+ }
+#endif
+#if defined(LINUX) || defined(ANDROID)
+ Pathname sname("/proc/stat");
+ sfile_.reset(Filesystem::OpenFile(sname, "rb"));
+ if (sfile_.get() == NULL) {
+ LOG_ERR(LS_ERROR) << "open proc/stat failed:";
+ return false;
+ }
+ if (!sfile_->DisableBuffering()) {
+ LOG_ERR(LS_ERROR) << "could not disable buffering for proc/stat";
+ return false;
+ }
+#endif // defined(LINUX) || defined(ANDROID)
+ GetProcessLoad(); // Initialize values.
+ GetSystemLoad();
+ // Help next user call return valid data by recomputing load.
+ process_.prev_load_time_ = 0u;
+ system_.prev_load_time_ = 0u;
+ return true;
+}
+
+float CpuSampler::UpdateCpuLoad(uint64 current_total_times,
+ uint64 current_cpu_times,
+ uint64 *prev_total_times,
+ uint64 *prev_cpu_times) {
+ float result = 0.f;
+ if (current_total_times < *prev_total_times ||
+ current_cpu_times < *prev_cpu_times) {
+ LOG(LS_ERROR) << "Inconsistent time values are passed. ignored";
+ } else {
+ const uint64 cpu_diff = current_cpu_times - *prev_cpu_times;
+ const uint64 total_diff = current_total_times - *prev_total_times;
+ result = (total_diff == 0ULL ? 0.f :
+ static_cast<float>(1.0f * cpu_diff / total_diff));
+ if (result > static_cast<float>(cpus_)) {
+ result = static_cast<float>(cpus_);
+ }
+ *prev_total_times = current_total_times;
+ *prev_cpu_times = current_cpu_times;
+ }
+ return result;
+}
+
+float CpuSampler::GetSystemLoad() {
+ uint32 timenow = Time();
+ int elapsed = static_cast<int>(TimeDiff(timenow, system_.prev_load_time_));
+ if (min_load_interval_ != 0 && system_.prev_load_time_ != 0u &&
+ elapsed < min_load_interval_) {
+ return system_.prev_load_;
+ }
+#ifdef WIN32
+ uint64 total_times, cpu_times;
+
+ typedef BOOL (_stdcall *GST_PROC)(LPFILETIME, LPFILETIME, LPFILETIME);
+ typedef NTSTATUS (WINAPI *QSI_PROC)(SYSTEM_INFORMATION_CLASS,
+ PVOID, ULONG, PULONG);
+
+ GST_PROC get_system_times = reinterpret_cast<GST_PROC>(get_system_times_);
+ QSI_PROC nt_query_system_information = reinterpret_cast<QSI_PROC>(
+ nt_query_system_information_);
+
+ if (get_system_times) {
+ FILETIME idle_time, kernel_time, user_time;
+ if (!get_system_times(&idle_time, &kernel_time, &user_time)) {
+ LOG(LS_ERROR) << "::GetSystemTimes() failed: " << ::GetLastError();
+ return 0.f;
+ }
+ // kernel_time includes Kernel idle time, so no need to
+ // include cpu_time as total_times
+ total_times = ToUInt64(kernel_time) + ToUInt64(user_time);
+ cpu_times = total_times - ToUInt64(idle_time);
+
+ } else {
+ if (nt_query_system_information) {
+ ULONG returned_length = 0;
+ scoped_array<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> processor_info(
+ new SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[cpus_]);
+ nt_query_system_information(
+ ::SystemProcessorPerformanceInformation,
+ reinterpret_cast<void*>(processor_info.get()),
+ cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION),
+ &returned_length);
+
+ if (returned_length !=
+ (cpus_ * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION))) {
+ LOG(LS_ERROR) << "NtQuerySystemInformation has unexpected size";
+ return 0.f;
+ }
+
+ uint64 current_idle = 0;
+ uint64 current_kernel = 0;
+ uint64 current_user = 0;
+ for (int ix = 0; ix < cpus_; ++ix) {
+ current_idle += processor_info[ix].IdleTime.QuadPart;
+ current_kernel += processor_info[ix].UserTime.QuadPart;
+ current_user += processor_info[ix].KernelTime.QuadPart;
+ }
+ total_times = current_kernel + current_user;
+ cpu_times = total_times - current_idle;
+ } else {
+ return 0.f;
+ }
+ }
+#endif // WIN32
+
+#if defined(IOS) || defined(OSX)
+ host_cpu_load_info_data_t cpu_info;
+ mach_msg_type_number_t info_count = HOST_CPU_LOAD_INFO_COUNT;
+ if (KERN_SUCCESS != host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
+ reinterpret_cast<host_info_t>(&cpu_info),
+ &info_count)) {
+ LOG(LS_ERROR) << "::host_statistics() failed";
+ return 0.f;
+ }
+
+ const uint64 cpu_times = cpu_info.cpu_ticks[CPU_STATE_NICE] +
+ cpu_info.cpu_ticks[CPU_STATE_SYSTEM] +
+ cpu_info.cpu_ticks[CPU_STATE_USER];
+ const uint64 total_times = cpu_times + cpu_info.cpu_ticks[CPU_STATE_IDLE];
+#endif // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+ if (sfile_.get() == NULL) {
+ LOG(LS_ERROR) << "Invalid handle for proc/stat";
+ return 0.f;
+ }
+ std::string statbuf;
+ sfile_->SetPosition(0);
+ if (!sfile_->ReadLine(&statbuf)) {
+ LOG_ERR(LS_ERROR) << "Could not read proc/stat file";
+ return 0.f;
+ }
+
+ unsigned long long user;
+ unsigned long long nice;
+ unsigned long long system;
+ unsigned long long idle;
+ if (sscanf(statbuf.c_str(), "cpu %Lu %Lu %Lu %Lu",
+ &user, &nice,
+ &system, &idle) != 4) {
+ LOG_ERR(LS_ERROR) << "Could not parse cpu info";
+ return 0.f;
+ }
+ const uint64 cpu_times = nice + system + user;
+ const uint64 total_times = cpu_times + idle;
+#endif // defined(LINUX) || defined(ANDROID)
+ system_.prev_load_time_ = timenow;
+ system_.prev_load_ = UpdateCpuLoad(total_times,
+ cpu_times * cpus_,
+ &system_.prev_total_times_,
+ &system_.prev_cpu_times_);
+ return system_.prev_load_;
+}
+
+float CpuSampler::GetProcessLoad() {
+ uint32 timenow = Time();
+ int elapsed = static_cast<int>(TimeDiff(timenow, process_.prev_load_time_));
+ if (min_load_interval_ != 0 && process_.prev_load_time_ != 0u &&
+ elapsed < min_load_interval_) {
+ return process_.prev_load_;
+ }
+#ifdef WIN32
+ FILETIME current_file_time;
+ ::GetSystemTimeAsFileTime(¤t_file_time);
+
+ FILETIME create_time, exit_time, kernel_time, user_time;
+ if (!::GetProcessTimes(::GetCurrentProcess(),
+ &create_time, &exit_time, &kernel_time, &user_time)) {
+ LOG(LS_ERROR) << "::GetProcessTimes() failed: " << ::GetLastError();
+ return 0.f;
+ }
+
+ const uint64 total_times =
+ ToUInt64(current_file_time) - ToUInt64(create_time);
+ const uint64 cpu_times =
+ (ToUInt64(kernel_time) + ToUInt64(user_time));
+#endif // WIN32
+
+#ifdef POSIX
+ // Common to both OSX and Linux.
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ const uint64 total_times = tv.tv_sec * kNumMicrosecsPerSec + tv.tv_usec;
+#endif
+
+#if defined(IOS) || defined(OSX)
+ // Get live thread usage.
+ task_thread_times_info task_times_info;
+ mach_msg_type_number_t info_count = TASK_THREAD_TIMES_INFO_COUNT;
+
+ if (KERN_SUCCESS != task_info(mach_task_self(), TASK_THREAD_TIMES_INFO,
+ reinterpret_cast<task_info_t>(&task_times_info),
+ &info_count)) {
+ LOG(LS_ERROR) << "::task_info(TASK_THREAD_TIMES_INFO) failed";
+ return 0.f;
+ }
+
+ // Get terminated thread usage.
+ task_basic_info task_term_info;
+ info_count = TASK_BASIC_INFO_COUNT;
+ if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO,
+ reinterpret_cast<task_info_t>(&task_term_info),
+ &info_count)) {
+ LOG(LS_ERROR) << "::task_info(TASK_BASIC_INFO) failed";
+ return 0.f;
+ }
+
+ const uint64 cpu_times = (TimeValueTToInt64(task_times_info.user_time) +
+ TimeValueTToInt64(task_times_info.system_time) +
+ TimeValueTToInt64(task_term_info.user_time) +
+ TimeValueTToInt64(task_term_info.system_time));
+#endif // defined(IOS) || defined(OSX)
+
+#if defined(LINUX) || defined(ANDROID)
+ rusage usage;
+ if (getrusage(RUSAGE_SELF, &usage) < 0) {
+ LOG_ERR(LS_ERROR) << "getrusage failed";
+ return 0.f;
+ }
+
+ const uint64 cpu_times =
+ (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * kNumMicrosecsPerSec +
+ usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
+#endif // defined(LINUX) || defined(ANDROID)
+ process_.prev_load_time_ = timenow;
+ process_.prev_load_ = UpdateCpuLoad(total_times,
+ cpu_times,
+ &process_.prev_total_times_,
+ &process_.prev_cpu_times_);
+ return process_.prev_load_;
+}
+
+int CpuSampler::GetMaxCpus() const {
+ return cpus_;
+}
+
+int CpuSampler::GetCurrentCpus() {
+ return sysinfo_->GetCurCpus();
+}
+
+///////////////////////////////////////////////////////////////////
+// Implementation of class CpuMonitor.
+CpuMonitor::CpuMonitor(Thread* thread)
+ : monitor_thread_(thread ? thread : Thread::Current()) {
+ monitor_thread_->SignalQueueDestroyed.connect(
+ this, &CpuMonitor::OnMessageQueueDestroyed);
+}
+
+CpuMonitor::~CpuMonitor() {
+ Stop();
+}
+
+bool CpuMonitor::Start(int period_ms) {
+ if (!sampler_.Init()) return false;
+
+ period_ms_ = period_ms;
+ if (monitor_thread_) {
+ monitor_thread_->PostDelayed(period_ms_, this);
+ }
+ return true;
+}
+
+void CpuMonitor::Stop() {
+ if (monitor_thread_) {
+ monitor_thread_->Clear(this);
+ }
+}
+
+void CpuMonitor::OnMessage(Message* msg) {
+ int max_cpus = sampler_.GetMaxCpus();
+ int current_cpus = sampler_.GetCurrentCpus();
+ float process_load = sampler_.GetProcessLoad();
+ float system_load = sampler_.GetSystemLoad();
+ SignalUpdate(current_cpus, max_cpus, process_load, system_load);
+
+ if (monitor_thread_) {
+ monitor_thread_->PostDelayed(period_ms_, this);
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/cpumonitor.h b/talk/base/cpumonitor.h
new file mode 100644
index 0000000..f633f50
--- /dev/null
+++ b/talk/base/cpumonitor.h
@@ -0,0 +1,139 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_CPUMONITOR_H_
+#define TALK_BASE_CPUMONITOR_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#if defined(LINUX) || defined(ANDROID)
+#include "talk/base/stream.h"
+#endif // defined(LINUX) || defined(ANDROID)
+
+namespace talk_base {
+class Thread;
+class SystemInfo;
+
+struct CpuStats {
+ CpuStats()
+ : prev_total_times_(0),
+ prev_cpu_times_(0),
+ prev_load_(0.f),
+ prev_load_time_(0u) {
+ }
+
+ uint64 prev_total_times_;
+ uint64 prev_cpu_times_;
+ float prev_load_; // Previous load value.
+ uint32 prev_load_time_; // Time previous load value was taken.
+};
+
+// CpuSampler samples the process and system load.
+class CpuSampler {
+ public:
+ CpuSampler();
+ ~CpuSampler();
+
+ // Initialize CpuSampler. Returns true if successful.
+ bool Init();
+
+ // Set minimum interval in ms between computing new load values.
+ // Default 950 ms. Set to 0 to disable interval.
+ void set_load_interval(int min_load_interval);
+
+ // Return CPU load of current process as a float from 0 to 1.
+ float GetProcessLoad();
+
+ // Return CPU load of current process as a float from 0 to 1.
+ float GetSystemLoad();
+
+ // Return number of cpus. Includes hyperthreads.
+ int GetMaxCpus() const;
+
+ // Return current number of cpus available to this process.
+ int GetCurrentCpus();
+
+ // For testing. Allows forcing of fallback to using NTDLL functions.
+ void set_force_fallback(bool fallback) {
+#ifdef WIN32
+ force_fallback_ = fallback;
+#endif
+ }
+
+ private:
+ float UpdateCpuLoad(uint64 current_total_times,
+ uint64 current_cpu_times,
+ uint64 *prev_total_times,
+ uint64 *prev_cpu_times);
+ CpuStats process_;
+ CpuStats system_;
+ int cpus_;
+ int min_load_interval_; // Minimum time between computing new load.
+ scoped_ptr<SystemInfo> sysinfo_;
+#ifdef WIN32
+ void* get_system_times_;
+ void* nt_query_system_information_;
+ bool force_fallback_;
+#endif
+#if defined(LINUX) || defined(ANDROID)
+ // File for reading /proc/stat
+ scoped_ptr<FileStream> sfile_;
+#endif // defined(LINUX) || defined(ANDROID)
+};
+
+// CpuMonitor samples and signals the CPU load periodically.
+class CpuMonitor
+ : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+ explicit CpuMonitor(Thread* thread);
+ virtual ~CpuMonitor();
+
+ bool Start(int period_ms);
+ void Stop();
+ // Signal parameters are current cpus, max cpus, process load and system load.
+ sigslot::signal4<int, int, float, float> SignalUpdate;
+
+ protected:
+ // Override virtual method of parent MessageHandler.
+ virtual void OnMessage(talk_base::Message* msg);
+ // Clear the monitor thread and stop sending it messages if the thread goes
+ // away before our lifetime.
+ void OnMessageQueueDestroyed() { monitor_thread_ = NULL; }
+
+ private:
+ Thread* monitor_thread_;
+ CpuSampler sampler_;
+ int period_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(CpuMonitor);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_CPUMONITOR_H_
diff --git a/talk/base/cpumonitor_unittest.cc b/talk/base/cpumonitor_unittest.cc
new file mode 100644
index 0000000..e83c969
--- /dev/null
+++ b/talk/base/cpumonitor_unittest.cc
@@ -0,0 +1,401 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iomanip>
+#include <iostream>
+#include <vector>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/cpumonitor.h"
+#include "talk/base/flags.h"
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/timing.h"
+
+namespace talk_base {
+
+static const int kMaxCpus = 1024;
+static const int kSettleTime = 100; // Amount of time to between tests.
+static const int kIdleTime = 500; // Amount of time to be idle in ms.
+static const int kBusyTime = 1000; // Amount of time to be busy in ms.
+
+class BusyThread : public talk_base::Thread {
+ public:
+ BusyThread(double load, double duration, double interval) :
+ load_(load), duration_(duration), interval_(interval) {
+ }
+ void Run() {
+ Timing time;
+ double busy_time = interval_ * load_ / 100.0;
+ for (;;) {
+ time.BusyWait(busy_time);
+ time.IdleWait(interval_ - busy_time);
+ if (duration_) {
+ duration_ -= interval_;
+ if (duration_ <= 0) {
+ break;
+ }
+ }
+ }
+ }
+ private:
+ double load_;
+ double duration_;
+ double interval_;
+};
+
+class CpuLoadListener : public sigslot::has_slots<> {
+ public:
+ CpuLoadListener()
+ : current_cpus_(0),
+ cpus_(0),
+ process_load_(.0f),
+ system_load_(.0f),
+ count_(0) {
+ }
+
+ void OnCpuLoad(int current_cpus, int cpus, float proc_load, float sys_load) {
+ current_cpus_ = current_cpus;
+ cpus_ = cpus;
+ process_load_ = proc_load;
+ system_load_ = sys_load;
+ ++count_;
+ }
+
+ int current_cpus() const { return current_cpus_; }
+ int cpus() const { return cpus_; }
+ float process_load() const { return process_load_; }
+ float system_load() const { return system_load_; }
+ int count() const { return count_; }
+
+ private:
+ int current_cpus_;
+ int cpus_;
+ float process_load_;
+ float system_load_;
+ int count_;
+};
+
+// Set affinity (which cpu to run on), but respecting FLAG_affinity:
+// -1 means no affinity - run on whatever cpu is available.
+// 0 .. N means run on specific cpu. The tool will create N threads and call
+// SetThreadAffinity on 0 to N - 1 as cpu. FLAG_affinity sets the first cpu
+// so the range becomes affinity to affinity + N - 1
+// Note that this function affects Windows scheduling, effectively giving
+// the thread with affinity for a specified CPU more priority on that CPU.
+bool SetThreadAffinity(BusyThread* t, int cpu, int affinity) {
+#ifdef WIN32
+ if (affinity >= 0) {
+ return ::SetThreadAffinityMask(t->GetHandle(),
+ 1 << (cpu + affinity)) != FALSE;
+ }
+#endif
+ return true;
+}
+
+bool SetThreadPriority(BusyThread* t, int prio) {
+ if (!prio) {
+ return true;
+ }
+ bool ok = t->SetPriority(static_cast<talk_base::ThreadPriority>(prio));
+ if (!ok) {
+ std::cout << "Error setting thread priority." << std::endl;
+ }
+ return ok;
+}
+
+int CpuLoad(double cpuload, double duration, int numthreads,
+ int priority, double interval, int affinity) {
+ int ret = 0;
+ std::vector<BusyThread*> threads;
+ for (int i = 0; i < numthreads; ++i) {
+ threads.push_back(new BusyThread(cpuload, duration, interval));
+ // NOTE(fbarchard): Priority must be done before Start.
+ if (!SetThreadPriority(threads[i], priority) ||
+ !threads[i]->Start() ||
+ !SetThreadAffinity(threads[i], i, affinity)) {
+ ret = 1;
+ break;
+ }
+ }
+ // Wait on each thread
+ if (ret == 0) {
+ for (int i = 0; i < numthreads; ++i) {
+ threads[i]->Stop();
+ }
+ }
+
+ for (int i = 0; i < numthreads; ++i) {
+ delete threads[i];
+ }
+ return ret;
+}
+
+// Make 2 CPUs busy
+static void CpuTwoBusyLoop(int busytime) {
+ CpuLoad(100.0, busytime / 1000.0, 2, 1, 0.050, -1);
+}
+
+// Make 1 CPUs busy
+static void CpuBusyLoop(int busytime) {
+ CpuLoad(100.0, busytime / 1000.0, 1, 1, 0.050, -1);
+}
+
+// Make 1 use half CPU time.
+static void CpuHalfBusyLoop(int busytime) {
+ CpuLoad(50.0, busytime / 1000.0, 1, 1, 0.050, -1);
+}
+
+void TestCpuSampler(bool test_proc, bool test_sys, bool force_fallback) {
+ CpuSampler sampler;
+ sampler.set_force_fallback(force_fallback);
+ EXPECT_TRUE(sampler.Init());
+ sampler.set_load_interval(100);
+ int cpus = sampler.GetMaxCpus();
+
+ // Test1: CpuSampler under idle situation.
+ Thread::SleepMs(kSettleTime);
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ Thread::SleepMs(kIdleTime);
+
+ float proc_idle = 0.f, sys_idle = 0.f;
+ if (test_proc) {
+ proc_idle = sampler.GetProcessLoad();
+ }
+ if (test_sys) {
+ sys_idle = sampler.GetSystemLoad();
+ }
+ if (test_proc) {
+ LOG(LS_INFO) << "ProcessLoad Idle: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << proc_idle;
+ EXPECT_GE(proc_idle, 0.f);
+ EXPECT_LE(proc_idle, static_cast<float>(cpus));
+ }
+ if (test_sys) {
+ LOG(LS_INFO) << "SystemLoad Idle: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << sys_idle;
+ EXPECT_GE(sys_idle, 0.f);
+ EXPECT_LE(sys_idle, static_cast<float>(cpus));
+ }
+
+ // Test2: CpuSampler with main process at 50% busy.
+ Thread::SleepMs(kSettleTime);
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ CpuHalfBusyLoop(kBusyTime);
+
+ float proc_halfbusy = 0.f, sys_halfbusy = 0.f;
+ if (test_proc) {
+ proc_halfbusy = sampler.GetProcessLoad();
+ }
+ if (test_sys) {
+ sys_halfbusy = sampler.GetSystemLoad();
+ }
+ if (test_proc) {
+ LOG(LS_INFO) << "ProcessLoad Halfbusy: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << proc_halfbusy;
+ EXPECT_GE(proc_halfbusy, 0.f);
+ EXPECT_LE(proc_halfbusy, static_cast<float>(cpus));
+ }
+ if (test_sys) {
+ LOG(LS_INFO) << "SystemLoad Halfbusy: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << sys_halfbusy;
+ EXPECT_GE(sys_halfbusy, 0.f);
+ EXPECT_LE(sys_halfbusy, static_cast<float>(cpus));
+ }
+
+ // Test3: CpuSampler with main process busy.
+ Thread::SleepMs(kSettleTime);
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ CpuBusyLoop(kBusyTime);
+
+ float proc_busy = 0.f, sys_busy = 0.f;
+ if (test_proc) {
+ proc_busy = sampler.GetProcessLoad();
+ }
+ if (test_sys) {
+ sys_busy = sampler.GetSystemLoad();
+ }
+ if (test_proc) {
+ LOG(LS_INFO) << "ProcessLoad Busy: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << proc_busy;
+ EXPECT_GE(proc_busy, 0.f);
+ EXPECT_LE(proc_busy, static_cast<float>(cpus));
+ }
+ if (test_sys) {
+ LOG(LS_INFO) << "SystemLoad Busy: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << sys_busy;
+ EXPECT_GE(sys_busy, 0.f);
+ EXPECT_LE(sys_busy, static_cast<float>(cpus));
+ }
+
+ // Test4: CpuSampler with 2 cpus process busy.
+ if (cpus >= 2) {
+ Thread::SleepMs(kSettleTime);
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ CpuTwoBusyLoop(kBusyTime);
+
+ float proc_twobusy = 0.f, sys_twobusy = 0.f;
+ if (test_proc) {
+ proc_twobusy = sampler.GetProcessLoad();
+ }
+ if (test_sys) {
+ sys_twobusy = sampler.GetSystemLoad();
+ }
+ if (test_proc) {
+ LOG(LS_INFO) << "ProcessLoad 2 CPU Busy:"
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << proc_twobusy;
+ EXPECT_GE(proc_twobusy, 0.f);
+ EXPECT_LE(proc_twobusy, static_cast<float>(cpus));
+ }
+ if (test_sys) {
+ LOG(LS_INFO) << "SystemLoad 2 CPU Busy: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << sys_twobusy;
+ EXPECT_GE(sys_twobusy, 0.f);
+ EXPECT_LE(sys_twobusy, static_cast<float>(cpus));
+ }
+ }
+
+ // Test5: CpuSampler with idle process after being busy.
+ Thread::SleepMs(kSettleTime);
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ Thread::SleepMs(kIdleTime);
+
+ if (test_proc) {
+ proc_idle = sampler.GetProcessLoad();
+ }
+ if (test_sys) {
+ sys_idle = sampler.GetSystemLoad();
+ }
+ if (test_proc) {
+ LOG(LS_INFO) << "ProcessLoad Idle: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << proc_idle;
+ EXPECT_GE(proc_idle, 0.f);
+ EXPECT_LE(proc_idle, proc_busy);
+ }
+ if (test_sys) {
+ LOG(LS_INFO) << "SystemLoad Idle: "
+ << setiosflags(std::ios_base::fixed)
+ << std::setprecision(2) << std::setw(6) << sys_idle;
+ EXPECT_GE(sys_idle, 0.f);
+ EXPECT_LE(sys_idle, static_cast<float>(cpus));
+ }
+}
+
+TEST(CpuMonitorTest, TestCpus) {
+ CpuSampler sampler;
+ EXPECT_TRUE(sampler.Init());
+ int current_cpus = sampler.GetCurrentCpus();
+ int cpus = sampler.GetMaxCpus();
+ LOG(LS_INFO) << "Current Cpus: " << std::setw(9) << current_cpus;
+ LOG(LS_INFO) << "Maximum Cpus: " << std::setw(9) << cpus;
+ EXPECT_GT(cpus, 0);
+ EXPECT_LE(cpus, kMaxCpus);
+ EXPECT_GT(current_cpus, 0);
+ EXPECT_LE(current_cpus, cpus);
+}
+
+#ifdef WIN32
+// Tests overall system CpuSampler using legacy OS fallback code if applicable.
+TEST(CpuMonitorTest, TestGetSystemLoadForceFallback) {
+ TestCpuSampler(false, true, true);
+}
+#endif
+
+// Tests both process and system functions in use at same time.
+TEST(CpuMonitorTest, TestGetBothLoad) {
+ TestCpuSampler(true, true, false);
+}
+
+// Tests a query less than the interval produces the same value.
+TEST(CpuMonitorTest, TestInterval) {
+ CpuSampler sampler;
+ EXPECT_TRUE(sampler.Init());
+
+ // Test1: Interval of 500 ms
+ sampler.set_load_interval(500);
+
+ sampler.GetProcessLoad();
+ sampler.GetSystemLoad();
+
+ float proc_orig = sampler.GetProcessLoad();
+ float sys_orig = sampler.GetSystemLoad();
+
+ CpuBusyLoop(200);
+
+ float proc_halftime = sampler.GetProcessLoad();
+ float sys_halftime = sampler.GetSystemLoad();
+
+ EXPECT_EQ(proc_orig, proc_halftime);
+ EXPECT_EQ(sys_orig, sys_halftime);
+}
+
+TEST(CpuMonitorTest, TestCpuMonitor) {
+ CpuMonitor monitor(Thread::Current());
+ CpuLoadListener listener;
+ monitor.SignalUpdate.connect(&listener, &CpuLoadListener::OnCpuLoad);
+ EXPECT_TRUE(monitor.Start(10));
+ Thread::Current()->ProcessMessages(50);
+ EXPECT_GT(listener.count(), 2); // We have checked cpu load more than twice.
+ EXPECT_GT(listener.current_cpus(), 0);
+ EXPECT_GT(listener.cpus(), 0);
+ EXPECT_GE(listener.process_load(), .0f);
+ EXPECT_GE(listener.system_load(), .0f);
+
+ monitor.Stop();
+ // Wait 20 ms to ake sure all signals are delivered.
+ Thread::Current()->ProcessMessages(20);
+ int old_count = listener.count();
+ Thread::Current()->ProcessMessages(20);
+ // Verfy no more siganls.
+ EXPECT_EQ(old_count, listener.count());
+}
+
+} // namespace talk_base
diff --git a/talk/base/criticalsection.h b/talk/base/criticalsection.h
index fd0f06c..5a09a93 100644
--- a/talk/base/criticalsection.h
+++ b/talk/base/criticalsection.h
@@ -63,6 +63,13 @@
EnterCriticalSection(&crit_);
TRACK_OWNER(thread_ = GetCurrentThreadId());
}
+ bool TryEnter() {
+ if (TryEnterCriticalSection(&crit_) != FALSE) {
+ TRACK_OWNER(thread_ = GetCurrentThreadId());
+ return true;
+ }
+ return false;
+ }
void Leave() {
TRACK_OWNER(thread_ = 0);
LeaveCriticalSection(&crit_);
@@ -96,6 +103,13 @@
pthread_mutex_lock(&mutex_);
TRACK_OWNER(thread_ = pthread_self());
}
+ bool TryEnter() {
+ if (pthread_mutex_trylock(&mutex_) == 0) {
+ TRACK_OWNER(thread_ = pthread_self());
+ return true;
+ }
+ return false;
+ }
void Leave() {
TRACK_OWNER(thread_ = 0);
pthread_mutex_unlock(&mutex_);
diff --git a/talk/base/dbus.cc b/talk/base/dbus.cc
new file mode 100644
index 0000000..86fea4d
--- /dev/null
+++ b/talk/base/dbus.cc
@@ -0,0 +1,405 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_DBUS_GLIB
+
+#include "talk/base/dbus.h"
+
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Avoid static object construction/destruction on startup/shutdown.
+static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
+static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
+
+// Releases DBus-Glib symbols.
+static void ReleaseDBusGlibSymbol() {
+ if (g_dbus_symbol != NULL) {
+ delete g_dbus_symbol;
+ g_dbus_symbol = NULL;
+ }
+}
+
+// Loads DBus-Glib symbols.
+static void InitializeDBusGlibSymbol() {
+ // This is thread safe.
+ if (NULL == g_dbus_symbol) {
+ g_dbus_symbol = new LibDBusGlibSymbolTable();
+
+ // Loads dbus-glib
+ if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) {
+ LOG(LS_WARNING) << "Failed to load dbus-glib symbol table.";
+ ReleaseDBusGlibSymbol();
+ } else {
+ // Nothing we can do if atexit() failed. Just ignore its returned value.
+ atexit(ReleaseDBusGlibSymbol);
+ }
+ }
+}
+
+// Returns a reference to the given late-binded symbol, with the correct type.
+#define LATE(sym) LATESYM_GET(LibDBusGlibSymbolTable, \
+ DBusMonitor::GetDBusGlibSymbolTable(), sym)
+
+// Implementation of class DBusSigMessageData
+DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
+ : TypedMessageData<DBusMessage *>(message) {
+ LATE(dbus_message_ref)(data());
+}
+
+DBusSigMessageData::~DBusSigMessageData() {
+ LATE(dbus_message_unref)(data());
+}
+
+// Implementation of class DBusSigFilter
+
+// Builds a DBus filter string from given DBus path, interface and member.
+std::string DBusSigFilter::BuildFilterString(const std::string &path,
+ const std::string &interface,
+ const std::string &member) {
+ std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'");
+ if (!path.empty()) {
+ ret += ("," DBUS_PATH "='");
+ ret += path;
+ ret += "'";
+ }
+ if (!interface.empty()) {
+ ret += ("," DBUS_INTERFACE "='");
+ ret += interface;
+ ret += "'";
+ }
+ if (!member.empty()) {
+ ret += ("," DBUS_MEMBER "='");
+ ret += member;
+ ret += "'";
+ }
+ return ret;
+}
+
+// Forwards the message to the given instance.
+DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn,
+ DBusMessage *message,
+ void *instance) {
+ ASSERT(instance);
+ if (instance) {
+ return static_cast<DBusSigFilter *>(instance)->Callback(message);
+ }
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+// Posts a message to caller thread.
+DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) {
+ if (caller_thread_) {
+ caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message));
+ }
+ // Don't "eat" the message here. Let it pop up.
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+// From MessageHandler.
+void DBusSigFilter::OnMessage(Message *message) {
+ if (message != NULL && DSM_SIGNAL == message->message_id) {
+ DBusSigMessageData *msg =
+ static_cast<DBusSigMessageData *>(message->pdata);
+ if (msg) {
+ ProcessSignal(msg->data());
+ delete msg;
+ }
+ }
+}
+
+// Definition of private class DBusMonitoringThread.
+// It creates a worker-thread to listen signals on DBus. The worker-thread will
+// be running in a priate GMainLoop forever until either Stop() has been invoked
+// or it hits an error.
+class DBusMonitor::DBusMonitoringThread : public talk_base::Thread {
+ public:
+ explicit DBusMonitoringThread(DBusMonitor *monitor,
+ GMainContext *context,
+ GMainLoop *mainloop,
+ std::vector<DBusSigFilter *> *filter_list)
+ : monitor_(monitor),
+ context_(context),
+ mainloop_(mainloop),
+ connection_(NULL),
+ idle_source_(NULL),
+ filter_list_(filter_list) {
+ ASSERT(monitor_);
+ ASSERT(context_);
+ ASSERT(mainloop_);
+ ASSERT(filter_list_);
+ }
+
+ // Override virtual method of Thread. Context: worker-thread.
+ virtual void Run() {
+ ASSERT(NULL == connection_);
+
+ // Setup DBus connection and start monitoring.
+ monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
+ if (!Setup()) {
+ LOG(LS_ERROR) << "DBus monitoring setup failed.";
+ monitor_->OnMonitoringStatusChanged(DMS_FAILED);
+ CleanUp();
+ return;
+ }
+ monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
+ LATE(g_main_loop_run)(mainloop_);
+ monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
+
+ // Done normally. Clean up DBus connection.
+ CleanUp();
+ return;
+ }
+
+ // Override virtual method of Thread. Context: caller-thread.
+ virtual void Stop() {
+ ASSERT(NULL == idle_source_);
+ // Add an idle source and let the gmainloop quit on idle.
+ idle_source_ = LATE(g_idle_source_new)();
+ if (idle_source_) {
+ LATE(g_source_set_callback)(idle_source_, &Idle, this, NULL);
+ LATE(g_source_attach)(idle_source_, context_);
+ } else {
+ LOG(LS_ERROR) << "g_idle_source_new() failed.";
+ QuitGMainloop(); // Try to quit anyway.
+ }
+
+ Thread::Stop(); // Wait for the thread.
+ }
+
+ private:
+ // Registers all DBus filters.
+ void RegisterAllFilters() {
+ ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
+
+ for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
+ it != filter_list_->end(); ++it) {
+ DBusSigFilter *filter = (*it);
+ if (!filter) {
+ LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
+ continue;
+ }
+
+ LATE(dbus_bus_add_match)(
+ LATE(dbus_g_connection_get_connection)(connection_),
+ filter->filter().c_str(), NULL);
+
+ if (!LATE(dbus_connection_add_filter)(
+ LATE(dbus_g_connection_get_connection)(connection_),
+ &DBusSigFilter::DBusCallback, filter, NULL)) {
+ LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
+ << "Filter: " << filter->filter();
+ continue;
+ }
+ }
+ }
+
+ // Unregisters all DBus filters.
+ void UnRegisterAllFilters() {
+ ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
+
+ for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
+ it != filter_list_->end(); ++it) {
+ DBusSigFilter *filter = (*it);
+ if (!filter) {
+ LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
+ continue;
+ }
+ LATE(dbus_connection_remove_filter)(
+ LATE(dbus_g_connection_get_connection)(connection_),
+ &DBusSigFilter::DBusCallback, filter);
+ }
+ }
+
+ // Sets up the monitoring thread.
+ bool Setup() {
+ LATE(g_main_context_push_thread_default)(context_);
+
+ // Start connection to dbus.
+ // If dbus daemon is not running, returns false immediately.
+ connection_ = LATE(dbus_g_bus_get_private)(monitor_->type_, context_, NULL);
+ if (NULL == connection_) {
+ LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
+ return false;
+ }
+ if (NULL == LATE(dbus_g_connection_get_connection)(connection_)) {
+ LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
+ << "DBus daemon is probably not running.";
+ return false;
+ }
+
+ // Application don't exit if DBus daemon die.
+ LATE(dbus_connection_set_exit_on_disconnect)(
+ LATE(dbus_g_connection_get_connection)(connection_), FALSE);
+
+ // Connect all filters.
+ RegisterAllFilters();
+
+ return true;
+ }
+
+ // Cleans up the monitoring thread.
+ void CleanUp() {
+ if (idle_source_) {
+ // We did an attach() with the GSource, so we need to destroy() it.
+ LATE(g_source_destroy)(idle_source_);
+ // We need to unref() the GSource to end the last reference we got.
+ LATE(g_source_unref)(idle_source_);
+ idle_source_ = NULL;
+ }
+ if (connection_) {
+ if (LATE(dbus_g_connection_get_connection)(connection_)) {
+ UnRegisterAllFilters();
+ LATE(dbus_connection_close)(
+ LATE(dbus_g_connection_get_connection)(connection_));
+ }
+ LATE(dbus_g_connection_unref)(connection_);
+ connection_ = NULL;
+ }
+ LATE(g_main_loop_unref)(mainloop_);
+ mainloop_ = NULL;
+ LATE(g_main_context_unref)(context_);
+ context_ = NULL;
+ }
+
+ // Handles callback on Idle. We only add this source when ready to stop.
+ static gboolean Idle(gpointer data) {
+ static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
+ return TRUE;
+ }
+
+ // We only hit this when ready to quit.
+ void QuitGMainloop() {
+ LATE(g_main_loop_quit)(mainloop_);
+ }
+
+ DBusMonitor *monitor_;
+
+ GMainContext *context_;
+ GMainLoop *mainloop_;
+ DBusGConnection *connection_;
+ GSource *idle_source_;
+
+ std::vector<DBusSigFilter *> *filter_list_;
+};
+
+// Implementation of class DBusMonitor
+
+// Returns DBus-Glib symbol handle. Initialize it first if hasn't.
+LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
+ // This is multi-thread safe.
+ pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
+
+ return g_dbus_symbol;
+};
+
+// Creates an instance of DBusMonitor
+DBusMonitor *DBusMonitor::Create(DBusBusType type) {
+ if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
+ return NULL;
+ }
+ return new DBusMonitor(type);
+}
+
+DBusMonitor::DBusMonitor(DBusBusType type)
+ : type_(type),
+ status_(DMS_NOT_INITIALIZED),
+ monitoring_thread_(NULL) {
+ ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
+}
+
+DBusMonitor::~DBusMonitor() {
+ StopMonitoring();
+}
+
+bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
+ if (monitoring_thread_) {
+ return false;
+ }
+ if (!filter) {
+ return false;
+ }
+ filter_list_.push_back(filter);
+ return true;
+}
+
+bool DBusMonitor::StartMonitoring() {
+ if (!monitoring_thread_) {
+ LATE(g_type_init)();
+ LATE(g_thread_init)(NULL);
+ LATE(dbus_g_thread_init)();
+
+ GMainContext *context = LATE(g_main_context_new)();
+ if (NULL == context) {
+ LOG(LS_ERROR) << "g_main_context_new() failed.";
+ return false;
+ }
+
+ GMainLoop *mainloop = LATE(g_main_loop_new)(context, FALSE);
+ if (NULL == mainloop) {
+ LOG(LS_ERROR) << "g_main_loop_new() failed.";
+ LATE(g_main_context_unref)(context);
+ return false;
+ }
+
+ monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
+ &filter_list_);
+ if (monitoring_thread_ == NULL) {
+ LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
+ LATE(g_main_context_unref)(context);
+ LATE(g_main_loop_unref)(mainloop);
+ return false;
+ }
+ monitoring_thread_->Start();
+ }
+ return true;
+}
+
+bool DBusMonitor::StopMonitoring() {
+ if (monitoring_thread_) {
+ monitoring_thread_->Stop();
+ monitoring_thread_ = NULL;
+ }
+ return true;
+}
+
+DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
+ return status_;
+}
+
+void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
+ status_ = status;
+}
+
+#undef LATE
+
+} // namespace talk_base
+
+#endif // HAVE_DBUS_GLIB
diff --git a/talk/base/dbus.h b/talk/base/dbus.h
new file mode 100644
index 0000000..7dce350
--- /dev/null
+++ b/talk/base/dbus.h
@@ -0,0 +1,185 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_DBUS_H_
+#define TALK_BASE_DBUS_H_
+
+#ifdef HAVE_DBUS_GLIB
+
+#include <dbus/dbus.h>
+
+#include <string>
+#include <vector>
+
+#include "talk/base/libdbusglibsymboltable.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+#define DBUS_TYPE "type"
+#define DBUS_SIGNAL "signal"
+#define DBUS_PATH "path"
+#define DBUS_INTERFACE "interface"
+#define DBUS_MEMBER "member"
+
+#ifdef CHROMEOS
+#define CROS_PM_PATH "/"
+#define CROS_PM_INTERFACE "org.chromium.PowerManager"
+#define CROS_SIG_POWERCHANGED "PowerStateChanged"
+#define CROS_VALUE_SLEEP "mem"
+#define CROS_VALUE_RESUME "on"
+#else
+#define UP_PATH "/org/freedesktop/UPower"
+#define UP_INTERFACE "org.freedesktop.UPower"
+#define UP_SIG_SLEEPING "Sleeping"
+#define UP_SIG_RESUMING "Resuming"
+#endif // CHROMEOS
+
+// Wraps a DBus messages.
+class DBusSigMessageData : public TypedMessageData<DBusMessage *> {
+ public:
+ explicit DBusSigMessageData(DBusMessage *message);
+ ~DBusSigMessageData();
+};
+
+// DBusSigFilter is an abstract class that defines the interface of DBus
+// signal handling.
+// The subclasses implement ProcessSignal() for various purposes.
+// When a DBus signal comes, a DSM_SIGNAL message is posted to the caller thread
+// which will then invokes ProcessSignal().
+class DBusSigFilter : protected MessageHandler {
+ public:
+ enum DBusSigMessage { DSM_SIGNAL };
+
+ // This filter string should ususally come from BuildFilterString()
+ explicit DBusSigFilter(const std::string &filter)
+ : caller_thread_(Thread::Current()), filter_(filter) {
+ }
+
+ // Builds a DBus monitor filter string from given DBus path, interface, and
+ // member.
+ // See http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+ static std::string BuildFilterString(const std::string &path,
+ const std::string &interface,
+ const std::string &member);
+
+ // Handles callback on DBus messages by DBus system.
+ static DBusHandlerResult DBusCallback(DBusConnection *dbus_conn,
+ DBusMessage *message,
+ void *instance);
+
+ // Handles callback on DBus messages to each DBusSigFilter instance.
+ DBusHandlerResult Callback(DBusMessage *message);
+
+ // From MessageHandler.
+ virtual void OnMessage(Message *message);
+
+ // Returns the DBus monitor filter string.
+ const std::string &filter() const { return filter_; }
+
+ private:
+ // On caller thread.
+ virtual void ProcessSignal(DBusMessage *message) = 0;
+
+ Thread *caller_thread_;
+ const std::string filter_;
+};
+
+// DBusMonitor is a class for DBus signal monitoring.
+//
+// The caller-thread calls AddFilter() first to add the signals that it wants to
+// monitor and then calls StartMonitoring() to start the monitoring.
+// This will create a worker-thread which listens on DBus connection and sends
+// DBus signals back through the callback.
+// The worker-thread will be running forever until either StopMonitoring() is
+// called from the caller-thread or the worker-thread hit some error.
+//
+// Programming model:
+// 1. Caller-thread: Creates an object of DBusMonitor.
+// 2. Caller-thread: Calls DBusMonitor::AddFilter() one or several times.
+// 3. Caller-thread: StartMonitoring().
+// ...
+// 4. Worker-thread: DBus signal recieved. Post a message to caller-thread.
+// 5. Caller-thread: DBusFilterBase::ProcessSignal() is invoked.
+// ...
+// 6. Caller-thread: StopMonitoring().
+//
+// Assumption:
+// AddFilter(), StartMonitoring(), and StopMonitoring() methods are called by
+// a single thread. Hence, there is no need to make them thread safe.
+class DBusMonitor {
+ public:
+ // Status of DBus monitoring.
+ enum DBusMonitorStatus {
+ DMS_NOT_INITIALIZED, // Not initialized.
+ DMS_INITIALIZING, // Initializing the monitoring thread.
+ DMS_RUNNING, // Monitoring.
+ DMS_STOPPED, // Not monitoring. Stopped normally.
+ DMS_FAILED, // Not monitoring. Failed.
+ };
+
+ // Returns the DBus-Glib symbol table.
+ // We should only use this function to access DBus-Glib symbols.
+ static LibDBusGlibSymbolTable *GetDBusGlibSymbolTable();
+
+ // Creates an instance of DBusMonitor.
+ static DBusMonitor *Create(DBusBusType type);
+ ~DBusMonitor();
+
+ // Adds a filter to DBusMonitor.
+ bool AddFilter(DBusSigFilter *filter);
+
+ // Starts DBus message monitoring.
+ bool StartMonitoring();
+
+ // Stops DBus message monitoring.
+ bool StopMonitoring();
+
+ // Gets the status of DBus monitoring.
+ DBusMonitorStatus GetStatus();
+
+ private:
+ // Forward declaration. Defined in the .cc file.
+ class DBusMonitoringThread;
+
+ explicit DBusMonitor(DBusBusType type);
+
+ // Updates status_ when monitoring status has changed.
+ void OnMonitoringStatusChanged(DBusMonitorStatus status);
+
+ DBusBusType type_;
+ DBusMonitorStatus status_;
+ DBusMonitoringThread *monitoring_thread_;
+ std::vector<DBusSigFilter *> filter_list_;
+};
+
+} // namespace talk_base
+
+#endif // HAVE_DBUS_GLIB
+
+#endif // TALK_BASE_DBUS_H_
diff --git a/talk/base/dbus_unittest.cc b/talk/base/dbus_unittest.cc
new file mode 100644
index 0000000..2b9a545
--- /dev/null
+++ b/talk/base/dbus_unittest.cc
@@ -0,0 +1,249 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_DBUS_GLIB
+
+#include "talk/base/dbus.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+#define SIG_NAME "NameAcquired"
+
+static const uint32 kTimeoutMs = 5000U;
+
+class DBusSigFilterTest : public DBusSigFilter {
+ public:
+ // DBusSigFilterTest listens on DBus service itself for "NameAcquired" signal.
+ // This signal should be received when the application connects to DBus
+ // service and gains ownership of a name.
+ // http://dbus.freedesktop.org/doc/dbus-specification.html
+ DBusSigFilterTest()
+ : DBusSigFilter(GetFilter()),
+ message_received_(false) {
+ }
+
+ bool MessageReceived() {
+ return message_received_;
+ }
+
+ private:
+ static std::string GetFilter() {
+ return talk_base::DBusSigFilter::BuildFilterString("", "", SIG_NAME);
+ }
+
+ // Implement virtual method of DBusSigFilter. On caller thread.
+ virtual void ProcessSignal(DBusMessage *message) {
+ EXPECT_TRUE(message != NULL);
+ message_received_ = true;
+ }
+
+ bool message_received_;
+};
+
+TEST(DBusMonitorTest, StartStopStartStop) {
+ DBusSigFilterTest filter;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter));
+
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_NOT_INITIALIZED);
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_RUNNING);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+// DBusMonitorTest listens on DBus service itself for "NameAcquired" signal.
+// This signal should be received when the application connects to DBus
+// service and gains ownership of a name.
+// This test is to make sure that we capture the "NameAcquired" signal.
+TEST(DBusMonitorTest, ReceivedNameAcquiredSignal) {
+ DBusSigFilterTest filter;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter));
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE_WAIT(filter.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+TEST(DBusMonitorTest, ConcurrentMonitors) {
+ DBusSigFilterTest filter1;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor1;
+ monitor1.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor1.get()) {
+ EXPECT_TRUE(monitor1->AddFilter(&filter1));
+ DBusSigFilterTest filter2;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor2;
+ monitor2.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ EXPECT_TRUE(monitor2->AddFilter(&filter2));
+
+ EXPECT_TRUE(monitor1->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor1->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE(monitor2->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor2->GetStatus(), kTimeoutMs);
+
+ EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE(monitor2->StopMonitoring());
+ EXPECT_EQ(monitor2->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+ EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE(monitor1->StopMonitoring());
+ EXPECT_EQ(monitor1->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+TEST(DBusMonitorTest, ConcurrentFilters) {
+ DBusSigFilterTest filter1;
+ DBusSigFilterTest filter2;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter1));
+ EXPECT_TRUE(monitor->AddFilter(&filter2));
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+
+ EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+TEST(DBusMonitorTest, NoAddFilterIfRunning) {
+ DBusSigFilterTest filter1;
+ DBusSigFilterTest filter2;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter1));
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_FALSE(monitor->AddFilter(&filter2));
+
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+TEST(DBusMonitorTest, AddFilterAfterStop) {
+ DBusSigFilterTest filter1;
+ DBusSigFilterTest filter2;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter1));
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+
+ EXPECT_TRUE(monitor->AddFilter(&filter2));
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_EQ_WAIT(DBusMonitor::DMS_RUNNING, monitor->GetStatus(), kTimeoutMs);
+ EXPECT_TRUE_WAIT(filter1.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE_WAIT(filter2.MessageReceived(), kTimeoutMs);
+ EXPECT_TRUE(monitor->StopMonitoring());
+ EXPECT_EQ(monitor->GetStatus(), DBusMonitor::DMS_STOPPED);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started. Skipping test.";
+ }
+}
+
+TEST(DBusMonitorTest, StopRightAfterStart) {
+ DBusSigFilterTest filter;
+ talk_base::scoped_ptr<talk_base::DBusMonitor> monitor;
+ monitor.reset(talk_base::DBusMonitor::Create(DBUS_BUS_SYSTEM));
+ if (monitor.get()) {
+ EXPECT_TRUE(monitor->AddFilter(&filter));
+
+ EXPECT_TRUE(monitor->StartMonitoring());
+ EXPECT_TRUE(monitor->StopMonitoring());
+
+ // Stop the monitoring thread right after it had been started.
+ // If the monitoring thread got a chance to receive a DBus signal, it would
+ // post a message to the main thread and signal the main thread wakeup.
+ // This message will be cleaned out automatically when the filter get
+ // destructed. Here we also consume the wakeup signal (if there is one) so
+ // that the testing (main) thread is reset to a clean state.
+ talk_base::Thread::Current()->ProcessMessages(1);
+ } else {
+ LOG(LS_WARNING) << "DBus Monitor not started.";
+ }
+}
+
+TEST(DBusSigFilter, BuildFilterString) {
+ EXPECT_EQ(DBusSigFilter::BuildFilterString("", "", ""),
+ (DBUS_TYPE "='" DBUS_SIGNAL "'"));
+ EXPECT_EQ(DBusSigFilter::BuildFilterString("p", "", ""),
+ (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p'"));
+ EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i", ""),
+ (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p',"
+ DBUS_INTERFACE "='i'"));
+ EXPECT_EQ(DBusSigFilter::BuildFilterString("p","i","m"),
+ (DBUS_TYPE "='" DBUS_SIGNAL "'," DBUS_PATH "='p',"
+ DBUS_INTERFACE "='i'," DBUS_MEMBER "='m'"));
+}
+
+} // namespace talk_base
+
+#endif // HAVE_DBUS_GLIB
diff --git a/talk/base/diskcache_win32.cc b/talk/base/diskcache_win32.cc
new file mode 100644
index 0000000..b49ed81
--- /dev/null
+++ b/talk/base/diskcache_win32.cc
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/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/talk/base/diskcache_win32.h b/talk/base/diskcache_win32.h
new file mode 100644
index 0000000..a5e8de5
--- /dev/null
+++ b/talk/base/diskcache_win32.h
@@ -0,0 +1,46 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_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/talk/base/fakenetwork.h b/talk/base/fakenetwork.h
new file mode 100644
index 0000000..c5ae4b6
--- /dev/null
+++ b/talk/base/fakenetwork.h
@@ -0,0 +1,106 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FAKENETWORK_H_
+#define TALK_BASE_FAKENETWORK_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/network.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Fake network manager that allows us to manually specify the IPs to use.
+class FakeNetworkManager : public NetworkManagerBase,
+ public MessageHandler {
+ public:
+ FakeNetworkManager()
+ : thread_(Thread::Current()),
+ next_index_(0),
+ started_(false) {
+ }
+
+ void AddInterface(const SocketAddress& iface) {
+ // ensure a unique name for the interface
+ SocketAddress address("test" + talk_base::ToString(next_index_++), 0);
+ address.SetResolvedIP(iface.ipaddr());
+ ifaces_.push_back(address);
+ DoUpdateNetworks();
+ }
+
+ void RemoveInterface(const SocketAddress& iface) {
+ for (std::vector<SocketAddress>::iterator it = ifaces_.begin();
+ it != ifaces_.end(); ++it) {
+ if (it->EqualIPs(iface)) {
+ ifaces_.erase(it);
+ break;
+ }
+ }
+ DoUpdateNetworks();
+ }
+
+ virtual void StartUpdating() {
+ started_ = true;
+ thread_->Post(this);
+ }
+
+ virtual void StopUpdating() {
+ started_ = false;
+ }
+
+ // MessageHandler interface.
+ virtual void OnMessage(Message* msg) {
+ DoUpdateNetworks();
+ }
+
+ private:
+ void DoUpdateNetworks() {
+ if (!started_)
+ return;
+ std::vector<Network*> networks;
+ for (std::vector<SocketAddress>::iterator it = ifaces_.begin();
+ it != ifaces_.end(); ++it) {
+ networks.push_back(new Network(it->hostname(), it->hostname(),
+ it->ipaddr()));
+ }
+ MergeNetworkList(networks, true);
+ }
+
+ Thread* thread_;
+ std::vector<SocketAddress> ifaces_;
+ int next_index_;
+ bool started_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_FAKENETWORK_H_
diff --git a/talk/base/faketaskrunner.h b/talk/base/faketaskrunner.h
new file mode 100644
index 0000000..6b5b035
--- /dev/null
+++ b/talk/base/faketaskrunner.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A fake TaskRunner for use in unit tests.
+
+#ifndef TALK_BASE_FAKETASKRUNNER_H_
+#define TALK_BASE_FAKETASKRUNNER_H_
+
+#include "talk/base/taskparent.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+class FakeTaskRunner : public TaskRunner {
+ public:
+ FakeTaskRunner() : current_time_(0) {}
+ virtual ~FakeTaskRunner() {}
+
+ virtual void WakeTasks() { RunTasks(); }
+
+ virtual int64 CurrentTime() {
+ // Implement if needed.
+ return current_time_++;
+ }
+
+ int64 current_time_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_FAKETASKRUNNER_H_
diff --git a/talk/base/filelock.cc b/talk/base/filelock.cc
new file mode 100644
index 0000000..2bc7045
--- /dev/null
+++ b/talk/base/filelock.cc
@@ -0,0 +1,79 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/filelock.h"
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+FileLock::FileLock(const std::string& path, FileStream* file)
+ : path_(path), file_(file) {
+}
+
+FileLock::~FileLock() {
+ MaybeUnlock();
+}
+
+void FileLock::Unlock() {
+ LOG_F(LS_INFO);
+ MaybeUnlock();
+}
+
+void FileLock::MaybeUnlock() {
+ if (file_.get() != NULL) {
+ LOG(LS_INFO) << "Unlocking:" << path_;
+ file_->Close();
+ Filesystem::DeleteFile(path_);
+ file_.reset();
+ }
+}
+
+FileLock* FileLock::TryLock(const std::string& path) {
+ FileStream* stream = new FileStream();
+ bool ok = false;
+#ifdef WIN32
+ // Open and lock in a single operation.
+ ok = stream->OpenShare(path, "a", _SH_DENYRW, NULL);
+#else // Linux and OSX
+ ok = stream->Open(path, "a", NULL) && stream->TryLock();
+#endif
+ if (ok) {
+ return new FileLock(path, stream);
+ } else {
+ // Something failed, either we didn't succeed to open the
+ // file or we failed to lock it. Anyway remove the heap
+ // allocated object and then return NULL to indicate failure.
+ delete stream;
+ return NULL;
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/filelock.h b/talk/base/filelock.h
new file mode 100644
index 0000000..a4936f5
--- /dev/null
+++ b/talk/base/filelock.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_FILELOCK_H_
+#define TALK_BASE_FILELOCK_H_
+
+#include <string>
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class FileStream;
+
+// Implements a very simple cross process lock based on a file.
+// When Lock(...) is called we try to open/create the file in read/write
+// mode without any sharing. (Or locking it with flock(...) on Unix)
+// If the process crash the OS will make sure that the file descriptor
+// is released and another process can accuire the lock.
+// This doesn't work on ancient OSX/Linux versions if used on NFS.
+// (Nfs-client before: ~2.6 and Linux Kernel < 2.6.)
+class FileLock {
+ public:
+ virtual ~FileLock();
+
+ // Attempts to lock the file. The caller owns the returned
+ // lock object. Returns NULL if the file already was locked.
+ static FileLock* TryLock(const std::string& path);
+ void Unlock();
+
+ protected:
+ FileLock(const std::string& path, FileStream* file);
+
+ private:
+ void MaybeUnlock();
+
+ std::string path_;
+ scoped_ptr<FileStream> file_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(FileLock);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_FILELOCK_H_
diff --git a/talk/base/filelock_unittest.cc b/talk/base/filelock_unittest.cc
new file mode 100644
index 0000000..e585f91
--- /dev/null
+++ b/talk/base/filelock_unittest.cc
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/event.h"
+#include "talk/base/filelock.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+const static std::string kLockFile = "TestLockFile";
+const static int kTimeoutMS = 5000;
+
+class FileLockTest : public testing::Test, public Runnable {
+ public:
+ FileLockTest() : done_(false, false), thread_lock_failed_(false) {
+ }
+
+ virtual void Run(Thread* t) {
+ scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+ // The lock is already owned by the main thread of
+ // this test, therefore the TryLock(...) call should fail.
+ thread_lock_failed_ = lock.get() == NULL;
+ done_.Set();
+ }
+
+ protected:
+ virtual void SetUp() {
+ thread_lock_failed_ = false;
+ Filesystem::GetAppTempFolder(&temp_dir_);
+ temp_file_ = Pathname(temp_dir_.pathname(), kLockFile);
+ }
+
+ void LockOnThread() {
+ locker_.Start(this);
+ done_.Wait(kTimeoutMS);
+ }
+
+ Event done_;
+ Thread locker_;
+ bool thread_lock_failed_;
+ Pathname temp_dir_;
+ Pathname temp_file_;
+};
+
+TEST_F(FileLockTest, TestLockFileDeleted) {
+ scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+ EXPECT_TRUE(lock.get() != NULL);
+ EXPECT_FALSE(Filesystem::IsAbsent(temp_file_.pathname()));
+ lock->Unlock();
+ EXPECT_TRUE(Filesystem::IsAbsent(temp_file_.pathname()));
+}
+
+TEST_F(FileLockTest, TestLock) {
+ scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+ EXPECT_TRUE(lock.get() != NULL);
+}
+
+TEST_F(FileLockTest, TestLockX2) {
+ scoped_ptr<FileLock> lock1(FileLock::TryLock(temp_file_.pathname()));
+ EXPECT_TRUE(lock1.get() != NULL);
+
+ scoped_ptr<FileLock> lock2(FileLock::TryLock(temp_file_.pathname()));
+ EXPECT_TRUE(lock2.get() == NULL);
+}
+
+TEST_F(FileLockTest, TestThreadedLock) {
+ scoped_ptr<FileLock> lock(FileLock::TryLock(temp_file_.pathname()));
+ EXPECT_TRUE(lock.get() != NULL);
+
+ LockOnThread();
+ EXPECT_TRUE(thread_lock_failed_);
+}
+
+} // namespace talk_base
diff --git a/talk/base/fileutils.cc b/talk/base/fileutils.cc
index 4504d29..7b0d348 100644
--- a/talk/base/fileutils.cc
+++ b/talk/base/fileutils.cc
@@ -154,15 +154,17 @@
#endif
}
-scoped_ptr<FilesystemInterface> Filesystem::default_filesystem_;
+FilesystemInterface* Filesystem::default_filesystem_ = NULL;
+
FilesystemInterface *Filesystem::EnsureDefaultFilesystem() {
- if (!default_filesystem_.get())
+ if (!default_filesystem_) {
#ifdef WIN32
- default_filesystem_.reset(new Win32Filesystem());
+ default_filesystem_ = new Win32Filesystem();
#else
- default_filesystem_.reset(new UnixFilesystem());
+ default_filesystem_ = new UnixFilesystem();
#endif
- return default_filesystem_.get();
+ }
+ return default_filesystem_;
}
bool FilesystemInterface::CopyFolder(const Pathname &old_path,
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
index 644516d..186c963 100644
--- a/talk/base/fileutils.h
+++ b/talk/base/fileutils.h
@@ -285,18 +285,18 @@
class Filesystem {
public:
static FilesystemInterface *default_filesystem() {
- ASSERT(default_filesystem_.get() != NULL);
- return default_filesystem_.get();
+ ASSERT(default_filesystem_ != NULL);
+ return default_filesystem_;
}
static void set_default_filesystem(FilesystemInterface *filesystem) {
- default_filesystem_.reset(filesystem);
+ default_filesystem_ = filesystem;
}
static FilesystemInterface *swap_default_filesystem(
FilesystemInterface *filesystem) {
- FilesystemInterface *cur = default_filesystem_.release();
- default_filesystem_.reset(filesystem);
+ FilesystemInterface *cur = default_filesystem_;
+ default_filesystem_ = filesystem;
return cur;
}
@@ -425,7 +425,7 @@
}
private:
- static scoped_ptr<FilesystemInterface> default_filesystem_;
+ static FilesystemInterface* default_filesystem_;
static FilesystemInterface *EnsureDefaultFilesystem();
DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem);
@@ -455,4 +455,3 @@
} // namespace talk_base
#endif // TALK_BASE_FILEUTILS_H_
-
diff --git a/talk/base/fileutils_mock.h b/talk/base/fileutils_mock.h
index 2ccb6a3..b91e802 100644
--- a/talk/base/fileutils_mock.h
+++ b/talk/base/fileutils_mock.h
@@ -33,9 +33,9 @@
#include <vector>
#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
#include "talk/base/pathutils.h"
#include "talk/base/stream.h"
-#include "testing/base/public/gmock.h"
namespace talk_base {
@@ -168,81 +168,81 @@
}
bool CreatePrivateFile(const Pathname &filename) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool DeleteFile(const Pathname &filename) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool DeleteEmptyFolder(const Pathname &folder) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool DeleteFolderContents(const Pathname &folder) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool DeleteFolderAndContents(const Pathname &folder) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool CreateFolder(const Pathname &pathname) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool IsFolder(const Pathname &pathname) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool IsFile(const Pathname &pathname) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool IsAbsent(const Pathname &pathname) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool IsTemporaryPath(const Pathname &pathname) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool GetTemporaryFolder(Pathname &path, bool create,
const std::string *append) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
std::string TempFilename(const Pathname &dir, const std::string &prefix) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return std::string();
}
bool GetFileSize(const Pathname &path, size_t *size) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool GetFileTime(const Pathname &path, FileTimeType which,
time_t* time) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool GetAppPathname(Pathname *path) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool GetAppDataFolder(Pathname *path, bool per_user) {
- EXPECT_TRUE_M(per_user, "Unsupported operation");
+ EXPECT_TRUE(per_user) << "Unsupported operation";
#ifdef WIN32
path->SetPathname("c:\\Users\\test_user", "");
#else
@@ -251,11 +251,11 @@
return true;
}
bool GetAppTempFolder(Pathname *path) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) {
- EXPECT_TRUE_M(false, "Unsupported operation");
+ EXPECT_TRUE(false) << "Unsupported operation";
return false;
}
Pathname GetCurrentDirectory() {
diff --git a/talk/base/firewallsocketserver.cc b/talk/base/firewallsocketserver.cc
index a99c72e..71385f8 100644
--- a/talk/base/firewallsocketserver.cc
+++ b/talk/base/firewallsocketserver.cc
@@ -177,11 +177,11 @@
const Rule& r = rules_[i];
if ((r.p != p) && (r.p != FP_ANY))
continue;
- if ((r.src.ip() != src.ip()) && !r.src.IsAny())
+ if ((r.src.ipaddr() != src.ipaddr()) && !r.src.IsAny())
continue;
if ((r.src.port() != src.port()) && (r.src.port() != 0))
continue;
- if ((r.dst.ip() != dst.ip()) && !r.dst.IsAny())
+ if ((r.dst.ipaddr() != dst.ipaddr()) && !r.dst.IsAny())
continue;
if ((r.dst.port() != dst.port()) && (r.dst.port() != 0))
continue;
diff --git a/talk/base/flags.h b/talk/base/flags.h
index a6eee00..f22e125 100644
--- a/talk/base/flags.h
+++ b/talk/base/flags.h
@@ -25,6 +25,9 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+
+// Originally comes from shared/commandlineflags/flags.h
+
// Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros,
// where xxx is the flag type. Flags are referred to via FLAG_yyy,
// where yyy is the flag name. For intialization and iteration of flags,
diff --git a/talk/base/gunit.h b/talk/base/gunit.h
index 9d04595..0682337 100644
--- a/talk/base/gunit.h
+++ b/talk/base/gunit.h
@@ -109,21 +109,6 @@
} \
} while (0);
-#ifdef __LP64__
-// When compiling in 64-bit mode we redefine the TEST and TEST_F macros to
-// append _64bit to all testsuite names to distinguish the test results from
-// those for the 32-bit cross-built binaries.
-#undef TEST
-#undef TEST_F
-// Copied and adjusted from google3/third_party/gtest/include/gtest/gtest.h
-#define TEST(test_case_name, test_name)\
- GTEST_TEST_(test_case_name##_64bit, test_name, \
- ::testing::Test, ::testing::internal::GetTestTypeId())
-#define TEST_F(test_fixture, test_name)\
- GTEST_TEST_(test_fixture##_64bit, test_name, test_fixture, \
- ::testing::internal::GetTypeId<test_fixture>())
-#endif
-
talk_base::Pathname GetTalkDirectory();
#endif // TALK_BASE_GUNIT_H_
diff --git a/talk/base/helpers.cc b/talk/base/helpers.cc
index 5b0e447..366a0fd 100644
--- a/talk/base/helpers.cc
+++ b/talk/base/helpers.cc
@@ -40,9 +40,10 @@
#endif
#include "talk/base/base64.h"
+#include "talk/base/basictypes.h"
#include "talk/base/logging.h"
#include "talk/base/scoped_ptr.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
// Protect against max macro inclusion.
#undef max
@@ -206,8 +207,9 @@
// This round about way of creating a global RNG is to safe-guard against
// indeterminant static initialization order.
scoped_ptr<RandomGenerator>& GetGlobalRng() {
- static scoped_ptr<RandomGenerator> g_rng(new SecureRandomGenerator());
- return g_rng;
+ LIBJINGLE_DEFINE_STATIC_LOCAL(scoped_ptr<RandomGenerator>, global_rng,
+ (new SecureRandomGenerator()));
+ return global_rng;
}
RandomGenerator& Rng() {
diff --git a/talk/base/ipaddress.cc b/talk/base/ipaddress.cc
new file mode 100644
index 0000000..036584f
--- /dev/null
+++ b/talk/base/ipaddress.cc
@@ -0,0 +1,388 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#ifdef OPENBSD
+#include <netinet/in_systm.h>
+#endif
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#include "talk/base/ipaddress.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+static const unsigned char kMappedPrefix[] = {0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0xFF};
+static bool IsPrivateV4(uint32 ip);
+static bool IsMappedAddress(const in6_addr& addr);
+static in_addr ExtractMappedAddress(const in6_addr& addr);
+
+uint32 IPAddress::v4AddressAsHostOrderInteger() const {
+ if (family_ == AF_INET) {
+ return ntohl(u_.ip4.s_addr);
+ } else {
+ return 0;
+ }
+}
+
+size_t IPAddress::Size() const {
+ switch (family_) {
+ case AF_INET:
+ return sizeof(in_addr);
+ case AF_INET6:
+ return sizeof(in6_addr);
+ }
+ return 0;
+}
+
+
+bool IPAddress::operator==(const IPAddress &other) const {
+ if (family_ != other.family_) {
+ return false;
+ }
+ if (family_ == AF_INET) {
+ return memcmp(&u_.ip4, &other.u_.ip4, sizeof(u_.ip4)) == 0;
+ }
+ if (family_ == AF_INET6) {
+ return memcmp(&u_.ip6, &other.u_.ip6, sizeof(u_.ip6)) == 0;
+ }
+ return family_ == AF_UNSPEC;
+}
+
+bool IPAddress::operator!=(const IPAddress &other) const {
+ return !((*this) == other);
+}
+
+bool IPAddress::operator >(const IPAddress &other) const {
+ return (*this) != other && !((*this) < other);
+}
+
+bool IPAddress::operator <(const IPAddress &other) const {
+ // IPv4 is 'less than' IPv6
+ if (family_ != other.family_) {
+ if (family_ == AF_UNSPEC) {
+ return true;
+ }
+ if (family_ == AF_INET && other.family_ == AF_INET6) {
+ return true;
+ }
+ return false;
+ }
+ // Comparing addresses of the same family.
+ switch (family_) {
+ case AF_INET: {
+ return ntohl(u_.ip4.s_addr) < ntohl(other.u_.ip4.s_addr);
+ }
+ case AF_INET6: {
+ return memcmp(&u_.ip6.s6_addr, &other.u_.ip6.s6_addr, 16) < 0;
+ }
+ }
+ // Catches AF_UNSPEC and invalid addresses.
+ return false;
+}
+
+std::ostream& operator<<(std::ostream& os, const IPAddress& ip) {
+ os << ip.ToString();
+ return os;
+}
+
+in6_addr IPAddress::ipv6_address() const {
+ return u_.ip6;
+}
+
+in_addr IPAddress::ipv4_address() const {
+ return u_.ip4;
+}
+
+std::string IPAddress::ToString() const {
+ if (family_ != AF_INET && family_ != AF_INET6) {
+ return std::string();
+ }
+ char buf[INET6_ADDRSTRLEN] = {0};
+ const void* src = &u_.ip4;
+ if (family_ == AF_INET6) {
+ src = &u_.ip6;
+ }
+ if (!talk_base::inet_ntop(family_, src, buf, sizeof(buf))) {
+ return std::string();
+ }
+ return std::string(buf);
+}
+
+IPAddress IPAddress::Normalized() const {
+ if (family_ != AF_INET6) {
+ return *this;
+ }
+ if (!IsMappedAddress(u_.ip6)) {
+ return *this;
+ }
+ in_addr addr = ExtractMappedAddress(u_.ip6);
+ return IPAddress(addr);
+}
+
+IPAddress IPAddress::AsIPv6Address() const {
+ if (family_ != AF_INET) {
+ return *this;
+ }
+ // uint32 v4 = (u_.ip4.s_addr);
+ in6_addr v6addr;
+ ::memcpy(&v6addr.s6_addr, kMappedPrefix, sizeof(kMappedPrefix));
+ ::memcpy(&v6addr.s6_addr[12], &u_.ip4.s_addr, sizeof(u_.ip4.s_addr));
+ return IPAddress(v6addr);
+}
+
+bool IsPrivateV4(uint32 ip_in_host_order) {
+ return ((ip_in_host_order >> 24) == 127) ||
+ ((ip_in_host_order >> 24) == 10) ||
+ ((ip_in_host_order >> 20) == ((172 << 4) | 1)) ||
+ ((ip_in_host_order >> 16) == ((192 << 8) | 168)) ||
+ ((ip_in_host_order >> 16) == ((169 << 8) | 254));
+}
+
+bool IsMappedAddress(const in6_addr& addr) {
+ return memcmp(&(addr.s6_addr), kMappedPrefix, sizeof(kMappedPrefix)) == 0;
+}
+
+in_addr ExtractMappedAddress(const in6_addr& in6) {
+ in_addr ipv4;
+ ::memcpy(&ipv4.s_addr, &in6.s6_addr[12], sizeof(ipv4.s_addr));
+ return ipv4;
+}
+
+bool IPFromHostEnt(hostent* host_ent, IPAddress* out) {
+ return IPFromHostEnt(host_ent, 0, out);
+}
+
+bool IPFromHostEnt(hostent* host_ent, int idx, IPAddress* out) {
+ if (!out || (idx < 0)) {
+ return false;
+ }
+ char** requested_address = host_ent->h_addr_list;
+ // Find the idx-th element (while checking for null, which terminates the
+ // list of addresses).
+ while (*requested_address && idx) {
+ idx--;
+ requested_address++;
+ }
+ if (!(*requested_address)) {
+ return false;
+ }
+
+ if (host_ent->h_addrtype == AF_INET) {
+ in_addr ip;
+ ip.s_addr = *reinterpret_cast<uint32*>(*requested_address);
+ *out = IPAddress(ip);
+ return true;
+ } else if (host_ent->h_addrtype == AF_INET6) {
+ in6_addr ip;
+ ::memcpy(&ip.s6_addr, *requested_address, host_ent->h_length);
+ *out = IPAddress(ip);
+ return true;
+ }
+ return false;
+}
+
+bool IPFromString(const std::string& str, IPAddress* out) {
+ if (!out) {
+ return false;
+ }
+ in_addr addr;
+ if (talk_base::inet_pton(AF_INET, str.c_str(), &addr) == 0) {
+ in6_addr addr6;
+ if (talk_base::inet_pton(AF_INET6, str.c_str(), &addr6) == 0) {
+ *out = IPAddress();
+ return false;
+ }
+ *out = IPAddress(addr6);
+ } else {
+ *out = IPAddress(addr);
+ }
+ return true;
+}
+
+bool IPIsAny(const IPAddress& ip) {
+ static const IPAddress kIPv4Any(INADDR_ANY);
+ static const IPAddress kIPv6Any(in6addr_any);
+ switch (ip.family()) {
+ case AF_INET:
+ return ip == kIPv4Any;
+ case AF_INET6:
+ return ip == kIPv6Any;
+ case AF_UNSPEC:
+ return false;
+ }
+ return false;
+}
+
+bool IPIsLoopback(const IPAddress& ip) {
+ static const IPAddress kIPv4Loopback(INADDR_LOOPBACK);
+ static const IPAddress kIPv6Loopback(in6addr_loopback);
+ switch (ip.family()) {
+ case AF_INET: {
+ return ip == kIPv4Loopback;
+ }
+ case AF_INET6: {
+ return ip == kIPv6Loopback;
+ }
+ }
+ return false;
+}
+
+bool IPIsPrivate(const IPAddress& ip) {
+ switch (ip.family()) {
+ case AF_INET: {
+ return IsPrivateV4(ip.v4AddressAsHostOrderInteger());
+ }
+ case AF_INET6: {
+ in6_addr v6 = ip.ipv6_address();
+ return (v6.s6_addr[0] == 0xFE && v6.s6_addr[1] == 0x80) ||
+ IPIsLoopback(ip);
+ }
+ }
+ return false;
+}
+
+size_t HashIP(const IPAddress& ip) {
+ switch (ip.family()) {
+ case AF_INET: {
+ return ip.ipv4_address().s_addr;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = ip.ipv6_address();
+ const uint32* v6_as_ints =
+ reinterpret_cast<const uint32*>(&v6addr.s6_addr);
+ return v6_as_ints[0] ^ v6_as_ints[1] ^ v6_as_ints[2] ^ v6_as_ints[3];
+ }
+ }
+ return 0;
+}
+
+IPAddress TruncateIP(const IPAddress& ip, int length) {
+ if (length < 0) {
+ return IPAddress();
+ }
+ if (ip.family() == AF_INET) {
+ if (length > 31) {
+ return ip;
+ }
+ if (length == 0) {
+ return IPAddress(INADDR_ANY);
+ }
+ int mask = (0xFFFFFFFF << (32 - length));
+ uint32 host_order_ip = NetworkToHost32(ip.ipv4_address().s_addr);
+ in_addr masked;
+ masked.s_addr = HostToNetwork32(host_order_ip & mask);
+ return IPAddress(masked);
+ } else if (ip.family() == AF_INET6) {
+ if (length > 127) {
+ return ip;
+ }
+ if (length == 0) {
+ return IPAddress(in6addr_any);
+ }
+ in6_addr v6addr = ip.ipv6_address();
+ int position = length / 32;
+ int inner_length = (length - (position * 32));
+ int inner_mask = (0xFFFFFFFF << (32 - inner_length));
+ uint32* v6_as_ints =
+ reinterpret_cast<uint32*>(&v6addr.s6_addr);
+ in6_addr ip_addr = ip.ipv6_address();
+ for (int i = 0; i < 4; ++i) {
+ if (i == position) {
+ uint32 host_order_inner = NetworkToHost32(v6_as_ints[i]);
+ v6_as_ints[i] = HostToNetwork32(host_order_inner & inner_mask);
+ } else if (i > position) {
+ v6_as_ints[i] = 0;
+ }
+ }
+ return IPAddress(v6addr);
+ }
+ return IPAddress();
+}
+
+int CountIPMaskBits(IPAddress mask) {
+ // Doing this the lazy/simple way.
+ // Clever bit tricks welcome but please be careful re: byte order.
+ uint32 word_to_count = 0;
+ int bits = 0;
+ switch (mask.family()) {
+ case AF_INET: {
+ word_to_count = NetworkToHost32(mask.ipv4_address().s_addr);
+ break;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = mask.ipv6_address();
+ const uint32* v6_as_ints =
+ reinterpret_cast<const uint32*>(&v6addr.s6_addr);
+ int i = 0;
+ for (; i < 4; ++i) {
+ if (v6_as_ints[i] != 0xFFFFFFFF) {
+ break;
+ }
+ }
+ if (i < 4) {
+ word_to_count = NetworkToHost32(v6_as_ints[i]);
+ }
+ bits = (i * 32);
+ break;
+ }
+ default: {
+ return 0;
+ }
+ }
+ // Check for byte boundaries before scanning.
+ if (word_to_count == 0) {
+ return bits;
+ } else if (word_to_count == 0xFF000000) {
+ return bits + 8;
+ } else if (word_to_count == 0xFFFF0000) {
+ return bits + 16;
+ } else if (word_to_count == 0xFFFFFF00) {
+ return bits + 24;
+ }
+
+ while (word_to_count & 0x80000000) {
+ word_to_count <<= 1;
+ ++bits;
+ }
+ return bits;
+}
+} // Namespace talk base
diff --git a/talk/base/ipaddress.h b/talk/base/ipaddress.h
new file mode 100644
index 0000000..b1063fd
--- /dev/null
+++ b/talk/base/ipaddress.h
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_IPADDRESS_H_
+#define TALK_BASE_IPADDRESS_H_
+
+#ifdef POSIX
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#endif
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+// Version-agnostic IP address class, wraps a union of in_addr and in6_addr.
+class IPAddress {
+ public:
+ IPAddress() : family_(AF_UNSPEC) {
+ ::memset(&u_, 0, sizeof(u_));
+ }
+
+ explicit IPAddress(const in_addr &ip4) : family_(AF_INET) {
+ memset(&u_, 0, sizeof(u_));
+ u_.ip4 = ip4;
+ }
+
+ explicit IPAddress(const in6_addr &ip6) : family_(AF_INET6) {
+ u_.ip6 = ip6;
+ }
+
+ explicit IPAddress(uint32 ip_in_host_byte_order) : family_(AF_INET) {
+ memset(&u_, 0, sizeof(u_));
+ u_.ip4.s_addr = htonl(ip_in_host_byte_order);
+ }
+
+ IPAddress(const IPAddress &other) : family_(other.family_) {
+ ::memcpy(&u_, &other.u_, sizeof(u_));
+ }
+
+ ~IPAddress() {}
+
+ const IPAddress & operator=(const IPAddress &other) {
+ family_ = other.family_;
+ ::memcpy(&u_, &other.u_, sizeof(u_));
+ return *this;
+ }
+
+ bool operator==(const IPAddress &other) const;
+ bool operator!=(const IPAddress &other) const;
+ bool operator <(const IPAddress &other) const;
+ bool operator >(const IPAddress &other) const;
+ friend std::ostream& operator<<(std::ostream& os, const IPAddress& addr);
+
+ int family() const { return family_; }
+ in_addr ipv4_address() const;
+ in6_addr ipv6_address() const;
+
+ // Returns the number of bytes needed to store the raw address.
+ size_t Size() const;
+
+ // Wraps inet_ntop.
+ std::string ToString() const;
+
+ // Returns an unmapped address from a possibly-mapped address.
+ // Returns the same address if this isn't a mapped address.
+ IPAddress Normalized() const;
+
+ // Returns this address as an IPv6 address.
+ // Maps v4 addresses (as ::ffff:a.b.c.d), returns v6 addresses unchanged.
+ IPAddress AsIPv6Address() const;
+
+ // For socketaddress' benefit. Returns the IP in host byte order.
+ uint32 v4AddressAsHostOrderInteger() const;
+
+ private:
+ int family_;
+ union {
+ in_addr ip4;
+ in6_addr ip6;
+ } u_;
+};
+
+bool IPFromHostEnt(hostent* hostEnt, IPAddress* out);
+bool IPFromHostEnt(hostent* hostEnt, int idx, IPAddress* out);
+bool IPFromString(const std::string& str, IPAddress* out);
+bool IPIsAny(const IPAddress& ip);
+bool IPIsLoopback(const IPAddress& ip);
+bool IPIsPrivate(const IPAddress& ip);
+size_t HashIP(const IPAddress& ip);
+
+// Returns 'ip' truncated to be 'length' bits long.
+IPAddress TruncateIP(const IPAddress& ip, int length);
+// Returns the number of contiguously set bits, counting from the MSB in network
+// byte order, in this IPAddress. Bits after the first 0 encountered are not
+// counted.
+int CountIPMaskBits(IPAddress mask);
+} // namespace talk_base
+
+#endif // TALK_BASE_IPADDRESS_H_
diff --git a/talk/base/ipaddress_unittest.cc b/talk/base/ipaddress_unittest.cc
new file mode 100644
index 0000000..40cf187
--- /dev/null
+++ b/talk/base/ipaddress_unittest.cc
@@ -0,0 +1,765 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/ipaddress.h"
+
+namespace talk_base {
+
+static const unsigned int kIPv4AddrSize = 4;
+static const unsigned int kIPv6AddrSize = 16;
+static const unsigned int kIPv4RFC1918Addr = 0xC0A80701;
+static const unsigned int kIPv4PublicAddr = 0x01020304;
+static const in6_addr kIPv6LinkLocalAddr = {{{0xfe, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv6PublicAddr = {{{0x24, 0x01, 0xfa, 0x00,
+ 0x00, 0x04, 0x10, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv6CompatAddr = {{{0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xfe, 0xe5, 0x00, 0xc3}}};
+static const in6_addr kIPv4MappedAnyAddr = {{{0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00}}};
+static const in6_addr kIPv4MappedLoopbackAddr = {{{0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff,
+ 0x7f, 0x00, 0x00, 0x01}}};
+static const in6_addr kIPv4MappedRFC1918Addr = {{{0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff,
+ 0xc0, 0xa8, 0x07, 0x01}}};
+static const in6_addr kIPv4MappedPublicAddr = {{{0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0xff,
+ 0x01, 0x02, 0x03, 0x04}}};
+static const in6_addr kIPv6AllNodes = {{{0xff, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01}}};
+
+static const std::string kIPv4AnyAddrString = "0.0.0.0";
+static const std::string kIPv4LoopbackAddrString = "127.0.0.1";
+static const std::string kIPv4RFC1918AddrString = "192.168.7.1";
+static const std::string kIPv4PublicAddrString = "1.2.3.4";
+static const std::string kIPv6AnyAddrString = "::";
+static const std::string kIPv6LoopbackAddrString = "::1";
+static const std::string kIPv6LinkLocalAddrString = "fe80::be30:5bff:fee5:c3";
+static const std::string kIPv6PublicAddrString =
+ "2401:fa00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv4MappedAnyAddrString = "::ffff:0:0";
+static const std::string kIPv4MappedRFC1918AddrString = "::ffff:c0a8:701";
+static const std::string kIPv4MappedLoopbackAddrString = "::ffff:7f00:1";
+static const std::string kIPv4MappedPublicAddrString = "::ffff:102:0304";
+static const std::string kIPv4MappedV4StyleAddrString = "::ffff:192.168.7.1";
+
+static const std::string kIPv4BrokenString1 = "192.168.7.";
+static const std::string kIPv4BrokenString2 = "192.168.7.1.1";
+static const std::string kIPv4BrokenString3 = "192.168.7.1:80";
+static const std::string kIPv4BrokenString4 = "192.168.7.ONE";
+static const std::string kIPv4BrokenString5 = "-192.168.7.1";
+static const std::string kIPv4BrokenString6 = "256.168.7.1";
+static const std::string kIPv6BrokenString1 = "2401:fa00:4:1000:be30";
+static const std::string kIPv6BrokenString2 =
+ "2401:fa00:4:1000:be30:5bff:fee5:c3:1";
+static const std::string kIPv6BrokenString3 =
+ "[2401:fa00:4:1000:be30:5bff:fee5:c3]:1";
+static const std::string kIPv6BrokenString4 =
+ "2401::4::be30";
+static const std::string kIPv6BrokenString5 =
+ "2401:::4:fee5:be30";
+static const std::string kIPv6BrokenString6 =
+ "2401f:fa00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString7 =
+ "2401:ga00:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString8 =
+ "2401:fa000:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString9 =
+ "2401:fal0:4:1000:be30:5bff:fee5:c3";
+static const std::string kIPv6BrokenString10 =
+ "::ffff:192.168.7.";
+static const std::string kIPv6BrokenString11 =
+ "::ffff:192.168.7.1.1.1";
+static const std::string kIPv6BrokenString12 =
+ "::fffe:192.168.7.1";
+static const std::string kIPv6BrokenString13 =
+ "::ffff:192.168.7.ff";
+static const std::string kIPv6BrokenString14 =
+ "0x2401:fa00:4:1000:be30:5bff:fee5:c3";
+
+bool IPFromHostEntWorks(const std::string& name, int expected_family,
+ IPAddress expected_addr) {
+ struct hostent* ent = gethostbyname(name.c_str());
+ if (ent) {
+ IPAddress addr;
+ if (!IPFromHostEnt(ent, &addr)) {
+ return false;
+ }
+ return addr == expected_addr;
+ }
+ return true;
+}
+
+bool AreEqual(const IPAddress& addr,
+ const IPAddress& addr2) {
+ if ((IPIsAny(addr) != IPIsAny(addr2)) ||
+ (IPIsLoopback(addr) != IPIsLoopback(addr2)) ||
+ (IPIsPrivate(addr) != IPIsPrivate(addr2)) ||
+ (HashIP(addr) != HashIP(addr2)) ||
+ (addr.Size() != addr2.Size()) ||
+ (addr.family() != addr2.family()) ||
+ (addr.ToString() != addr2.ToString())) {
+ return false;
+ }
+ in_addr v4addr, v4addr2;
+ v4addr = addr.ipv4_address();
+ v4addr2 = addr2.ipv4_address();
+ if (0 != memcmp(&v4addr, &v4addr2, sizeof(v4addr))) {
+ return false;
+ }
+ in6_addr v6addr, v6addr2;
+ v6addr = addr.ipv6_address();
+ v6addr2 = addr2.ipv6_address();
+ if (0 != memcmp(&v6addr, &v6addr2, sizeof(v6addr))) {
+ return false;
+ }
+ return true;
+}
+
+bool BrokenIPStringFails(const std::string& broken) {
+ IPAddress addr(0); // Intentionally make it v4.
+ if (IPFromString(kIPv4BrokenString1, &addr)) {
+ return false;
+ }
+ return addr.family() == AF_UNSPEC;
+}
+
+bool CheckMaskCount(const std::string& mask, int expected_length) {
+ IPAddress addr;
+ return IPFromString(mask, &addr) &&
+ (expected_length == CountIPMaskBits(addr));
+}
+
+bool CheckTruncateIP(const std::string& initial, int truncate_length,
+ const std::string& expected_result) {
+ IPAddress addr, expected;
+ IPFromString(initial, &addr);
+ IPFromString(expected_result, &expected);
+ IPAddress truncated = TruncateIP(addr, truncate_length);
+ return truncated == expected;
+}
+
+TEST(IPAddressTest, TestDefaultCtor) {
+ IPAddress addr;
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+
+ EXPECT_EQ(0U, addr.Size());
+ EXPECT_EQ(AF_UNSPEC, addr.family());
+ EXPECT_EQ("", addr.ToString());
+}
+
+TEST(IPAddressTest, TestInAddrCtor) {
+ in_addr v4addr;
+
+ // Test V4 Any address.
+ v4addr.s_addr = INADDR_ANY;
+ IPAddress addr(v4addr);
+ EXPECT_TRUE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4AnyAddrString, addr.ToString());
+
+ // Test a V4 loopback address.
+ v4addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr = IPAddress(v4addr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_TRUE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString());
+
+ // Test an RFC1918 address.
+ v4addr.s_addr = htonl(kIPv4RFC1918Addr);
+ addr = IPAddress(v4addr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString());
+
+ // Test a 'normal' v4 address.
+ v4addr.s_addr = htonl(kIPv4PublicAddr);
+ addr = IPAddress(v4addr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestInAddr6Ctor) {
+ // Test v6 empty.
+ IPAddress addr(in6addr_any);
+ EXPECT_TRUE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv6AddrSize, addr.Size());
+ EXPECT_EQ(kIPv6AnyAddrString, addr.ToString());
+
+ // Test v6 loopback.
+ addr = IPAddress(in6addr_loopback);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_TRUE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv6AddrSize, addr.Size());
+ EXPECT_EQ(kIPv6LoopbackAddrString, addr.ToString());
+
+ // Test v6 link-local.
+ addr = IPAddress(kIPv6LinkLocalAddr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv6AddrSize, addr.Size());
+ EXPECT_EQ(kIPv6LinkLocalAddrString, addr.ToString());
+
+ // Test v6 global address.
+ addr = IPAddress(kIPv6PublicAddr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv6AddrSize, addr.Size());
+ EXPECT_EQ(kIPv6PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestUint32Ctor) {
+ // Test V4 Any address.
+ IPAddress addr(0);
+ EXPECT_TRUE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4AnyAddrString, addr.ToString());
+
+ // Test a V4 loopback address.
+ addr = IPAddress(INADDR_LOOPBACK);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_TRUE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4LoopbackAddrString, addr.ToString());
+
+ // Test an RFC1918 address.
+ addr = IPAddress(kIPv4RFC1918Addr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_TRUE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4RFC1918AddrString, addr.ToString());
+
+ // Test a 'normal' v4 address.
+ addr = IPAddress(kIPv4PublicAddr);
+ EXPECT_FALSE(IPIsAny(addr));
+ EXPECT_FALSE(IPIsLoopback(addr));
+ EXPECT_FALSE(IPIsPrivate(addr));
+ EXPECT_EQ(kIPv4AddrSize, addr.Size());
+ EXPECT_EQ(kIPv4PublicAddrString, addr.ToString());
+}
+
+TEST(IPAddressTest, TestHostEntCtor) {
+ IPAddress addr(INADDR_LOOPBACK);
+ EXPECT_PRED3(IPFromHostEntWorks, "localhost", AF_INET, addr);
+
+ addr = IPAddress(kIPv6AllNodes);
+ EXPECT_PRED3(IPFromHostEntWorks, "ip6-allnodes", AF_INET6, addr);
+
+ // gethostbyname works for literal addresses too
+ addr = IPAddress(INADDR_ANY);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv4AnyAddrString, AF_INET, addr);
+ addr = IPAddress(kIPv4RFC1918Addr);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv4RFC1918AddrString, AF_INET, addr);
+ addr = IPAddress(kIPv4PublicAddr);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv4PublicAddrString, AF_INET, addr);
+
+ addr = IPAddress(in6addr_any);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv6AnyAddrString, AF_INET6, addr);
+ addr = IPAddress(in6addr_loopback);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv6LoopbackAddrString, AF_INET6, addr);
+ addr = IPAddress(kIPv6LinkLocalAddr);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv6LinkLocalAddrString, AF_INET6, addr);
+ addr = IPAddress(kIPv6PublicAddr);
+ EXPECT_PRED3(IPFromHostEntWorks,
+ kIPv6PublicAddrString, AF_INET6, addr);
+}
+
+TEST(IPAddressTest, TestCopyCtor) {
+ in_addr v4addr;
+ v4addr.s_addr = htonl(kIPv4PublicAddr);
+ IPAddress addr(v4addr);
+ IPAddress addr2(addr);
+
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(INADDR_ANY);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(INADDR_LOOPBACK);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(kIPv4PublicAddr);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(kIPv4RFC1918Addr);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(in6addr_any);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(in6addr_loopback);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(kIPv6LinkLocalAddr);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr = IPAddress(kIPv6PublicAddr);
+ addr2 = IPAddress(addr);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+}
+
+TEST(IPAddressTest, TestEquality) {
+ // Check v4 equality
+ in_addr v4addr, v4addr2;
+ v4addr.s_addr = htonl(kIPv4PublicAddr);
+ v4addr2.s_addr = htonl(kIPv4PublicAddr + 1);
+ IPAddress addr(v4addr);
+ IPAddress addr2(v4addr2);
+ IPAddress addr3(v4addr);
+
+ EXPECT_TRUE(addr == addr);
+ EXPECT_TRUE(addr2 == addr2);
+ EXPECT_TRUE(addr3 == addr3);
+ EXPECT_TRUE(addr == addr3);
+ EXPECT_TRUE(addr3 == addr);
+ EXPECT_FALSE(addr2 == addr);
+ EXPECT_FALSE(addr2 == addr3);
+ EXPECT_FALSE(addr == addr2);
+ EXPECT_FALSE(addr3 == addr2);
+
+ // Check v6 equality
+ IPAddress addr4(kIPv6PublicAddr);
+ IPAddress addr5(kIPv6LinkLocalAddr);
+ IPAddress addr6(kIPv6PublicAddr);
+
+ EXPECT_TRUE(addr4 == addr4);
+ EXPECT_TRUE(addr5 == addr5);
+ EXPECT_TRUE(addr4 == addr6);
+ EXPECT_TRUE(addr6 == addr4);
+ EXPECT_FALSE(addr4 == addr5);
+ EXPECT_FALSE(addr5 == addr4);
+ EXPECT_FALSE(addr6 == addr5);
+ EXPECT_FALSE(addr5 == addr6);
+
+ // Check v4/v6 cross-equality
+ EXPECT_FALSE(addr == addr4);
+ EXPECT_FALSE(addr == addr5);
+ EXPECT_FALSE(addr == addr6);
+ EXPECT_FALSE(addr4 == addr);
+ EXPECT_FALSE(addr5 == addr);
+ EXPECT_FALSE(addr6 == addr);
+ EXPECT_FALSE(addr2 == addr4);
+ EXPECT_FALSE(addr2 == addr5);
+ EXPECT_FALSE(addr2 == addr6);
+ EXPECT_FALSE(addr4 == addr2);
+ EXPECT_FALSE(addr5 == addr2);
+ EXPECT_FALSE(addr6 == addr2);
+ EXPECT_FALSE(addr3 == addr4);
+ EXPECT_FALSE(addr3 == addr5);
+ EXPECT_FALSE(addr3 == addr6);
+ EXPECT_FALSE(addr4 == addr3);
+ EXPECT_FALSE(addr5 == addr3);
+ EXPECT_FALSE(addr6 == addr3);
+
+ // Special cases: loopback and any.
+ // They're special but they're still not equal.
+ IPAddress v4loopback(htonl(INADDR_LOOPBACK));
+ IPAddress v6loopback(in6addr_loopback);
+ EXPECT_FALSE(v4loopback == v6loopback);
+
+ IPAddress v4any(0);
+ IPAddress v6any(in6addr_any);
+ EXPECT_FALSE(v4any == v6any);
+}
+
+TEST(IPAddressTest, TestComparison) {
+ // Defined in 'ascending' order.
+ // v6 > v4, and intra-family sorting is purely numerical
+ IPAddress addr0; // AF_UNSPEC
+ IPAddress addr1(INADDR_ANY); // 0.0.0.0
+ IPAddress addr2(kIPv4PublicAddr); // 1.2.3.4
+ IPAddress addr3(INADDR_LOOPBACK); // 127.0.0.1
+ IPAddress addr4(kIPv4RFC1918Addr); // 192.168.7.1.
+ IPAddress addr5(in6addr_any); // ::
+ IPAddress addr6(in6addr_loopback); // ::1
+ IPAddress addr7(kIPv6PublicAddr); // 2401....
+ IPAddress addr8(kIPv6LinkLocalAddr); // fe80....
+
+ EXPECT_TRUE(addr0 < addr1);
+ EXPECT_TRUE(addr1 < addr2);
+ EXPECT_TRUE(addr2 < addr3);
+ EXPECT_TRUE(addr3 < addr4);
+ EXPECT_TRUE(addr4 < addr5);
+ EXPECT_TRUE(addr5 < addr6);
+ EXPECT_TRUE(addr6 < addr7);
+ EXPECT_TRUE(addr7 < addr8);
+
+ EXPECT_FALSE(addr0 > addr1);
+ EXPECT_FALSE(addr1 > addr2);
+ EXPECT_FALSE(addr2 > addr3);
+ EXPECT_FALSE(addr3 > addr4);
+ EXPECT_FALSE(addr4 > addr5);
+ EXPECT_FALSE(addr5 > addr6);
+ EXPECT_FALSE(addr6 > addr7);
+ EXPECT_FALSE(addr7 > addr8);
+
+ EXPECT_FALSE(addr0 > addr0);
+ EXPECT_FALSE(addr1 > addr1);
+ EXPECT_FALSE(addr2 > addr2);
+ EXPECT_FALSE(addr3 > addr3);
+ EXPECT_FALSE(addr4 > addr4);
+ EXPECT_FALSE(addr5 > addr5);
+ EXPECT_FALSE(addr6 > addr6);
+ EXPECT_FALSE(addr7 > addr7);
+ EXPECT_FALSE(addr8 > addr8);
+
+ EXPECT_FALSE(addr0 < addr0);
+ EXPECT_FALSE(addr1 < addr1);
+ EXPECT_FALSE(addr2 < addr2);
+ EXPECT_FALSE(addr3 < addr3);
+ EXPECT_FALSE(addr4 < addr4);
+ EXPECT_FALSE(addr5 < addr5);
+ EXPECT_FALSE(addr6 < addr6);
+ EXPECT_FALSE(addr7 < addr7);
+ EXPECT_FALSE(addr8 < addr8);
+}
+
+TEST(IPAddressTest, TestFromString) {
+ IPAddress addr;
+ IPAddress addr2;
+ addr2 = IPAddress(INADDR_ANY);
+
+ EXPECT_TRUE(IPFromString(kIPv4AnyAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv4AnyAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(INADDR_LOOPBACK);
+ EXPECT_TRUE(IPFromString(kIPv4LoopbackAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv4LoopbackAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(kIPv4RFC1918Addr);
+ EXPECT_TRUE(IPFromString(kIPv4RFC1918AddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv4RFC1918AddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(kIPv4PublicAddr);
+ EXPECT_TRUE(IPFromString(kIPv4PublicAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv4PublicAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(in6addr_any);
+ EXPECT_TRUE(IPFromString(kIPv6AnyAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv6AnyAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(in6addr_loopback);
+ EXPECT_TRUE(IPFromString(kIPv6LoopbackAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv6LoopbackAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(kIPv6LinkLocalAddr);
+ EXPECT_TRUE(IPFromString(kIPv6LinkLocalAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv6LinkLocalAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(kIPv6PublicAddr);
+ EXPECT_TRUE(IPFromString(kIPv6PublicAddrString, &addr));
+ EXPECT_EQ(addr.ToString(), kIPv6PublicAddrString);
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ addr2 = IPAddress(kIPv4MappedRFC1918Addr);
+ EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr));
+ EXPECT_PRED2(AreEqual, addr, addr2);
+
+ // Broken cases, should set addr to AF_UNSPEC.
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString1);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString2);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString3);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString4);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString5);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv4BrokenString6);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString1);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString2);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString3);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString4);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString5);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString6);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString7);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString8);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString9);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString10);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString11);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString12);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString13);
+ EXPECT_PRED1(BrokenIPStringFails, kIPv6BrokenString14);
+}
+
+TEST(IPAddressTest, TestIsPrivate) {
+ EXPECT_FALSE(IPIsPrivate(IPAddress(INADDR_ANY)));
+ EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4PublicAddr)));
+ EXPECT_FALSE(IPIsPrivate(IPAddress(in6addr_any)));
+ EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv6PublicAddr)));
+ EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedAnyAddr)));
+ EXPECT_FALSE(IPIsPrivate(IPAddress(kIPv4MappedPublicAddr)));
+
+ EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv4RFC1918Addr)));
+ EXPECT_TRUE(IPIsPrivate(IPAddress(INADDR_LOOPBACK)));
+ EXPECT_TRUE(IPIsPrivate(IPAddress(in6addr_loopback)));
+ EXPECT_TRUE(IPIsPrivate(IPAddress(kIPv6LinkLocalAddr)));
+}
+
+TEST(IPAddressTest, TestIsLoopback) {
+ EXPECT_FALSE(IPIsLoopback(IPAddress(INADDR_ANY)));
+ EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4PublicAddr)));
+ EXPECT_FALSE(IPIsLoopback(IPAddress(in6addr_any)));
+ EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv6PublicAddr)));
+ EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedAnyAddr)));
+ EXPECT_FALSE(IPIsLoopback(IPAddress(kIPv4MappedPublicAddr)));
+
+ EXPECT_TRUE(IPIsLoopback(IPAddress(INADDR_LOOPBACK)));
+ EXPECT_TRUE(IPIsLoopback(IPAddress(in6addr_loopback)));
+}
+
+TEST(IPAddressTest, TestNormalized) {
+ // Check normalizing a ::ffff:a.b.c.d address.
+ IPAddress addr;
+ EXPECT_TRUE(IPFromString(kIPv4MappedV4StyleAddrString, &addr));
+ IPAddress addr2(kIPv4RFC1918Addr);
+ addr = addr.Normalized();
+ EXPECT_EQ(addr2, addr);
+
+ // Check normalizing a ::ffff:aabb:ccdd address.
+ addr = IPAddress(kIPv4MappedPublicAddr);
+ addr2 = IPAddress(kIPv4PublicAddr);
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+
+ // Check that a non-mapped v6 addresses isn't altered.
+ addr = IPAddress(kIPv6PublicAddr);
+ addr2 = IPAddress(kIPv6PublicAddr);
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+
+ // Check that addresses that look a bit like mapped addresses aren't altered
+ EXPECT_TRUE(IPFromString("fe80::ffff:0102:0304", &addr));
+ addr2 = addr;
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+ EXPECT_TRUE(IPFromString("::0102:0304", &addr));
+ addr2 = addr;
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+ // This string should 'work' as an IP address but is not a mapped address,
+ // so it shouldn't change on normalization.
+ EXPECT_TRUE(IPFromString("::192.168.7.1", &addr));
+ addr2 = addr;
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+
+ // Check that v4 addresses aren't altered.
+ addr = IPAddress(htonl(kIPv4PublicAddr));
+ addr2 = IPAddress(htonl(kIPv4PublicAddr));
+ addr = addr.Normalized();
+ EXPECT_EQ(addr, addr2);
+}
+
+TEST(IPAddressTest, TestAsIPv6Address) {
+ IPAddress addr(kIPv4PublicAddr);
+ IPAddress addr2(kIPv4MappedPublicAddr);
+ addr = addr.AsIPv6Address();
+ EXPECT_EQ(addr, addr2);
+
+ addr = IPAddress(kIPv4MappedPublicAddr);
+ addr2 = IPAddress(kIPv4MappedPublicAddr);
+ addr = addr.AsIPv6Address();
+ EXPECT_EQ(addr, addr2);
+
+ addr = IPAddress(kIPv6PublicAddr);
+ addr2 = IPAddress(kIPv6PublicAddr);
+ addr = addr.AsIPv6Address();
+ EXPECT_EQ(addr, addr2);
+}
+
+TEST(IPAddressTest, TestCountIPMaskBits) {
+ IPAddress mask;
+ // IPv4 on byte boundaries
+ EXPECT_PRED2(CheckMaskCount, "255.255.255.255", 32);
+ EXPECT_PRED2(CheckMaskCount, "255.255.255.0", 24);
+ EXPECT_PRED2(CheckMaskCount, "255.255.0.0", 16);
+ EXPECT_PRED2(CheckMaskCount, "255.0.0.0", 8);
+ EXPECT_PRED2(CheckMaskCount, "0.0.0.0", 0);
+
+ // IPv4 not on byte boundaries
+ EXPECT_PRED2(CheckMaskCount, "128.0.0.0", 1);
+ EXPECT_PRED2(CheckMaskCount, "224.0.0.0", 3);
+ EXPECT_PRED2(CheckMaskCount, "255.248.0.0", 13);
+ EXPECT_PRED2(CheckMaskCount, "255.255.224.0", 19);
+ EXPECT_PRED2(CheckMaskCount, "255.255.255.252", 30);
+
+ // V6 on byte boundaries
+ EXPECT_PRED2(CheckMaskCount, "::", 0);
+ EXPECT_PRED2(CheckMaskCount, "ff00::", 8);
+ EXPECT_PRED2(CheckMaskCount, "ffff::", 16);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ff00::", 24);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff::", 32);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00::", 40);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::", 48);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00::", 56);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff::", 64);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff00::", 72);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff::", 80);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff00::", 88);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff::", 96);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0000", 104);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0000", 112);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128);
+
+ // V6 not on byte boundaries.
+ EXPECT_PRED2(CheckMaskCount, "8000::", 1);
+ EXPECT_PRED2(CheckMaskCount, "ff80::", 9);
+ EXPECT_PRED2(CheckMaskCount, "ffff:fe00::", 23);
+ EXPECT_PRED2(CheckMaskCount, "ffff:fffe::", 31);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:e000::", 35);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffe0::", 43);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:f800::", 53);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:fff8::", 61);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fc00::", 70);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fffc::", 78);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:8000::", 81);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff80::", 89);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fe00::", 103);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0000", 111);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00", 118);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc", 126);
+
+ // Non-contiguous ranges. These are kind-of invalid but lets test them
+ // to make sure they don't crash anything or infinite loop or something.
+ EXPECT_PRED2(CheckMaskCount, "217.0.0.0", 2);
+ EXPECT_PRED2(CheckMaskCount, "255.185.0.0", 9);
+ EXPECT_PRED2(CheckMaskCount, "255.255.251.0", 21);
+ EXPECT_PRED2(CheckMaskCount, "255.255.251.255", 21);
+ EXPECT_PRED2(CheckMaskCount, "255.255.254.201", 23);
+ EXPECT_PRED2(CheckMaskCount, "::1", 0);
+ EXPECT_PRED2(CheckMaskCount, "fe80::1", 7);
+ EXPECT_PRED2(CheckMaskCount, "ff80::1", 9);
+ EXPECT_PRED2(CheckMaskCount, "ffff::1", 16);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ff00:1::1", 24);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff::ffff:1", 32);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00:1::", 40);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::ff00", 48);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00:1234::", 56);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:0012::ffff", 64);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff01::", 72);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::", 80);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::", 88);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000", 96);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000", 104);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211", 112);
+ EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f", 120);
+}
+
+TEST(IPAddressTest, TestTruncateIP) {
+ EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 24, "255.255.255.0");
+ EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 16, "255.255.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 8, "255.0.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "202.67.7.255", 24, "202.67.7.0");
+ EXPECT_PRED3(CheckTruncateIP, "202.129.65.205", 16, "202.129.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "55.25.2.77", 8, "55.0.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "74.128.99.254", 1, "0.0.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "106.55.99.254", 3, "96.0.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "172.167.53.222", 13, "172.160.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "255.255.224.0", 18, "255.255.192.0");
+ EXPECT_PRED3(CheckTruncateIP, "255.255.255.252", 28, "255.255.255.240");
+
+ EXPECT_PRED3(CheckTruncateIP, "fe80:1111:2222:3333:4444:5555:6666:7777", 1,
+ "8000::");
+ EXPECT_PRED3(CheckTruncateIP, "fff0:1111:2222:3333:4444:5555:6666:7777", 9,
+ "ff80::");
+ EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 23,
+ "ffff:fe00::");
+ EXPECT_PRED3(CheckTruncateIP, "2400:f9af:e456:1111:2222:3333:4444:5555", 35,
+ "2400:f9af:e000::");
+ EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4444:5555:6666:7777:8888", 53,
+ "9999:1111:2233:4000::");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 68,
+ "1111:2222:3333:4444:5000::");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 92,
+ "1111:2222:3333:4444:5555:6660::");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 105,
+ "1111:2222:3333:4444:5555:6666:7700::");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 124,
+ "1111:2222:3333:4444:5555:6666:7777:8880");
+
+ // Slightly degenerate cases
+ EXPECT_PRED3(CheckTruncateIP, "202.165.33.127", 32, "202.165.33.127");
+ EXPECT_PRED3(CheckTruncateIP, "235.105.77.12", 0, "0.0.0.0");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 128,
+ "1111:2222:3333:4444:5555:6666:7777:8888");
+ EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 0,
+ "::");
+}
+} // namespace talk_base
diff --git a/talk/base/json_unittest.cc b/talk/base/json_unittest.cc
new file mode 100644
index 0000000..a39b944
--- /dev/null
+++ b/talk/base/json_unittest.cc
@@ -0,0 +1,205 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+#include "talk/base/gunit.h"
+#include "talk/base/json.h"
+
+static Json::Value in_s("foo");
+static Json::Value in_sn("99");
+static Json::Value in_si("-99");
+static Json::Value in_sb("true");
+static Json::Value in_sd("1.2");
+static Json::Value in_n(12);
+static Json::Value in_i(-12);
+static Json::Value in_u(34U);
+static Json::Value in_b(true);
+static Json::Value in_d(1.2);
+static Json::Value big_sn("12345678901234567890");
+static Json::Value big_si("-12345678901234567890");
+static Json::Value big_u(0xFFFFFFFF);
+static Json::Value bad_a(Json::arrayValue);
+static Json::Value bad_o(Json::objectValue);
+
+TEST(JsonTest, GetString) {
+ std::string out;
+ EXPECT_TRUE(GetStringFromJson(in_s, &out));
+ EXPECT_EQ("foo", out);
+ EXPECT_TRUE(GetStringFromJson(in_sn, &out));
+ EXPECT_EQ("99", out);
+ EXPECT_TRUE(GetStringFromJson(in_si, &out));
+ EXPECT_EQ("-99", out);
+ EXPECT_TRUE(GetStringFromJson(in_i, &out));
+ EXPECT_EQ("-12", out);
+ EXPECT_TRUE(GetStringFromJson(in_n, &out));
+ EXPECT_EQ("12", out);
+ EXPECT_TRUE(GetStringFromJson(in_u, &out));
+ EXPECT_EQ("34", out);
+ EXPECT_TRUE(GetStringFromJson(in_b, &out));
+ EXPECT_EQ("true", out);
+ // Not supported here yet.
+ EXPECT_FALSE(GetStringFromJson(bad_a, &out));
+ EXPECT_FALSE(GetStringFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetInt) {
+ int out;
+ EXPECT_TRUE(GetIntFromJson(in_sn, &out));
+ EXPECT_EQ(99, out);
+ EXPECT_TRUE(GetIntFromJson(in_si, &out));
+ EXPECT_EQ(-99, out);
+ EXPECT_TRUE(GetIntFromJson(in_n, &out));
+ EXPECT_EQ(12, out);
+ EXPECT_TRUE(GetIntFromJson(in_i, &out));
+ EXPECT_EQ(-12, out);
+ EXPECT_TRUE(GetIntFromJson(in_u, &out));
+ EXPECT_EQ(34, out);
+ EXPECT_TRUE(GetIntFromJson(in_b, &out));
+ EXPECT_EQ(1, out);
+ EXPECT_FALSE(GetIntFromJson(in_s, &out));
+ EXPECT_FALSE(GetIntFromJson(big_sn, &out));
+ EXPECT_FALSE(GetIntFromJson(big_si, &out));
+ EXPECT_FALSE(GetIntFromJson(big_u, &out));
+ EXPECT_FALSE(GetIntFromJson(bad_a, &out));
+ EXPECT_FALSE(GetIntFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetUInt) {
+ unsigned int out;
+ EXPECT_TRUE(GetUIntFromJson(in_sn, &out));
+ EXPECT_EQ(99U, out);
+ EXPECT_TRUE(GetUIntFromJson(in_n, &out));
+ EXPECT_EQ(12U, out);
+ EXPECT_TRUE(GetUIntFromJson(in_u, &out));
+ EXPECT_EQ(34U, out);
+ EXPECT_TRUE(GetUIntFromJson(in_b, &out));
+ EXPECT_EQ(1U, out);
+ EXPECT_TRUE(GetUIntFromJson(big_u, &out));
+ EXPECT_EQ(0xFFFFFFFFU, out);
+ EXPECT_FALSE(GetUIntFromJson(in_s, &out));
+ // TODO: Fail reading negative strings.
+ // EXPECT_FALSE(GetUIntFromJson(in_si, &out));
+ EXPECT_FALSE(GetUIntFromJson(in_i, &out));
+ EXPECT_FALSE(GetUIntFromJson(big_sn, &out));
+ EXPECT_FALSE(GetUIntFromJson(big_si, &out));
+ EXPECT_FALSE(GetUIntFromJson(bad_a, &out));
+ EXPECT_FALSE(GetUIntFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetBool) {
+ bool out;
+ EXPECT_TRUE(GetBoolFromJson(in_sb, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_TRUE(GetBoolFromJson(in_n, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_TRUE(GetBoolFromJson(in_i, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_TRUE(GetBoolFromJson(in_u, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_TRUE(GetBoolFromJson(in_b, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_TRUE(GetBoolFromJson(big_u, &out));
+ EXPECT_EQ(true, out);
+ EXPECT_FALSE(GetBoolFromJson(in_s, &out));
+ EXPECT_FALSE(GetBoolFromJson(in_sn, &out));
+ EXPECT_FALSE(GetBoolFromJson(in_si, &out));
+ EXPECT_FALSE(GetBoolFromJson(big_sn, &out));
+ EXPECT_FALSE(GetBoolFromJson(big_si, &out));
+ EXPECT_FALSE(GetBoolFromJson(bad_a, &out));
+ EXPECT_FALSE(GetBoolFromJson(bad_o, &out));
+}
+
+TEST(JsonTest, GetDouble) {
+ double out;
+ EXPECT_TRUE(GetDoubleFromJson(in_sn, &out));
+ EXPECT_EQ(99, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_si, &out));
+ EXPECT_EQ(-99, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_sd, &out));
+ EXPECT_EQ(1.2, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_n, &out));
+ EXPECT_EQ(12, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_i, &out));
+ EXPECT_EQ(-12, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_u, &out));
+ EXPECT_EQ(34, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_b, &out));
+ EXPECT_EQ(1, out);
+ EXPECT_TRUE(GetDoubleFromJson(in_d, &out));
+ EXPECT_EQ(1.2, out);
+ EXPECT_FALSE(GetDoubleFromJson(in_s, &out));
+}
+
+TEST(JsonTest, GetFromArray) {
+ Json::Value a, out;
+ a.append(in_s);
+ a.append(in_i);
+ a.append(in_u);
+ a.append(in_b);
+ EXPECT_TRUE(GetValueFromJsonArray(a, 0, &out));
+ EXPECT_TRUE(GetValueFromJsonArray(a, 3, &out));
+ EXPECT_FALSE(GetValueFromJsonArray(a, 99, &out));
+ EXPECT_FALSE(GetValueFromJsonArray(a, 0xFFFFFFFF, &out));
+}
+
+TEST(JsonTest, GetFromObject) {
+ Json::Value o, out;
+ o["string"] = in_s;
+ o["int"] = in_i;
+ o["uint"] = in_u;
+ o["bool"] = in_b;
+ EXPECT_TRUE(GetValueFromJsonObject(o, "int", &out));
+ EXPECT_TRUE(GetValueFromJsonObject(o, "bool", &out));
+ EXPECT_FALSE(GetValueFromJsonObject(o, "foo", &out));
+ EXPECT_FALSE(GetValueFromJsonObject(o, "", &out));
+}
+
+TEST(JsonTest, VectorToArray) {
+ std::vector<std::string> in;
+ Json::Value out;
+ in.push_back("a");
+ in.push_back("b");
+ in.push_back("c");
+ out = StringVectorToJsonValue(in);
+ EXPECT_EQ(in.size(), out.size());
+ for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+ EXPECT_EQ(in[i], out[i].asString());
+ }
+}
+
+TEST(JsonTest, ArrayToVector) {
+ Json::Value in(Json::arrayValue);
+ std::vector<std::string> out;
+ in.append("a");
+ in.append("b");
+ in.append("c");
+ EXPECT_TRUE(JsonValueToStringVector(in, &out));
+ EXPECT_EQ(in.size(), out.size());
+ for (Json::Value::ArrayIndex i = 0; i < in.size(); ++i) {
+ EXPECT_EQ(in[i].asString(), out[i]);
+ }
+}
diff --git a/talk/base/latebindingsymboltable.cc b/talk/base/latebindingsymboltable.cc
index f9d59ab..348265e 100644
--- a/talk/base/latebindingsymboltable.cc
+++ b/talk/base/latebindingsymboltable.cc
@@ -37,7 +37,7 @@
inline static const char *GetDllError() {
#ifdef LINUX
- char *err = dlerror();
+ const char *err = dlerror();
if (err) {
return err;
} else {
@@ -75,7 +75,7 @@
void **symbol) {
#ifdef LINUX
*symbol = dlsym(handle, symbol_name);
- char *err = dlerror();
+ const char *err = dlerror();
if (err) {
LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err;
return false;
diff --git a/talk/base/latebindingsymboltable_unittest.cc b/talk/base/latebindingsymboltable_unittest.cc
new file mode 100644
index 0000000..a16423e
--- /dev/null
+++ b/talk/base/latebindingsymboltable_unittest.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef LINUX
+#include <math.h>
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+#ifdef LINUX
+
+// Some libm symbols for our test.
+#define LIBM_SYMBOLS_LIST \
+ X(acos) \
+ X(sin) \
+ X(tan)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibmTestSymbolTable)
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibmTestSymbolTable, sym)
+LIBM_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibmTestSymbolTable)
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibmTestSymbolTable, "libm.so.6")
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibmTestSymbolTable, sym)
+LIBM_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibmTestSymbolTable)
+
+#define LATE(sym) \
+ LATESYM_GET(LibmTestSymbolTable, &table, sym)
+
+TEST(LateBindingSymbolTable, libm) {
+ LibmTestSymbolTable table;
+ EXPECT_FALSE(table.IsLoaded());
+ ASSERT_TRUE(table.Load());
+ EXPECT_TRUE(table.IsLoaded());
+ EXPECT_EQ(LATE(acos)(0.5), acos(0.5));
+ EXPECT_EQ(LATE(sin)(0.5), sin(0.5));
+ EXPECT_EQ(LATE(tan)(0.5), tan(0.5));
+ // It would be nice to check that the addresses are the same, but the nature
+ // of dynamic linking and relocation makes them actually be different.
+ table.Unload();
+ EXPECT_FALSE(table.IsLoaded());
+}
+
+#else
+#error Not implemented
+#endif
+
+} // namespace talk_base
diff --git a/talk/base/libdbusglibsymboltable.cc b/talk/base/libdbusglibsymboltable.cc
new file mode 100644
index 0000000..3548a7c
--- /dev/null
+++ b/talk/base/libdbusglibsymboltable.cc
@@ -0,0 +1,41 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/libdbusglibsymboltable.h"
+
+namespace talk_base {
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibDBusGlibSymbolTable,
+ "libdbus-glib-1.so")
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibDBusGlibSymbolTable, sym)
+LIBDBUS_GLIB_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibDBusGlibSymbolTable)
+
+} // namespace talk_base
+
diff --git a/talk/base/libdbusglibsymboltable.h b/talk/base/libdbusglibsymboltable.h
new file mode 100644
index 0000000..9aad8a5
--- /dev/null
+++ b/talk/base/libdbusglibsymboltable.h
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
+#define TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace talk_base {
+
+// The libdbus-glib symbols we need, as an X-Macro list.
+// This list must contain precisely every libdbus-glib function that is used in
+// dbus.cc.
+#define LIBDBUS_GLIB_SYMBOLS_LIST \
+ X(dbus_bus_add_match) \
+ X(dbus_connection_add_filter) \
+ X(dbus_connection_close) \
+ X(dbus_connection_remove_filter) \
+ X(dbus_connection_set_exit_on_disconnect) \
+ X(dbus_g_bus_get) \
+ X(dbus_g_bus_get_private) \
+ X(dbus_g_connection_get_connection) \
+ X(dbus_g_connection_unref) \
+ X(dbus_g_thread_init) \
+ X(dbus_message_get_interface) \
+ X(dbus_message_get_member) \
+ X(dbus_message_get_path) \
+ X(dbus_message_get_type) \
+ X(dbus_message_iter_get_arg_type) \
+ X(dbus_message_iter_get_basic) \
+ X(dbus_message_iter_init) \
+ X(dbus_message_ref) \
+ X(dbus_message_unref) \
+ X(g_free) \
+ X(g_idle_source_new) \
+ X(g_main_context_new) \
+ X(g_main_context_push_thread_default) \
+ X(g_main_context_unref) \
+ X(g_main_loop_new) \
+ X(g_main_loop_quit) \
+ X(g_main_loop_run) \
+ X(g_main_loop_unref) \
+ X(g_object_unref) \
+ X(g_source_attach) \
+ X(g_source_destroy) \
+ X(g_source_set_callback) \
+ X(g_source_unref) \
+ X(g_thread_init) \
+ X(g_type_init)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibDBusGlibSymbolTable)
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibDBusGlibSymbolTable, sym)
+LIBDBUS_GLIB_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibDBusGlibSymbolTable)
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LIBDBUSGLIBSYMBOLTABLE_H_
+
diff --git a/talk/base/linux.cc b/talk/base/linux.cc
index 9303f4f..a4434f5 100644
--- a/talk/base/linux.cc
+++ b/talk/base/linux.cc
@@ -135,8 +135,23 @@
bool ProcCpuInfo::GetCpuFamily(int* id) {
int cpu_family = 0;
+#if defined(__arm__)
+ // On some ARM platforms, there is no 'cpu family' in '/proc/cpuinfo'. But
+ // there is 'CPU Architecture' which can be used as 'cpu family'.
+ // See http://en.wikipedia.org/wiki/ARM_architecture for a good list of
+ // ARM cpu families, architectures, and their mappings.
+ // There may be multiple sessions that aren't per-processor. We need to scan
+ // through each session until we find the first 'CPU architecture'.
+ size_t section_count = sections_.size();
+ for (size_t i = 0; i < section_count; ++i) {
+ if (GetSectionIntValue(i, "CPU architecture", &cpu_family)) {
+ // We returns the first one (if there are multiple entries).
+ break;
+ };
+ }
+#else
GetSectionIntValue(0, "cpu family", &cpu_family);
-
+#endif
if (id) {
*id = cpu_family;
}
diff --git a/talk/base/linux_unittest.cc b/talk/base/linux_unittest.cc
new file mode 100644
index 0000000..6e1a8a9
--- /dev/null
+++ b/talk/base/linux_unittest.cc
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include "talk/base/linux.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "testing/base/gunit.h"
+
+namespace talk_base {
+
+// These tests running on ARM are fairly specific to the output of the tegra2
+// ARM processor, and so may fail on other ARM-based systems.
+TEST(ProcCpuInfo, GetProcInfo) {
+ ProcCpuInfo proc_info;
+ EXPECT_TRUE(proc_info.LoadFromSystem());
+
+ int out_cpus = 0;
+ EXPECT_TRUE(proc_info.GetNumCpus(&out_cpus));
+ LOG(LS_INFO) << "GetNumCpus: " << out_cpus;
+ EXPECT_GT(out_cpus, 0);
+
+ int out_cpus_phys = 0;
+ EXPECT_TRUE(proc_info.GetNumPhysicalCpus(&out_cpus_phys));
+ LOG(LS_INFO) << "GetNumPhysicalCpus: " << out_cpus_phys;
+ EXPECT_GT(out_cpus_phys, 0);
+ EXPECT_LE(out_cpus_phys, out_cpus);
+
+ int out_family = 0;
+ EXPECT_TRUE(proc_info.GetCpuFamily(&out_family));
+ LOG(LS_INFO) << "cpu family: " << out_family;
+ EXPECT_GE(out_family, 4);
+
+#if defined(__arm__)
+ std::string out_processor;
+ EXPECT_TRUE(proc_info.GetSectionStringValue(0, "Processor", &out_processor));
+ LOG(LS_INFO) << "Processor: " << out_processor;
+ EXPECT_NE(std::string::npos, out_processor.find("ARM"));
+
+ // Most other info, such as model, stepping, vendor, etc.
+ // is missing on ARM systems.
+#else
+ int out_model = 0;
+ EXPECT_TRUE(proc_info.GetSectionIntValue(0, "model", &out_model));
+ LOG(LS_INFO) << "model: " << out_model;
+
+ int out_stepping = 0;
+ EXPECT_TRUE(proc_info.GetSectionIntValue(0, "stepping", &out_stepping));
+ LOG(LS_INFO) << "stepping: " << out_stepping;
+
+ int out_processor = 0;
+ EXPECT_TRUE(proc_info.GetSectionIntValue(0, "processor", &out_processor));
+ LOG(LS_INFO) << "processor: " << out_processor;
+ EXPECT_EQ(0, out_processor);
+
+ std::string out_str;
+ EXPECT_TRUE(proc_info.GetSectionStringValue(0, "vendor_id", &out_str));
+ LOG(LS_INFO) << "vendor_id: " << out_str;
+ EXPECT_FALSE(out_str.empty());
+#endif
+}
+
+TEST(ConfigParser, ParseConfig) {
+ ConfigParser parser;
+ MemoryStream *test_stream = new MemoryStream(
+ "Key1: Value1\n"
+ "Key2\t: Value2\n"
+ "Key3:Value3\n"
+ "\n"
+ "Key1:Value1\n");
+ ConfigParser::MapVector key_val_pairs;
+ parser.Attach(test_stream);
+ EXPECT_EQ(true, parser.Parse(&key_val_pairs));
+ EXPECT_EQ(2U, key_val_pairs.size());
+ EXPECT_EQ("Value1", key_val_pairs[0]["Key1"]);
+ EXPECT_EQ("Value2", key_val_pairs[0]["Key2"]);
+ EXPECT_EQ("Value3", key_val_pairs[0]["Key3"]);
+ EXPECT_EQ("Value1", key_val_pairs[1]["Key1"]);
+ key_val_pairs.clear();
+ EXPECT_EQ(true, parser.Open("/proc/cpuinfo"));
+ EXPECT_EQ(true, parser.Parse(&key_val_pairs));
+}
+
+TEST(ReadLinuxLsbRelease, ReturnsSomething) {
+ std::string str = ReadLinuxLsbRelease();
+ // ChromeOS don't have lsb_release
+ // EXPECT_FALSE(str.empty());
+}
+
+TEST(ReadLinuxUname, ReturnsSomething) {
+ std::string str = ReadLinuxUname();
+ EXPECT_FALSE(str.empty());
+}
+
+} // namespace talk_base
diff --git a/talk/base/linuxfdwalk.c b/talk/base/linuxfdwalk.c
new file mode 100644
index 0000000..4179f41
--- /dev/null
+++ b/talk/base/linuxfdwalk.c
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "talk/base/linuxfdwalk.h"
+
+// Parses a file descriptor number in base 10, requiring the strict format used
+// in /proc/*/fd. Returns the value, or -1 if not a valid string.
+static int parse_fd(const char *s) {
+ if (!*s) {
+ // Empty string is invalid.
+ return -1;
+ }
+ int val = 0;
+ do {
+ if (*s < '0' || *s > '9') {
+ // Non-numeric characters anywhere are invalid.
+ return -1;
+ }
+ int digit = *s++ - '0';
+ val = val * 10 + digit;
+ } while (*s);
+ return val;
+}
+
+int fdwalk(void (*func)(void *, int), void *opaque) {
+ DIR *dir = opendir("/proc/self/fd");
+ if (!dir) {
+ return -1;
+ }
+ int opendirfd = dirfd(dir);
+ int parse_errors = 0;
+ struct dirent *ent;
+ // Have to clear errno to distinguish readdir() completion from failure.
+ while (errno = 0, (ent = readdir(dir)) != NULL) {
+ if (strcmp(ent->d_name, ".") == 0 ||
+ strcmp(ent->d_name, "..") == 0) {
+ continue;
+ }
+ // We avoid atoi or strtol because those are part of libc and they involve
+ // locale stuff, which is probably not safe from a post-fork context in a
+ // multi-threaded app.
+ int fd = parse_fd(ent->d_name);
+ if (fd < 0) {
+ parse_errors = 1;
+ continue;
+ }
+ if (fd != opendirfd) {
+ (*func)(opaque, fd);
+ }
+ }
+ int saved_errno = errno;
+ if (closedir(dir) < 0) {
+ if (!saved_errno) {
+ // Return the closedir error.
+ return -1;
+ }
+ // Else ignore it because we have a more relevant error to return.
+ }
+ if (saved_errno) {
+ errno = saved_errno;
+ return -1;
+ } else if (parse_errors) {
+ errno = EBADF;
+ return -1;
+ } else {
+ return 0;
+ }
+}
diff --git a/talk/base/linuxfdwalk.h b/talk/base/linuxfdwalk.h
new file mode 100644
index 0000000..ea039bf
--- /dev/null
+++ b/talk/base/linuxfdwalk.h
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LINUXFDWALK_H_
+#define TALK_BASE_LINUXFDWALK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Linux port of SunOS's fdwalk(3) call. It loops over all open file descriptors
+// and calls func on each one. Additionally, it is safe to use from the child
+// of a fork that hasn't exec'ed yet, so you can use it to close all open file
+// descriptors prior to exec'ing a daemon.
+// The return value is 0 if successful, or else -1 and errno is set. The
+// possible errors include any error that can be returned by opendir(),
+// readdir(), or closedir(), plus EBADF if there are problems parsing the
+// contents of /proc/self/fd.
+// The file descriptors that are enumerated will not include the file descriptor
+// used for the enumeration itself.
+int fdwalk(void (*func)(void *, int), void *opaque);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // TALK_BASE_LINUXFDWALK_H_
diff --git a/talk/base/linuxfdwalk_unittest.cc b/talk/base/linuxfdwalk_unittest.cc
new file mode 100644
index 0000000..ff14b66
--- /dev/null
+++ b/talk/base/linuxfdwalk_unittest.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <set>
+#include <sstream>
+
+#include "talk/base/gunit.h"
+#include "talk/base/linuxfdwalk.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+static const int kArbitraryLargeFdNumber = 424;
+
+static void FdCheckVisitor(void *data, int fd) {
+ std::set<int> *fds = static_cast<std::set<int> *>(data);
+ EXPECT_EQ(1U, fds->erase(fd));
+}
+
+static void FdEnumVisitor(void *data, int fd) {
+ std::set<int> *fds = static_cast<std::set<int> *>(data);
+ EXPECT_TRUE(fds->insert(fd).second);
+}
+
+// Checks that the set of open fds is exactly the given list.
+static void CheckOpenFdList(std::set<int> fds) {
+ EXPECT_EQ(0, fdwalk(&FdCheckVisitor, &fds));
+ EXPECT_EQ(0U, fds.size());
+}
+
+static void GetOpenFdList(std::set<int> *fds) {
+ fds->clear();
+ EXPECT_EQ(0, fdwalk(&FdEnumVisitor, fds));
+}
+
+TEST(LinuxFdWalk, TestFdWalk) {
+ std::set<int> fds;
+ GetOpenFdList(&fds);
+ std::ostringstream str;
+ // I have observed that the open set when starting a test is [0, 6]. Leaked
+ // fds would change that, but so can (e.g.) running under a debugger, so we
+ // can't really do an EXPECT. :(
+ str << "File descriptors open in test executable:";
+ for (std::set<int>::const_iterator i = fds.begin(); i != fds.end(); ++i) {
+ str << " " << *i;
+ }
+ LOG(LS_INFO) << str.str();
+ // Open some files.
+ int fd1 = open("/dev/null", O_RDONLY);
+ EXPECT_LE(0, fd1);
+ int fd2 = open("/dev/null", O_WRONLY);
+ EXPECT_LE(0, fd2);
+ int fd3 = open("/dev/null", O_RDWR);
+ EXPECT_LE(0, fd3);
+ int fd4 = dup2(fd3, kArbitraryLargeFdNumber);
+ EXPECT_LE(0, fd4);
+ EXPECT_TRUE(fds.insert(fd1).second);
+ EXPECT_TRUE(fds.insert(fd2).second);
+ EXPECT_TRUE(fds.insert(fd3).second);
+ EXPECT_TRUE(fds.insert(fd4).second);
+ CheckOpenFdList(fds);
+ EXPECT_EQ(0, close(fd1));
+ EXPECT_EQ(0, close(fd2));
+ EXPECT_EQ(0, close(fd3));
+ EXPECT_EQ(0, close(fd4));
+}
diff --git a/talk/base/linuxwindowpicker.cc b/talk/base/linuxwindowpicker.cc
new file mode 100644
index 0000000..250c1db
--- /dev/null
+++ b/talk/base/linuxwindowpicker.cc
@@ -0,0 +1,735 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/linuxwindowpicker.h"
+
+#include <math.h>
+#include <string.h>
+
+#include <string>
+
+#include <X11/Xatom.h>
+#include <X11/extensions/Xcomposite.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/Xutil.h>
+
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+// Stupid X11. It seems none of the synchronous returns codes from X11 calls
+// are meaningful unless an asynchronous error handler is configured. This
+// RAII class registers and unregisters an X11 error handler.
+class XErrorSuppressor {
+ public:
+ explicit XErrorSuppressor(Display* display)
+ : display_(display), original_error_handler_(NULL) {
+ SuppressX11Errors();
+ }
+ ~XErrorSuppressor() {
+ UnsuppressX11Errors();
+ }
+
+ private:
+ static int ErrorHandler(Display* display, XErrorEvent* e) {
+ char buf[256];
+ XGetErrorText(display, e->error_code, buf, sizeof buf);
+ LOG(LS_WARNING) << "Received X11 error \"" << buf << "\" for request code "
+ << static_cast<unsigned int>(e->request_code);
+ return 0;
+ }
+
+ void SuppressX11Errors() {
+ XFlush(display_);
+ XSync(display_, False);
+ original_error_handler_ = XSetErrorHandler(&ErrorHandler);
+ }
+
+ void UnsuppressX11Errors() {
+ XFlush(display_);
+ XSync(display_, False);
+ XErrorHandler handler = XSetErrorHandler(original_error_handler_);
+ if (handler != &ErrorHandler) {
+ LOG(LS_WARNING) << "Unbalanced XSetErrorHandler() calls detected. "
+ << "Final error handler may not be what you expect!";
+ }
+ original_error_handler_ = NULL;
+ }
+
+ Display* display_;
+ XErrorHandler original_error_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(XErrorSuppressor);
+};
+
+// Hiding all X11 specifics inside its own class. This to avoid
+// conflicts between talk and X11 header declarations.
+class XWindowEnumerator {
+ public:
+ XWindowEnumerator()
+ : display_(NULL),
+ has_composite_extension_(false),
+ has_render_extension_(false) {
+ }
+
+ ~XWindowEnumerator() {
+ if (display_ != NULL) {
+ XCloseDisplay(display_);
+ }
+ }
+
+ bool Init() {
+ if (display_ != NULL) {
+ // Already initialized.
+ return true;
+ }
+ display_ = XOpenDisplay(NULL);
+ if (display_ == NULL) {
+ LOG(LS_ERROR) << "Failed to open display.";
+ return false;
+ }
+
+ XErrorSuppressor error_suppressor(display_);
+
+ wm_state_ = XInternAtom(display_, "WM_STATE", True);
+ net_wm_icon_ = XInternAtom(display_, "_NET_WM_ICON", False);
+
+ int event_base, error_base, major_version, minor_version;
+ if (XCompositeQueryExtension(display_, &event_base, &error_base) &&
+ XCompositeQueryVersion(display_, &major_version, &minor_version) &&
+ // XCompositeNameWindowPixmap() requires version 0.2
+ (major_version > 0 || minor_version >= 2)) {
+ has_composite_extension_ = true;
+ } else {
+ LOG(LS_INFO) << "Xcomposite extension not available or too old.";
+ }
+
+ if (XRenderQueryExtension(display_, &event_base, &error_base) &&
+ XRenderQueryVersion(display_, &major_version, &minor_version) &&
+ // XRenderSetPictureTransform() requires version 0.6
+ (major_version > 0 || minor_version >= 6)) {
+ has_render_extension_ = true;
+ } else {
+ LOG(LS_INFO) << "Xrender extension not available or too old.";
+ }
+ return true;
+ }
+
+ bool EnumerateWindows(WindowDescriptionList* descriptions) {
+ if (!Init()) {
+ return false;
+ }
+ XErrorSuppressor error_suppressor(display_);
+ int num_screens = XScreenCount(display_);
+ bool result = false;
+ for (int i = 0; i < num_screens; ++i) {
+ if (EnumerateScreenWindows(descriptions, i)) {
+ // We know we succeded on at least one screen.
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ bool EnumerateDesktops(DesktopDescriptionList* descriptions) {
+ if (!Init()) {
+ return false;
+ }
+ XErrorSuppressor error_suppressor(display_);
+ int num_screens = XScreenCount(display_);
+ for (int i = 0; i < num_screens; ++i) {
+ Window root_window = XRootWindow(display_, i);
+ DesktopId id(DesktopId(root_window, i));
+ // TODO: Figure out an appropriate desktop title.
+ DesktopDescription desc(id, "");
+ descriptions->push_back(desc);
+ }
+ return num_screens > 0;
+ }
+
+ bool IsVisible(const WindowId& id) {
+ if (!Init()) {
+ return false;
+ }
+ XErrorSuppressor error_suppressor(display_);
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(display_, id.id(), &attr)) {
+ LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+ return false;
+ }
+ return attr.map_state == IsViewable;
+ }
+
+ bool MoveToFront(const WindowId& id) {
+ if (!Init()) {
+ return false;
+ }
+ XErrorSuppressor error_suppressor(display_);
+ unsigned int num_children;
+ Window* children;
+ Window parent;
+ Window root;
+
+ // Find root window to pass event to.
+ int status = XQueryTree(display_, id.id(), &root, &parent, &children,
+ &num_children);
+ if (status == 0) {
+ LOG(LS_WARNING) << "Failed to query for child windows.";
+ return false;
+ }
+ if (children != NULL) {
+ XFree(children);
+ }
+
+ // Move the window to front.
+ XRaiseWindow(display_, id.id());
+
+ // Some window managers (e.g., metacity in GNOME) consider it illegal to
+ // raise a window without also giving it input focus with
+ // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough.
+ Atom atom = XInternAtom(display_, "_NET_ACTIVE_WINDOW", True);
+ if (atom != None) {
+ XEvent xev;
+ long event_mask;
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.window = id.id();
+ xev.xclient.message_type = atom;
+
+ // The format member is set to 8, 16, or 32 and specifies whether the
+ // data should be viewed as a list of bytes, shorts, or longs.
+ xev.xclient.format = 32;
+
+ xev.xclient.data.l[0] = 0;
+ xev.xclient.data.l[1] = 0;
+ xev.xclient.data.l[2] = 0;
+ xev.xclient.data.l[3] = 0;
+ xev.xclient.data.l[4] = 0;
+
+ event_mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+ XSendEvent(display_, root, False, event_mask, &xev);
+ }
+ XFlush(display_);
+ return true;
+ }
+
+ uint8* GetWindowIcon(const WindowId& id, int* width, int* height) {
+ if (!Init()) {
+ return NULL;
+ }
+ XErrorSuppressor error_suppressor(display_);
+ Atom ret_type;
+ int format;
+ unsigned long length, bytes_after, size;
+ unsigned char* data = NULL;
+
+ // Find out the size of the icon data.
+ if (XGetWindowProperty(
+ display_, id.id(), net_wm_icon_, 0, 0, False, XA_CARDINAL,
+ &ret_type, &format, &length, &size, &data) == Success &&
+ data) {
+ XFree(data);
+ } else {
+ LOG(LS_ERROR) << "Failed to get size of the icon.";
+ return NULL;
+ }
+ // Get the icon data, the format is one uint32 each for width and height,
+ // followed by the actual pixel data.
+ if (size >= 2 &&
+ XGetWindowProperty(
+ display_, id.id(), net_wm_icon_, 0, size, False, XA_CARDINAL,
+ &ret_type, &format, &length, &bytes_after, &data) == Success &&
+ data) {
+ uint32* data_ptr = reinterpret_cast<uint32*>(data);
+ int w, h;
+ w = data_ptr[0];
+ h = data_ptr[1];
+ if (size < static_cast<unsigned long>(w * h + 2)) {
+ XFree(data);
+ LOG(LS_ERROR) << "Not a vaild icon.";
+ return NULL;
+ }
+ uint8* rgba =
+ ArgbToRgba(&data_ptr[2], 0, 0, w, h, w, h, true);
+ XFree(data);
+ *width = w;
+ *height = h;
+ return rgba;
+ } else {
+ LOG(LS_ERROR) << "Failed to get window icon data.";
+ return NULL;
+ }
+ }
+
+ uint8* GetWindowThumbnail(const WindowId& id, int width, int height) {
+ if (!Init()) {
+ return NULL;
+ }
+
+ if (!has_composite_extension_) {
+ // Without the Xcomposite extension we would only get a good thumbnail if
+ // the whole window is visible on screen and not covered by any
+ // other window. This is not something we want so instead, just
+ // bail out.
+ LOG(LS_INFO) << "No Xcomposite extension detected.";
+ return NULL;
+ }
+ XErrorSuppressor error_suppressor(display_);
+
+ Window root;
+ int x;
+ int y;
+ unsigned int src_width;
+ unsigned int src_height;
+ unsigned int border_width;
+ unsigned int depth;
+
+ // In addition to needing X11 server-side support for Xcomposite, it
+ // actually needs to be turned on for this window in order to get a good
+ // thumbnail. If the user has modern hardware/drivers but isn't using a
+ // compositing window manager, that won't be the case. We could
+ // automatically turn it on for all windows here so that we can get
+ // thumbnails, but the transition is visually ugly, so instead we just want
+ // to detect this case and bail out. To do so, we attempt to retrieve the
+ // backing pixmap for the window, which will only exist if the window has
+ // been redirected to offscreen drawing by a compositing window manager. If
+ // it doesn't exist, the calls will raise errors, so we must suppress errors
+ // to prevent libX11 from aborting the program.
+ Pixmap src_pixmap = XCompositeNameWindowPixmap(display_, id.id());
+ if (!src_pixmap) {
+ // Even if the backing pixmap doesn't exist, this still should have
+ // succeeded and returned a valid handle (it just wouldn't be a handle to
+ // anything). So this is a real error path.
+ LOG(LS_ERROR) << "XCompositeNameWindowPixmap() failed";
+ return NULL;
+ }
+ if (!XGetGeometry(display_, src_pixmap, &root, &x, &y,
+ &src_width, &src_height, &border_width,
+ &depth)) {
+ // If the window does not actually have a backing pixmap, this is the path
+ // that will "fail", so it's a warning rather than an error.
+ LOG(LS_WARNING) << "XGetGeometry() failed (probably composite is not in "
+ << "use)";
+ XFreePixmap(display_, src_pixmap);
+ return NULL;
+ }
+
+ // If we get to here, then composite is in use for this window and it has a
+ // valid backing pixmap.
+
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(display_, id.id(), &attr)) {
+ LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+ XFreePixmap(display_, src_pixmap);
+ return NULL;
+ }
+
+ uint8* data = GetDrawableThumbnail(src_pixmap,
+ attr.visual,
+ src_width,
+ src_height,
+ width,
+ height);
+ XFreePixmap(display_, src_pixmap);
+ return data;
+ }
+
+ int GetNumDesktops() {
+ if (!Init()) {
+ return -1;
+ }
+
+ return XScreenCount(display_);
+ }
+
+ uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height) {
+ if (!Init()) {
+ return NULL;
+ }
+ XErrorSuppressor error_suppressor(display_);
+
+ Window root_window = id.id();
+ XWindowAttributes attr;
+ if (!XGetWindowAttributes(display_, root_window, &attr)) {
+ LOG(LS_ERROR) << "XGetWindowAttributes() failed";
+ return NULL;
+ }
+
+ return GetDrawableThumbnail(root_window,
+ attr.visual,
+ attr.width,
+ attr.height,
+ width,
+ height);
+ }
+
+ private:
+ uint8* GetDrawableThumbnail(Drawable src_drawable,
+ Visual* visual,
+ int src_width,
+ int src_height,
+ int dst_width,
+ int dst_height) {
+ if (!has_render_extension_) {
+ // Without the Xrender extension we would have to read the full window and
+ // scale it down in our process. Xrender is over a decade old so we aren't
+ // going to expend effort to support that situation. We still need to
+ // check though because probably some virtual VNC displays are in this
+ // category.
+ LOG(LS_INFO) << "No Xrender extension detected.";
+ return NULL;
+ }
+
+ XRenderPictFormat* format = XRenderFindVisualFormat(display_,
+ visual);
+ if (!format) {
+ LOG(LS_ERROR) << "XRenderFindVisualFormat() failed";
+ return NULL;
+ }
+
+ // Create a picture to reference the window pixmap.
+ XRenderPictureAttributes pa;
+ pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets
+ Picture src = XRenderCreatePicture(display_,
+ src_drawable,
+ format,
+ CPSubwindowMode,
+ &pa);
+ if (!src) {
+ LOG(LS_ERROR) << "XRenderCreatePicture() failed";
+ return NULL;
+ }
+
+ // Create a picture to reference the destination pixmap.
+ Pixmap dst_pixmap = XCreatePixmap(display_,
+ src_drawable,
+ dst_width,
+ dst_height,
+ format->depth);
+ if (!dst_pixmap) {
+ LOG(LS_ERROR) << "XCreatePixmap() failed";
+ XRenderFreePicture(display_, src);
+ return NULL;
+ }
+
+ Picture dst = XRenderCreatePicture(display_, dst_pixmap, format, 0, NULL);
+ if (!dst) {
+ LOG(LS_ERROR) << "XRenderCreatePicture() failed";
+ XFreePixmap(display_, dst_pixmap);
+ XRenderFreePicture(display_, src);
+ return NULL;
+ }
+
+ // Clear the background.
+ XRenderColor transparent = {0};
+ XRenderFillRectangle(display_,
+ PictOpSrc,
+ dst,
+ &transparent,
+ 0,
+ 0,
+ dst_width,
+ dst_height);
+
+ // Calculate how much we need to scale the image.
+ double scale_x = static_cast<double>(dst_width) /
+ static_cast<double>(src_width);
+ double scale_y = static_cast<double>(dst_height) /
+ static_cast<double>(src_height);
+ double scale = talk_base::_min(scale_y, scale_x);
+
+ int scaled_width = round(src_width * scale);
+ int scaled_height = round(src_height * scale);
+
+ // Render the thumbnail centered on both axis.
+ int centered_x = (dst_width - scaled_width) / 2;
+ int centered_y = (dst_height - scaled_height) / 2;
+
+ // Scaling matrix
+ XTransform xform = { {
+ { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) },
+ { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) },
+ { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) }
+ } };
+ XRenderSetPictureTransform(display_, src, &xform);
+
+ // Apply filter to smooth out the image.
+ XRenderSetPictureFilter(display_, src, FilterBest, NULL, 0);
+
+ // Render the image to the destination picture.
+ XRenderComposite(display_,
+ PictOpSrc,
+ src,
+ None,
+ dst,
+ 0,
+ 0,
+ 0,
+ 0,
+ centered_x,
+ centered_y,
+ scaled_width,
+ scaled_height);
+
+ // Get the pixel data from the X server. TODO: XGetImage
+ // might be slow here, compare with ShmGetImage.
+ XImage* image = XGetImage(display_,
+ dst_pixmap,
+ 0,
+ 0,
+ dst_width,
+ dst_height,
+ AllPlanes, ZPixmap);
+ uint8* data = ArgbToRgba(reinterpret_cast<uint32*>(image->data),
+ centered_x,
+ centered_y,
+ scaled_width,
+ scaled_height,
+ dst_width,
+ dst_height,
+ false);
+ XDestroyImage(image);
+ XRenderFreePicture(display_, dst);
+ XFreePixmap(display_, dst_pixmap);
+ XRenderFreePicture(display_, src);
+ return data;
+ }
+
+ uint8* ArgbToRgba(uint32* argb_data, int x, int y, int w, int h,
+ int stride_x, int stride_y, bool has_alpha) {
+ uint8* p;
+ int len = stride_x * stride_y * 4;
+ uint8* data = new uint8[len];
+ memset(data, 0, len);
+ p = data + 4 * (y * stride_x + x);
+ for (int i = 0; i < h; ++i) {
+ for (int j = 0; j < w; ++j) {
+ uint32 argb;
+ uint32 rgba;
+ argb = argb_data[stride_x * (y + i) + x + j];
+ rgba = (argb << 8) | (argb >> 24);
+ *p = rgba >> 24;
+ ++p;
+ *p = (rgba >> 16) & 0xff;
+ ++p;
+ *p = (rgba >> 8) & 0xff;
+ ++p;
+ *p = has_alpha ? rgba & 0xFF : 0xFF;
+ ++p;
+ }
+ p += (stride_x - w) * 4;
+ }
+ return data;
+ }
+
+ bool EnumerateScreenWindows(WindowDescriptionList* descriptions, int screen) {
+ Window parent;
+ Window *children;
+ int status;
+ unsigned int num_children;
+ Window root_window = XRootWindow(display_, screen);
+ status = XQueryTree(display_, root_window, &root_window, &parent, &children,
+ &num_children);
+ if (status == 0) {
+ LOG(LS_ERROR) << "Failed to query for child windows.";
+ return false;
+ }
+ for (unsigned int i = 0; i < num_children; ++i) {
+ // Iterate in reverse order to display windows from front to back.
+ Window app_window = GetApplicationWindow(children[num_children - 1 - i]);
+ if (app_window &&
+ !LinuxWindowPicker::IsDesktopElement(display_, app_window)) {
+ std::string title;
+ if (GetWindowTitle(app_window, &title)) {
+ WindowId id(app_window);
+ WindowDescription desc(id, title);
+ descriptions->push_back(desc);
+ }
+ }
+ }
+ if (children != NULL) {
+ XFree(children);
+ }
+ return true;
+ }
+
+ bool GetWindowTitle(Window window, std::string* title) {
+ int status;
+ bool result = false;
+ XTextProperty window_name;
+ window_name.value = NULL;
+ if (window) {
+ status = XGetWMName(display_, window, &window_name);
+ if (status && window_name.value && window_name.nitems) {
+ int cnt;
+ char **list = NULL;
+ status = Xutf8TextPropertyToTextList(display_, &window_name, &list,
+ &cnt);
+ if (status >= Success && cnt && *list) {
+ if (cnt > 1) {
+ LOG(LS_INFO) << "Window has " << cnt
+ << " text properties, only using the first one.";
+ }
+ *title = *list;
+ result = true;
+ }
+ if (list != NULL) {
+ XFreeStringList(list);
+ }
+ }
+ if (window_name.value != NULL) {
+ XFree(window_name.value);
+ }
+ }
+ return result;
+ }
+
+ Window GetApplicationWindow(Window window) {
+ Window root, parent;
+ Window app_window = 0;
+ Window *children;
+ unsigned int num_children;
+ Atom type = None;
+ int format;
+ unsigned long nitems, after;
+ unsigned char *data;
+
+ int ret = XGetWindowProperty(display_, window,
+ wm_state_, 0L, 2,
+ False, wm_state_, &type, &format,
+ &nitems, &after, &data);
+ if (ret != Success) {
+ LOG(LS_ERROR) << "XGetWindowProperty failed with return code " << ret
+ << " for window " << window << ".";
+ return 0;
+ }
+ if (type != None) {
+ int64 state = static_cast<int64>(*data);
+ XFree(data);
+ return state == NormalState ? window : 0;
+ }
+ XFree(data);
+ if (!XQueryTree(display_, window, &root, &parent, &children,
+ &num_children)) {
+ LOG(LS_ERROR) << "Failed to query for child windows although window"
+ << "does not have a valid WM_STATE.";
+ return 0;
+ }
+ for (unsigned int i = 0; i < num_children; ++i) {
+ app_window = GetApplicationWindow(children[i]);
+ if (app_window) {
+ break;
+ }
+ }
+ if (children != NULL) {
+ XFree(children);
+ }
+ return app_window;
+ }
+
+ Atom wm_state_;
+ Atom net_wm_icon_;
+ Display* display_;
+ bool has_composite_extension_;
+ bool has_render_extension_;
+};
+
+LinuxWindowPicker::LinuxWindowPicker() : enumerator_(new XWindowEnumerator()) {
+}
+
+LinuxWindowPicker::~LinuxWindowPicker() {
+}
+
+bool LinuxWindowPicker::IsDesktopElement(_XDisplay* display, Window window) {
+ if (window == 0) {
+ LOG(LS_WARNING) << "Zero is never a valid window.";
+ return false;
+ }
+ XClassHint class_hint;
+ Status s = XGetClassHint(display, window, &class_hint);
+ bool result = false;
+ if (s == 0) {
+ // No hints, assume this is a normal application window.
+ return result;
+ }
+ static const std::string gnome_panel("gnome-panel");
+ static const std::string desktop_window("desktop_window");
+
+ if (gnome_panel.compare(class_hint.res_name) == 0 ||
+ desktop_window.compare(class_hint.res_name) == 0) {
+ result = true;
+ }
+ XFree(class_hint.res_name);
+ XFree(class_hint.res_class);
+ return result;
+}
+
+bool LinuxWindowPicker::Init() {
+ return enumerator_->Init();
+}
+
+bool LinuxWindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+ return enumerator_->EnumerateWindows(descriptions);
+}
+
+bool LinuxWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+ return enumerator_->EnumerateDesktops(descriptions);
+}
+
+bool LinuxWindowPicker::IsVisible(const WindowId& id) {
+ return enumerator_->IsVisible(id);
+}
+
+bool LinuxWindowPicker::MoveToFront(const WindowId& id) {
+ return enumerator_->MoveToFront(id);
+}
+
+
+uint8* LinuxWindowPicker::GetWindowIcon(const WindowId& id, int* width,
+ int* height) {
+ return enumerator_->GetWindowIcon(id, width, height);
+}
+
+uint8* LinuxWindowPicker::GetWindowThumbnail(const WindowId& id, int width,
+ int height) {
+ return enumerator_->GetWindowThumbnail(id, width, height);
+}
+
+int LinuxWindowPicker::GetNumDesktops() {
+ return enumerator_->GetNumDesktops();
+}
+
+uint8* LinuxWindowPicker::GetDesktopThumbnail(const DesktopId& id,
+ int width,
+ int height) {
+ return enumerator_->GetDesktopThumbnail(id, width, height);
+}
+
+} // namespace talk_base
diff --git a/talk/base/linuxwindowpicker.h b/talk/base/linuxwindowpicker.h
new file mode 100644
index 0000000..ad92c6f
--- /dev/null
+++ b/talk/base/linuxwindowpicker.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_LINUXWINDOWPICKER_H_
+#define TALK_BASE_LINUXWINDOWPICKER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/windowpicker.h"
+
+// Avoid include <X11/Xlib.h>.
+struct _XDisplay;
+typedef unsigned long Window;
+
+namespace talk_base {
+
+class XWindowEnumerator;
+
+class LinuxWindowPicker : public WindowPicker {
+ public:
+ LinuxWindowPicker();
+ ~LinuxWindowPicker();
+
+ static bool IsDesktopElement(_XDisplay* display, Window window);
+
+ virtual bool Init();
+ virtual bool IsVisible(const WindowId& id);
+ virtual bool MoveToFront(const WindowId& id);
+ virtual bool GetWindowList(WindowDescriptionList* descriptions);
+ virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+ uint8* GetWindowIcon(const WindowId& id, int* width, int* height);
+ uint8* GetWindowThumbnail(const WindowId& id, int width, int height);
+ int GetNumDesktops();
+ uint8* GetDesktopThumbnail(const DesktopId& id, int width, int height);
+
+ private:
+ scoped_ptr<XWindowEnumerator> enumerator_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LINUXWINDOWPICKER_H_
diff --git a/talk/base/linuxwindowpicker_unittest.cc b/talk/base/linuxwindowpicker_unittest.cc
new file mode 100644
index 0000000..fa0d49a
--- /dev/null
+++ b/talk/base/linuxwindowpicker_unittest.cc
@@ -0,0 +1,46 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/linuxwindowpicker.h"
+#include "talk/base/logging.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef LINUX
+#error Only for Linux
+#endif
+
+namespace talk_base {
+
+TEST(LinuxWindowPickerTest, TestGetWindowList) {
+ LinuxWindowPicker window_picker;
+ WindowDescriptionList descriptions;
+ window_picker.Init();
+ window_picker.GetWindowList(&descriptions);
+}
+
+} // namespace talk_base
diff --git a/talk/base/logging.cc b/talk/base/logging.cc
index b5c588c..f094042 100644
--- a/talk/base/logging.cc
+++ b/talk/base/logging.cc
@@ -51,7 +51,7 @@
#include "talk/base/stream.h"
#include "talk/base/stringencode.h"
#include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
namespace talk_base {
diff --git a/talk/base/macasyncsocket.cc b/talk/base/macasyncsocket.cc
index e31be57..4924333 100644
--- a/talk/base/macasyncsocket.cc
+++ b/talk/base/macasyncsocket.cc
@@ -328,9 +328,11 @@
}
if (res) {
- // Add this socket to the run loop
- source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 0);
+ // Add this socket to the run loop, at priority 1 so that it will be
+ // queued behind any pending signals.
+ source_ = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket_, 1);
res = (source_ != NULL);
+ if (!res) errno = EINVAL;
}
if (res) {
diff --git a/talk/base/maccocoasocketserver.h b/talk/base/maccocoasocketserver.h
new file mode 100644
index 0000000..41c9f71
--- /dev/null
+++ b/talk/base/maccocoasocketserver.h
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+// A libjingle compatible SocketServer for OSX/iOS/Cocoa.
+
+#if !defined(USE_COCOA_THREADING) || (!defined(OSX) && !defined(IOS))
+#error You can only include this file on OSX or IOS, and must also define
+#error USE_COCOA_THREADING.
+#endif
+
+#import <Foundation/Foundation.h>
+
+#import "talk/base/messagequeue.h"
+#import "talk/base/socketserver.h"
+
+namespace talk_base {
+class MacCocoaSocketServer;
+}
+
+// MacCocoaSocketServerHelper serves as a delegate to NSMachPort or a target for
+// a timeout.
+@interface MacCocoaSocketServerHelper : NSObject {
+ // This is a weak reference. This works fine since the
+ // talk_base::MacCocoaSocketServer owns this object.
+ talk_base::MacCocoaSocketServer* socketServer; // weak.
+}
+
+@end
+
+namespace talk_base {
+
+// The name "SocketServer" is misleading for this class. This class inherits
+// from SocketServer, some variants of which create/use physical
+// sockets (specifically, PhysicalSocketServer). A general explanation
+// (courtesy of bpm) is that SocketServer is responsible for allowing a thread
+// to go into an efficient wait state while it waits for a timer to expire or an
+// incoming message from MessageQueue. For GUI applications, the main thread
+// must continue to pump the main event loop during this wait state, hence the
+// need for specialized SocketServers to run on the main thread.
+//
+// This particular socketServer however cannot handle a generalized wait state.
+// It can only handle Wait when called with cms == 0, or when cms > 0 and
+// process_io is false. In the former case, the process_io argument is ignored,
+// and in the latter case, the thread will sleep for cms milliSeconds.
+//
+// Use this class in a Cocoa application that uses libjingle's Task and
+// MessageQueue, on the main thread. The typical usage is something like this:
+//
+// talk_base::Thread* current = talk_base::Thread::Current();
+// MacCocoaSocketServer* ss =
+// new talk_base::MacCocoaSocketServer(current);
+// current->set_socketserver(ss);
+//
+// // Now schedule some libjingle tasks, then call
+//
+// [NSApp run];
+
+class MacCocoaSocketServer : public SocketServer {
+ public:
+ explicit MacCocoaSocketServer(MessageQueue* message_queue);
+ virtual ~MacCocoaSocketServer();
+
+ // SocketServer Interface. We don't create any synchronous sockets.
+ virtual Socket* CreateSocket(int type) { return NULL; }
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+
+ virtual bool Wait(int cms, bool process_io);
+ virtual void WakeUp();
+
+ void Pump();
+
+ private:
+ // message_queue_ is not owned by this object.
+ // It is cached from the constructor argument.
+ MessageQueue* message_queue_; // weak.
+ MacCocoaSocketServerHelper* helper_;
+ NSTimer* timer_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(MacCocoaSocketServer);
+};
+
+} // namespace talk_base
diff --git a/talk/base/maccocoasocketserver.mm b/talk/base/maccocoasocketserver.mm
new file mode 100644
index 0000000..6a91d43
--- /dev/null
+++ b/talk/base/maccocoasocketserver.mm
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+#import <assert.h>
+
+#import "talk/base/maccocoasocketserver.h"
+#import "talk/base/logging.h"
+#import "talk/base/macasyncsocket.h"
+
+static const double kTimerIntervalSecs = 0.1;
+
+@implementation MacCocoaSocketServerHelper
+
+- (id)initWithSocketServer:(talk_base::MacCocoaSocketServer*)ss {
+ self = [super init];
+ if (self) {
+ socketServer = ss;
+ }
+ return self;
+}
+
+- (void)timerFired:(NSTimer*)timer {
+ socketServer->Pump();
+}
+
+- (void)wakeUp {
+ socketServer->Pump();
+}
+
+@end
+
+namespace talk_base {
+
+MacCocoaSocketServer::MacCocoaSocketServer(MessageQueue* message_queue) :
+ message_queue_(message_queue) {
+ helper_ = [[MacCocoaSocketServerHelper alloc] initWithSocketServer:this];
+
+ NSTimer* timer =
+ [NSTimer scheduledTimerWithTimeInterval:kTimerIntervalSecs
+ target:helper_
+ selector:@selector(timerFired:)
+ userInfo:nil
+ repeats:YES];
+ timer_ = [timer retain];
+}
+
+MacCocoaSocketServer::~MacCocoaSocketServer() {
+ [timer_ invalidate];
+ [timer_ release];
+ [helper_ release];
+}
+
+AsyncSocket* MacCocoaSocketServer::CreateAsyncSocket(int type) {
+ assert(type == SOCK_STREAM);
+ return new MacAsyncSocket();
+}
+
+bool MacCocoaSocketServer::Wait(int cms, bool process_io) {
+ // Ideally we would have some way to run the UI loop for |cms| milliSeconds,
+ // or until WakeUp() is called (whichever is earlier).
+ // But there is no good solution for that - stopping/restarting the
+ // NSApp run loop or calling nextEventMatchingTask both have significant
+ // overhead, resulting in high CPU utilization when there are a lot of
+ // libjingle messages (hence WakeUp and Wait calls) floating around.
+
+ // Simply calling "usleep" will block the UI, which is OK when |process_io|
+ // is false.
+
+ if (cms != 0) {
+ assert(cms > 0 && !process_io);
+ if (cms < 0 || process_io) {
+ return false;
+ }
+ usleep(cms * 1000);
+ }
+
+ return true;
+}
+
+void MacCocoaSocketServer::WakeUp() {
+ [helper_ performSelectorOnMainThread:@selector(wakeUp)
+ withObject:nil
+ waitUntilDone:NO];
+}
+
+void MacCocoaSocketServer::Pump() {
+ // Process messages.
+
+ Message msg;
+ // We don't want to process an unbounded number of messages - while we do that
+ // the UI remains blocked. So we only process as many messages as are in the
+ // queue when we start.
+ //
+ // max(1,..) ensures we run Get() at least once, this is needed to check
+ // for "sent" messages that otherwise are not included into size() result.
+ for (size_t max_messages_to_process = _max<size_t>(1, message_queue_->size());
+ max_messages_to_process > 0 && message_queue_->Get(&msg, 0);
+ --max_messages_to_process) {
+ message_queue_->Dispatch(&msg);
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/maccocoathreadhelper.h b/talk/base/maccocoathreadhelper.h
new file mode 100644
index 0000000..a3cb3a8
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Helper function for using Cocoa with Posix threads. This header should be
+// included from C/C++ files that want to use some Cocoa functionality without
+// using the .mm extension (mostly for files that are compiled on multiple
+// platforms).
+
+#ifndef TALK_BASE_MACCOCOATHREADHELPER_H__
+#define TALK_BASE_MACCOCOATHREADHELPER_H__
+
+#if !defined(USE_COCOA_THREADING) || (!defined(OSX) && !defined(IOS))
+#error You can only include this file on OSX or IOS, and must also define
+#error USE_COCOA_THREADING.
+#endif
+
+namespace talk_base {
+
+// Cocoa must be "put into multithreading mode" before Cocoa functionality can
+// be used on POSIX threads. This function does that.
+void InitCocoaMultiThreading();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MACCOCOATHREADHELPER_H__
diff --git a/talk/base/maccocoathreadhelper.mm b/talk/base/maccocoathreadhelper.mm
new file mode 100644
index 0000000..e8482ce
--- /dev/null
+++ b/talk/base/maccocoathreadhelper.mm
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+// Helper function for using Cocoa with Posix threading.
+
+#import <assert.h>
+#import <Foundation/Foundation.h>
+
+#import "talk/base/maccocoathreadhelper.h"
+
+// A dummy thread object which essentially does nothing.
+// This is used in InitCocoaMultiThreading method.
+@interface DummyThreadObject : NSObject {
+}
+
++ (void)dummyFunc:(NSObject*)obj;
+
+@end
+
+@implementation DummyThreadObject
+
++ (void)dummyFunc:(NSObject*)obj {
+ // nothing to do
+}
+
+@end
+
+namespace talk_base {
+
+// Cocoa must be "put into multithreading mode" before Cocoa functionality can
+// be used on POSIX threads. The way to do that is to spawn one thread that may
+// immediately exit.
+void InitCocoaMultiThreading() {
+ if ([NSThread isMultiThreaded] == NO) {
+ // The sole purpose of this autorelease pool is to avoid a console
+ // message on Leopard that tells us we're autoreleasing the thread
+ // with no autorelease pool in place; we can't set up an autorelease
+ // pool before this, because this is executed from an initializer,
+ // which is run before main. This means we leak an autorelease pool,
+ // and one thread, and if other objects are set up in initializers after
+ // this they'll be silently added to this pool and never released.
+
+ // Doing NSAutoreleasePool* hack = [[NSAutoreleasePool alloc] init];
+ // causes unused variable error.
+ NSAutoreleasePool* hack;
+ hack = [[NSAutoreleasePool alloc] init];
+ [NSThread detachNewThreadSelector:@selector(dummyFunc:)
+ toTarget:[DummyThreadObject class]
+ withObject:nil];
+ }
+
+ assert([NSThread isMultiThreaded]);
+}
+
+} // namespace talk_base
diff --git a/talk/base/macsocketserver.cc b/talk/base/macsocketserver.cc
index 154b83d..5b2ec03 100644
--- a/talk/base/macsocketserver.cc
+++ b/talk/base/macsocketserver.cc
@@ -41,6 +41,47 @@
ASSERT(found == 1);
}
+bool MacBaseSocketServer::SetPosixSignalHandler(int signum,
+ void (*handler)(int)) {
+ Dispatcher* dispatcher = signal_dispatcher();
+ if (!PhysicalSocketServer::SetPosixSignalHandler(signum, handler)) {
+ return false;
+ }
+
+ // Only register the FD once, when the first custom handler is installed.
+ if (!dispatcher && (dispatcher = signal_dispatcher())) {
+ CFFileDescriptorContext ctx = { 0 };
+ ctx.info = this;
+
+ CFFileDescriptorRef desc = CFFileDescriptorCreate(
+ kCFAllocatorDefault,
+ dispatcher->GetDescriptor(),
+ false,
+ &MacBaseSocketServer::FileDescriptorCallback,
+ &ctx);
+ if (!desc) {
+ return false;
+ }
+
+ CFFileDescriptorEnableCallBacks(desc, kCFFileDescriptorReadCallBack);
+ CFRunLoopSourceRef ref =
+ CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, desc, 0);
+
+ if (!ref) {
+ CFRelease(desc);
+ return false;
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), ref, kCFRunLoopCommonModes);
+ CFRelease(desc);
+ CFRelease(ref);
+ }
+
+ return true;
+}
+
+// Used to disable socket events from waking our message queue when
+// process_io is false. Does not disable signal event handling though.
void MacBaseSocketServer::EnableSocketCallbacks(bool enable) {
for (std::set<MacAsyncSocket*>::iterator it = sockets().begin();
it != sockets().end(); ++it) {
@@ -52,6 +93,21 @@
}
}
+void MacBaseSocketServer::FileDescriptorCallback(CFFileDescriptorRef fd,
+ CFOptionFlags flags,
+ void* context) {
+ MacBaseSocketServer* this_ss =
+ reinterpret_cast<MacBaseSocketServer*>(context);
+ ASSERT(this_ss);
+ Dispatcher* signal_dispatcher = this_ss->signal_dispatcher();
+ ASSERT(signal_dispatcher);
+
+ signal_dispatcher->OnPreEvent(DE_READ);
+ signal_dispatcher->OnEvent(DE_READ, 0);
+ CFFileDescriptorEnableCallBacks(fd, kCFFileDescriptorReadCallBack);
+}
+
+
///////////////////////////////////////////////////////////////////////////////
// MacCFSocketServer
///////////////////////////////////////////////////////////////////////////////
@@ -225,6 +281,9 @@
// MacCarbonAppSocketServer
///////////////////////////////////////////////////////////////////////////////
+// Carbon is deprecated for x64. Switch to Cocoa
+#if !defined(__x86_64__)
+
MacCarbonAppSocketServer::MacCarbonAppSocketServer()
: event_queue_(GetCurrentEventQueue()) {
// Install event handler
@@ -296,6 +355,7 @@
}
ReleaseEvent(wake_up);
}
+#endif
///////////////////////////////////////////////////////////////////////////////
// MacNotificationsSocketServer
diff --git a/talk/base/macsocketserver.h b/talk/base/macsocketserver.h
index 6a4a729..53118b8 100644
--- a/talk/base/macsocketserver.h
+++ b/talk/base/macsocketserver.h
@@ -31,6 +31,9 @@
void RegisterSocket(MacAsyncSocket* socket);
void UnregisterSocket(MacAsyncSocket* socket);
+ // PhysicalSocketServer Overrides
+ virtual bool SetPosixSignalHandler(int signum, void (*handler)(int));
+
protected:
void EnableSocketCallbacks(bool enable);
const std::set<MacAsyncSocket*>& sockets() {
@@ -38,6 +41,10 @@
}
private:
+ static void FileDescriptorCallback(CFFileDescriptorRef ref,
+ CFOptionFlags flags,
+ void* context);
+
std::set<MacAsyncSocket*> sockets_;
};
diff --git a/talk/base/macsocketserver_unittest.cc b/talk/base/macsocketserver_unittest.cc
new file mode 100644
index 0000000..a204c24
--- /dev/null
+++ b/talk/base/macsocketserver_unittest.cc
@@ -0,0 +1,195 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+#include "talk/base/macsocketserver.h"
+
+namespace talk_base {
+
+class WakeThread : public Thread {
+ public:
+ WakeThread(SocketServer* ss) : ss_(ss) {
+ }
+ void Run() {
+ ss_->WakeUp();
+ }
+ private:
+ SocketServer* ss_;
+};
+
+// Test that MacCFSocketServer::Wait works as expected.
+TEST(MacCFSocketServerTest, TestWait) {
+ MacCFSocketServer server;
+ uint32 start = Time();
+ server.Wait(1000, true);
+ EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCFSocketServer::Wakeup works as expected.
+TEST(MacCFSocketServerTest, TestWakeup) {
+ MacCFSocketServer server;
+ WakeThread thread(&server);
+ uint32 start = Time();
+ thread.Start();
+ server.Wait(10000, true);
+ EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacCarbonSocketServer::Wait works as expected.
+TEST(MacCarbonSocketServerTest, TestWait) {
+ MacCarbonSocketServer server;
+ uint32 start = Time();
+ server.Wait(1000, true);
+ EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCarbonSocketServer::Wakeup works as expected.
+TEST(MacCarbonSocketServerTest, TestWakeup) {
+ MacCarbonSocketServer server;
+ WakeThread thread(&server);
+ uint32 start = Time();
+ thread.Start();
+ server.Wait(10000, true);
+ EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacCarbonAppSocketServer::Wait works as expected.
+TEST(MacCarbonAppSocketServerTest, TestWait) {
+ MacCarbonAppSocketServer server;
+ uint32 start = Time();
+ server.Wait(1000, true);
+ EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that MacCarbonAppSocketServer::Wakeup works as expected.
+TEST(MacCarbonAppSocketServerTest, TestWakeup) {
+ MacCarbonAppSocketServer server;
+ WakeThread thread(&server);
+ uint32 start = Time();
+ thread.Start();
+ server.Wait(10000, true);
+ EXPECT_LT(TimeSince(start), 10000);
+}
+
+// Test that MacAsyncSocket passes all the generic Socket tests.
+class MacAsyncSocketTest : public SocketTest {
+ protected:
+ MacAsyncSocketTest()
+ : server_(CreateSocketServer()),
+ scope_(server_.get()) {}
+ // Override for other implementations of MacBaseSocketServer.
+ virtual MacBaseSocketServer* CreateSocketServer() {
+ return new MacCFSocketServer();
+ };
+ talk_base::scoped_ptr<MacBaseSocketServer> server_;
+ SocketServerScope scope_;
+};
+
+TEST_F(MacAsyncSocketTest, TestConnect) {
+ SocketTest::TestConnect();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithDnsLookup) {
+ SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectFail) {
+ SocketTest::TestConnectFail();
+}
+
+// Reenable once we have mac async dns
+TEST_F(MacAsyncSocketTest, DISABLED_TestConnectWithDnsLookupFail) {
+ SocketTest::TestConnectWithDnsLookupFail();
+}
+
+TEST_F(MacAsyncSocketTest, TestConnectWithClosedSocket) {
+ SocketTest::TestConnectWithClosedSocket();
+}
+
+// Flaky at the moment (10% failure rate). Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, DISABLED_TestServerCloseDuringConnect) {
+ SocketTest::TestServerCloseDuringConnect();
+}
+// Flaky at the moment (0.5% failure rate). Seems the client doesn't get
+// signalled in a timely manner...
+TEST_F(MacAsyncSocketTest, TestClientCloseDuringConnect) {
+ SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(MacAsyncSocketTest, TestServerClose) {
+ SocketTest::TestServerClose();
+}
+
+TEST_F(MacAsyncSocketTest, TestCloseInClosedCallback) {
+ SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(MacAsyncSocketTest, TestSocketServerWait) {
+ SocketTest::TestSocketServerWait();
+}
+
+TEST_F(MacAsyncSocketTest, TestTcp) {
+ SocketTest::TestTcp();
+}
+
+TEST_F(MacAsyncSocketTest, TestSingleFlowControlCallback) {
+ SocketTest::TestSingleFlowControlCallback();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestUdp) {
+ SocketTest::TestUdp();
+}
+
+TEST_F(MacAsyncSocketTest, DISABLED_TestGetSetOptions) {
+ SocketTest::TestGetSetOptions();
+}
+
+class MacCarbonAsyncSocketTest : public MacAsyncSocketTest {
+ virtual MacBaseSocketServer* CreateSocketServer() {
+ return new MacCarbonSocketServer();
+ };
+};
+
+TEST_F(MacCarbonAsyncSocketTest, TestSocketServerWait) {
+ SocketTest::TestSocketServerWait();
+}
+
+class MacCarbonAppAsyncSocketTest : public MacAsyncSocketTest {
+ virtual MacBaseSocketServer* CreateSocketServer() {
+ return new MacCarbonAppSocketServer();
+ };
+};
+
+TEST_F(MacCarbonAppAsyncSocketTest, TestSocketServerWait) {
+ SocketTest::TestSocketServerWait();
+}
+
+} // namespace talk_base
diff --git a/talk/base/macutils_unittest.cc b/talk/base/macutils_unittest.cc
new file mode 100644
index 0000000..c66000a
--- /dev/null
+++ b/talk/base/macutils_unittest.cc
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/macutils.h"
+
+TEST(MacUtilsTest, GetOsVersionName) {
+ talk_base::MacOSVersionName ver = talk_base::GetOSVersionName();
+ EXPECT_NE(talk_base::kMacOSUnknown, ver);
+}
+
+TEST(MacUtilsTest, GetQuickTimeVersion) {
+ std::string version;
+ EXPECT_TRUE(talk_base::GetQuickTimeVersion(&version));
+}
+
+TEST(MacUtilsTest, RunAppleScriptCompileError) {
+ std::string script("set value to to 5");
+ EXPECT_FALSE(talk_base::RunAppleScript(script));
+}
+
+TEST(MacUtilsTest, RunAppleScriptRuntimeError) {
+ std::string script("set value to 5 / 0");
+ EXPECT_FALSE(talk_base::RunAppleScript(script));
+}
+
+TEST(MacUtilsTest, RunAppleScriptSuccess) {
+ std::string script("set value to 5");
+ EXPECT_TRUE(talk_base::RunAppleScript(script));
+}
diff --git a/talk/base/macwindowpicker.cc b/talk/base/macwindowpicker.cc
new file mode 100644
index 0000000..d9cb752
--- /dev/null
+++ b/talk/base/macwindowpicker.cc
@@ -0,0 +1,241 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/macwindowpicker.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <dlfcn.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+
+namespace talk_base {
+
+static const char* kCoreGraphicsName =
+ "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/"
+ "CoreGraphics.framework/CoreGraphics";
+
+static const char* kWindowListCopyWindowInfo = "CGWindowListCopyWindowInfo";
+static const char* kWindowListCreateDescriptionFromArray =
+ "CGWindowListCreateDescriptionFromArray";
+
+// Function pointer for holding the CGWindowListCopyWindowInfo function.
+typedef CFArrayRef(*CGWindowListCopyWindowInfoProc)(CGWindowListOption,
+ CGWindowID);
+
+// Function pointer for holding the CGWindowListCreateDescriptionFromArray
+// function.
+typedef CFArrayRef(*CGWindowListCreateDescriptionFromArrayProc)(CFArrayRef);
+
+MacWindowPicker::MacWindowPicker() : lib_handle_(NULL), get_window_list_(NULL),
+ get_window_list_desc_(NULL) {
+}
+
+MacWindowPicker::~MacWindowPicker() {
+ if (lib_handle_ != NULL) {
+ dlclose(lib_handle_);
+ }
+}
+
+bool MacWindowPicker::Init() {
+ // TODO: If this class grows to use more dynamically functions
+ // from the CoreGraphics framework, consider using
+ // talk/base/latebindingsymboltable.h.
+ lib_handle_ = dlopen(kCoreGraphicsName, RTLD_NOW);
+ if (lib_handle_ == NULL) {
+ LOG(LS_ERROR) << "Could not load CoreGraphics";
+ return false;
+ }
+
+ get_window_list_ = dlsym(lib_handle_, kWindowListCopyWindowInfo);
+ get_window_list_desc_ =
+ dlsym(lib_handle_, kWindowListCreateDescriptionFromArray);
+ if (get_window_list_ == NULL || get_window_list_desc_ == NULL) {
+ // The CGWindowListCopyWindowInfo and the
+ // CGWindowListCreateDescriptionFromArray functions was introduced
+ // in Leopard(10.5) so this is a normal failure on Tiger.
+ LOG(LS_INFO) << "Failed to load Core Graphics symbols";
+ dlclose(lib_handle_);
+ lib_handle_ = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+bool MacWindowPicker::IsVisible(const WindowId& id) {
+ // Init if we're not already inited.
+ if (get_window_list_desc_ == NULL && !Init()) {
+ return false;
+ }
+ CGWindowID ids[1];
+ ids[0] = id.id();
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+
+ CFArrayRef window_array =
+ reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
+ get_window_list_desc_)(window_id_array);
+ if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
+ // Could not find the window. It might have been closed.
+ LOG(LS_INFO) << "Window not found";
+ CFRelease(window_id_array);
+ return false;
+ }
+
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFBooleanRef is_visible = reinterpret_cast<CFBooleanRef>(
+ CFDictionaryGetValue(window, kCGWindowIsOnscreen));
+
+ // Check that the window is visible. If not we might crash.
+ bool visible = false;
+ if (is_visible != NULL) {
+ visible = CFBooleanGetValue(is_visible);
+ }
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+ return visible;
+}
+
+bool MacWindowPicker::MoveToFront(const WindowId& id) {
+ // Init if we're not already initialized.
+ if (get_window_list_desc_ == NULL && !Init()) {
+ return false;
+ }
+ CGWindowID ids[1];
+ ids[0] = id.id();
+ CFArrayRef window_id_array =
+ CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
+
+ CFArrayRef window_array =
+ reinterpret_cast<CGWindowListCreateDescriptionFromArrayProc>(
+ get_window_list_desc_)(window_id_array);
+ if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
+ // Could not find the window. It might have been closed.
+ LOG(LS_INFO) << "Window not found";
+ CFRelease(window_id_array);
+ return false;
+ }
+
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, 0));
+ CFStringRef window_name_ref = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+ CFNumberRef application_pid = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowOwnerPID));
+
+ int pid_val;
+ CFNumberGetValue(application_pid, kCFNumberIntType, &pid_val);
+ std::string window_name;
+ ToUtf8(window_name_ref, &window_name);
+
+ // Build an applescript that sets the selected window to front
+ // within the application. Then set the application to front.
+ bool result = true;
+ std::stringstream ss;
+ ss << "tell application \"System Events\"\n"
+ << "set proc to the first item of (every process whose unix id is "
+ << pid_val
+ << ")\n"
+ << "tell proc to perform action \"AXRaise\" of window \""
+ << window_name
+ << "\"\n"
+ << "set the frontmost of proc to true\n"
+ << "end tell";
+ if (!RunAppleScript(ss.str())) {
+ // This might happen to for example X applications where the X
+ // server spawns of processes with their own PID but the X server
+ // is still registered as owner to the application windows. As a
+ // workaround, we put the X server process to front, meaning that
+ // all X applications will show up. The drawback with this
+ // workaround is that the application that we really wanted to set
+ // to front might be behind another X application.
+ ProcessSerialNumber psn;
+ pid_t pid = pid_val;
+ int res = GetProcessForPID(pid, &psn);
+ if (res != 0) {
+ LOG(LS_ERROR) << "Failed getting process for pid";
+ result = false;
+ }
+ res = SetFrontProcess(&psn);
+ if (res != 0) {
+ LOG(LS_ERROR) << "Failed setting process to front";
+ result = false;
+ }
+ }
+ CFRelease(window_id_array);
+ CFRelease(window_array);
+ return result;
+}
+
+bool MacWindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+ const uint32_t kMaxDisplays = 128;
+ CGDirectDisplayID active_displays[kMaxDisplays];
+ uint32_t display_count = 0;
+
+ CGError err = CGGetActiveDisplayList(kMaxDisplays,
+ active_displays,
+ &display_count);
+ if (err != kCGErrorSuccess) {
+ LOG_E(LS_ERROR, OS, err) << "Failed to enumerate the active displays.";
+ return false;
+ }
+ for (uint32_t i = 0; i < display_count; ++i) {
+ DesktopId id(active_displays[i], static_cast<int>(i));
+ // TODO: Figure out an appropriate desktop title.
+ DesktopDescription desc(id, "");
+ descriptions->push_back(desc);
+ }
+ return display_count > 0;
+}
+
+bool MacWindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+ // Init if we're not already inited.
+ if (get_window_list_ == NULL && !Init()) {
+ return false;
+ }
+
+ // Only get onscreen, non-desktop windows.
+ CFArrayRef window_array =
+ reinterpret_cast<CGWindowListCopyWindowInfoProc>(get_window_list_)(
+ kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
+ kCGNullWindowID);
+ if (window_array == NULL) {
+ return false;
+ }
+
+ // Check windows to make sure they have an id, title, and use window layer 0.
+ CFIndex i;
+ CFIndex count = CFArrayGetCount(window_array);
+ for (i = 0; i < count; ++i) {
+ CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(window_array, i));
+ CFStringRef window_title = reinterpret_cast<CFStringRef>(
+ CFDictionaryGetValue(window, kCGWindowName));
+ CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowNumber));
+ CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
+ CFDictionaryGetValue(window, kCGWindowLayer));
+ if (window_title != NULL && window_id != NULL && window_layer != NULL) {
+ std::string title_str;
+ int id_val, layer_val;
+ ToUtf8(window_title, &title_str);
+ CFNumberGetValue(window_id, kCFNumberIntType, &id_val);
+ CFNumberGetValue(window_layer, kCFNumberIntType, &layer_val);
+
+ // Discard windows without a title.
+ if (layer_val == 0 && title_str.length() > 0) {
+ WindowId id(static_cast<CGWindowID>(id_val));
+ WindowDescription desc(id, title_str);
+ descriptions->push_back(desc);
+ }
+ }
+ }
+
+ CFRelease(window_array);
+ return true;
+}
+
+} // namespace talk_base
diff --git a/talk/base/macwindowpicker.h b/talk/base/macwindowpicker.h
new file mode 100644
index 0000000..d1ca6b8
--- /dev/null
+++ b/talk/base/macwindowpicker.h
@@ -0,0 +1,29 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#ifndef TALK_BASE_MACWINDOWPICKER_H_
+#define TALK_BASE_MACWINDOWPICKER_H_
+
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class MacWindowPicker : public WindowPicker {
+ public:
+ MacWindowPicker();
+ ~MacWindowPicker();
+ virtual bool Init();
+ virtual bool IsVisible(const WindowId& id);
+ virtual bool MoveToFront(const WindowId& id);
+ virtual bool GetWindowList(WindowDescriptionList* descriptions);
+ virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+
+ private:
+ void* lib_handle_;
+ void* get_window_list_;
+ void* get_window_list_desc_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MACWINDOWPICKER_H_
diff --git a/talk/base/macwindowpicker_unittest.cc b/talk/base/macwindowpicker_unittest.cc
new file mode 100644
index 0000000..9cb67db
--- /dev/null
+++ b/talk/base/macwindowpicker_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+#include "talk/base/macwindowpicker.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef OSX
+#error Only for Mac OSX
+#endif
+
+namespace talk_base {
+
+bool IsLeopardOrLater() {
+ return GetOSVersionName() >= kMacOSLeopard;
+}
+
+// Test that this works on new versions and fails acceptably on old versions.
+TEST(MacWindowPickerTest, TestGetWindowList) {
+ MacWindowPicker picker, picker2;
+ WindowDescriptionList descriptions;
+ if (IsLeopardOrLater()) {
+ EXPECT_TRUE(picker.Init());
+ EXPECT_TRUE(picker.GetWindowList(&descriptions));
+ EXPECT_TRUE(picker2.GetWindowList(&descriptions)); // Init is optional
+ } else {
+ EXPECT_FALSE(picker.Init());
+ EXPECT_FALSE(picker.GetWindowList(&descriptions));
+ EXPECT_FALSE(picker2.GetWindowList(&descriptions));
+ }
+}
+
+// TODO: Add verification of the actual parsing, ie, add
+// functionality to inject a fake get_window_array function which
+// provide a pre-constructed list of windows.
+
+} // namespace talk_base
diff --git a/talk/base/mathutils.h b/talk/base/mathutils.h
new file mode 100644
index 0000000..eeb110a
--- /dev/null
+++ b/talk/base/mathutils.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2005 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_MATHUTILS_H_
+#define TALK_BASE_MATHUTILS_H_
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265359f
+#endif
+
+#endif // TALK_BASE_MATHUTILS_H_
diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h
index 54d4860..2857182 100644
--- a/talk/base/messagequeue.h
+++ b/talk/base/messagequeue.h
@@ -39,9 +39,10 @@
#include "talk/base/criticalsection.h"
#include "talk/base/messagehandler.h"
#include "talk/base/scoped_ptr.h"
+#include "talk/base/scoped_ref_ptr.h"
#include "talk/base/sigslot.h"
#include "talk/base/socketserver.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
namespace talk_base {
@@ -98,6 +99,17 @@
scoped_ptr<T> data_;
};
+// Like ScopedMessageData, but for reference counted pointers.
+template <class T>
+class ScopedRefMessageData : public MessageData {
+ public:
+ explicit ScopedRefMessageData(T* data) : data_(data) { }
+ const scoped_refptr<T>& data() const { return data_; }
+ scoped_refptr<T>& data() { return data_; }
+ private:
+ scoped_refptr<T> data_;
+};
+
template<class T>
inline MessageData* WrapMessageData(const T& data) {
return new TypedMessageData<T>(data);
diff --git a/talk/base/messagequeue_unittest.cc b/talk/base/messagequeue_unittest.cc
index 7414618..1ca65ea 100644
--- a/talk/base/messagequeue_unittest.cc
+++ b/talk/base/messagequeue_unittest.cc
@@ -27,7 +27,7 @@
#include "talk/base/gunit.h"
#include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/base/messagequeue.h"
using namespace talk_base;
diff --git a/talk/base/multipart.cc b/talk/base/multipart.cc
new file mode 100644
index 0000000..d280ff3
--- /dev/null
+++ b/talk/base/multipart.cc
@@ -0,0 +1,268 @@
+// libjingle
+// Copyright 2004--2010, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/multipart.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MultipartStream
+///////////////////////////////////////////////////////////////////////////////
+
+MultipartStream::MultipartStream(const std::string& type,
+ const std::string& boundary)
+ : type_(type),
+ boundary_(boundary),
+ adding_(true),
+ current_(0),
+ position_(0) {
+ // The content type should be multipart/*.
+ ASSERT(0 == strncmp(type_.c_str(), "multipart/", 10));
+}
+
+MultipartStream::~MultipartStream() {
+ Close();
+}
+
+void MultipartStream::GetContentType(std::string* content_type) {
+ ASSERT(NULL != content_type);
+ content_type->assign(type_);
+ content_type->append("; boundary=");
+ content_type->append(boundary_);
+}
+
+bool MultipartStream::AddPart(StreamInterface* data_stream,
+ const std::string& content_disposition,
+ const std::string& content_type) {
+ if (!AddPart("", content_disposition, content_type))
+ return false;
+ parts_.push_back(data_stream);
+ data_stream->SignalEvent.connect(this, &MultipartStream::OnEvent);
+ return true;
+}
+
+bool MultipartStream::AddPart(const std::string& data,
+ const std::string& content_disposition,
+ const std::string& content_type) {
+ ASSERT(adding_);
+ if (!adding_)
+ return false;
+ std::stringstream ss;
+ if (!parts_.empty()) {
+ ss << "\r\n";
+ }
+ ss << "--" << boundary_ << "\r\n";
+ if (!content_disposition.empty()) {
+ ss << ToString(HH_CONTENT_DISPOSITION) << ": "
+ << content_disposition << "\r\n";
+ }
+ if (!content_type.empty()) {
+ ss << ToString(HH_CONTENT_TYPE) << ": "
+ << content_type << "\r\n";
+ }
+ ss << "\r\n" << data;
+ parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size()));
+ return true;
+}
+
+void MultipartStream::EndParts() {
+ ASSERT(adding_);
+ if (!adding_)
+ return;
+
+ std::stringstream ss;
+ if (!parts_.empty()) {
+ ss << "\r\n";
+ }
+ ss << "--" << boundary_ << "--" << "\r\n";
+ parts_.push_back(new MemoryStream(ss.str().data(), ss.str().size()));
+
+ ASSERT(0 == current_);
+ ASSERT(0 == position_);
+ adding_ = false;
+ SignalEvent(this, SE_OPEN | SE_READ, 0);
+}
+
+size_t MultipartStream::GetPartSize(const std::string& data,
+ const std::string& content_disposition,
+ const std::string& content_type) const {
+ size_t size = 0;
+ if (!parts_.empty()) {
+ size += 2; // for "\r\n";
+ }
+ size += boundary_.size() + 4; // for "--boundary_\r\n";
+ if (!content_disposition.empty()) {
+ // for ToString(HH_CONTENT_DISPOSITION): content_disposition\r\n
+ size += std::string(ToString(HH_CONTENT_DISPOSITION)).size() + 2 +
+ content_disposition.size() + 2;
+ }
+ if (!content_type.empty()) {
+ // for ToString(HH_CONTENT_TYPE): content_type\r\n
+ size += std::string(ToString(HH_CONTENT_TYPE)).size() + 2 +
+ content_type.size() + 2;
+ }
+ size += 2 + data.size(); // for \r\ndata
+ return size;
+}
+
+size_t MultipartStream::GetEndPartSize() const {
+ size_t size = 0;
+ if (!parts_.empty()) {
+ size += 2; // for "\r\n";
+ }
+ size += boundary_.size() + 6; // for "--boundary_--\r\n";
+ return size;
+}
+
+//
+// StreamInterface
+//
+
+StreamState MultipartStream::GetState() const {
+ if (adding_) {
+ return SS_OPENING;
+ }
+ return (current_ < parts_.size()) ? SS_OPEN : SS_CLOSED;
+}
+
+StreamResult MultipartStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (adding_) {
+ return SR_BLOCK;
+ }
+ size_t local_read;
+ if (!read) read = &local_read;
+ while (current_ < parts_.size()) {
+ StreamResult result = parts_[current_]->Read(buffer, buffer_len, read,
+ error);
+ if (SR_EOS != result) {
+ if (SR_SUCCESS == result) {
+ position_ += *read;
+ }
+ return result;
+ }
+ ++current_;
+ }
+ return SR_EOS;
+}
+
+StreamResult MultipartStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (error) {
+ *error = -1;
+ }
+ return SR_ERROR;
+}
+
+void MultipartStream::Close() {
+ for (size_t i = 0; i < parts_.size(); ++i) {
+ delete parts_[i];
+ }
+ parts_.clear();
+ adding_ = false;
+ current_ = 0;
+ position_ = 0;
+}
+
+bool MultipartStream::SetPosition(size_t position) {
+ if (adding_) {
+ return false;
+ }
+ size_t part_size, part_offset = 0;
+ for (size_t i = 0; i < parts_.size(); ++i) {
+ if (!parts_[i]->GetSize(&part_size)) {
+ return false;
+ }
+ if (part_offset + part_size > position) {
+ for (size_t j = i+1; j < _min(parts_.size(), current_+1); ++j) {
+ if (!parts_[j]->Rewind()) {
+ return false;
+ }
+ }
+ if (!parts_[i]->SetPosition(position - part_offset)) {
+ return false;
+ }
+ current_ = i;
+ position_ = position;
+ return true;
+ }
+ part_offset += part_size;
+ }
+ return false;
+}
+
+bool MultipartStream::GetPosition(size_t* position) const {
+ if (position) {
+ *position = position_;
+ }
+ return true;
+}
+
+bool MultipartStream::GetSize(size_t* size) const {
+ size_t part_size, total_size = 0;
+ for (size_t i = 0; i < parts_.size(); ++i) {
+ if (!parts_[i]->GetSize(&part_size)) {
+ return false;
+ }
+ total_size += part_size;
+ }
+ if (size) {
+ *size = total_size;
+ }
+ return true;
+}
+
+bool MultipartStream::GetAvailable(size_t* size) const {
+ if (adding_) {
+ return false;
+ }
+ size_t part_size, total_size = 0;
+ for (size_t i = current_; i < parts_.size(); ++i) {
+ if (!parts_[i]->GetAvailable(&part_size)) {
+ return false;
+ }
+ total_size += part_size;
+ }
+ if (size) {
+ *size = total_size;
+ }
+ return true;
+}
+
+//
+// StreamInterface Slots
+//
+
+void MultipartStream::OnEvent(StreamInterface* stream, int events, int error) {
+ if (adding_ || (current_ >= parts_.size()) || (parts_[current_] != stream)) {
+ return;
+ }
+ SignalEvent(this, events, error);
+}
+
+} // namespace talk_base
diff --git a/talk/base/multipart.h b/talk/base/multipart.h
new file mode 100644
index 0000000..cce592b
--- /dev/null
+++ b/talk/base/multipart.h
@@ -0,0 +1,94 @@
+// libjingle
+// Copyright 2004--2010, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_BASE_MULTIPART_H__
+#define TALK_BASE_MULTIPART_H__
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// MultipartStream - Implements an RFC2046 multipart stream by concatenating
+// the supplied parts together, and adding the correct boundaries.
+///////////////////////////////////////////////////////////////////////////////
+
+class MultipartStream : public StreamInterface, public sigslot::has_slots<> {
+ public:
+ MultipartStream(const std::string& type, const std::string& boundary);
+ virtual ~MultipartStream();
+
+ void GetContentType(std::string* content_type);
+
+ // Note: If content_disposition and/or content_type are the empty string,
+ // they will be omitted.
+ bool AddPart(StreamInterface* data_stream,
+ const std::string& content_disposition,
+ const std::string& content_type);
+ bool AddPart(const std::string& data,
+ const std::string& content_disposition,
+ const std::string& content_type);
+ void EndParts();
+
+ // Calculates the size of a part before actually adding the part.
+ size_t GetPartSize(const std::string& data,
+ const std::string& content_disposition,
+ const std::string& content_type) const;
+ size_t GetEndPartSize() const;
+
+ // StreamInterface
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+ virtual bool SetPosition(size_t position);
+ virtual bool GetPosition(size_t* position) const;
+ virtual bool GetSize(size_t* size) const;
+ virtual bool GetAvailable(size_t* size) const;
+
+ private:
+ typedef std::vector<StreamInterface*> PartList;
+
+ // StreamInterface Slots
+ void OnEvent(StreamInterface* stream, int events, int error);
+
+ std::string type_, boundary_;
+ PartList parts_;
+ bool adding_;
+ size_t current_; // The index into parts_ of the current read position.
+ size_t position_; // The current read position in bytes.
+
+ DISALLOW_COPY_AND_ASSIGN(MultipartStream);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MULTIPART_H__
diff --git a/talk/base/multipart_unittest.cc b/talk/base/multipart_unittest.cc
new file mode 100644
index 0000000..18e3cf9
--- /dev/null
+++ b/talk/base/multipart_unittest.cc
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/multipart.h"
+
+namespace talk_base {
+
+static const std::string kTestMultipartBoundary = "123456789987654321";
+static const std::string kTestContentType =
+ "multipart/form-data; boundary=123456789987654321";
+static const char kTestData[] = "This is a test.";
+static const char kTestStreamContent[] = "This is a test stream.";
+
+TEST(MultipartTest, TestBasicOperations) {
+ MultipartStream multipart("multipart/form-data", kTestMultipartBoundary);
+ std::string content_type;
+ multipart.GetContentType(&content_type);
+ EXPECT_EQ(kTestContentType, content_type);
+
+ EXPECT_EQ(talk_base::SS_OPENING, multipart.GetState());
+
+ // The multipart stream contains only --boundary--\r\n
+ size_t end_part_size = multipart.GetEndPartSize();
+ multipart.EndParts();
+ EXPECT_EQ(talk_base::SS_OPEN, multipart.GetState());
+ size_t size;
+ EXPECT_TRUE(multipart.GetSize(&size));
+ EXPECT_EQ(end_part_size, size);
+
+ // Write is not supported.
+ EXPECT_EQ(talk_base::SR_ERROR,
+ multipart.Write(kTestData, sizeof(kTestData), NULL, NULL));
+
+ multipart.Close();
+ EXPECT_EQ(talk_base::SS_CLOSED, multipart.GetState());
+ EXPECT_TRUE(multipart.GetSize(&size));
+ EXPECT_EQ(0U, size);
+}
+
+TEST(MultipartTest, TestAddAndRead) {
+ MultipartStream multipart("multipart/form-data", kTestMultipartBoundary);
+
+ size_t part_size =
+ multipart.GetPartSize(kTestData, "form-data; name=\"text\"", "text");
+ EXPECT_TRUE(multipart.AddPart(kTestData, "form-data; name=\"text\"", "text"));
+ size_t size;
+ EXPECT_TRUE(multipart.GetSize(&size));
+ EXPECT_EQ(part_size, size);
+
+ talk_base::MemoryStream* stream =
+ new talk_base::MemoryStream(kTestStreamContent);
+ size_t stream_size = 0;
+ EXPECT_TRUE(stream->GetSize(&stream_size));
+ part_size +=
+ multipart.GetPartSize("", "form-data; name=\"stream\"", "stream");
+ part_size += stream_size;
+
+ EXPECT_TRUE(multipart.AddPart(
+ new talk_base::MemoryStream(kTestStreamContent),
+ "form-data; name=\"stream\"",
+ "stream"));
+ EXPECT_TRUE(multipart.GetSize(&size));
+ EXPECT_EQ(part_size, size);
+
+ // In adding state, block read.
+ char buffer[1024];
+ EXPECT_EQ(talk_base::SR_BLOCK,
+ multipart.Read(buffer, sizeof(buffer), NULL, NULL));
+ // Write is not supported.
+ EXPECT_EQ(talk_base::SR_ERROR,
+ multipart.Write(buffer, sizeof(buffer), NULL, NULL));
+
+ part_size += multipart.GetEndPartSize();
+ multipart.EndParts();
+ EXPECT_TRUE(multipart.GetSize(&size));
+ EXPECT_EQ(part_size, size);
+
+ // Read the multipart stream into StringStream
+ std::string str;
+ talk_base::StringStream str_stream(str);
+ EXPECT_EQ(talk_base::SR_SUCCESS,
+ Flow(&multipart, buffer, sizeof(buffer), &str_stream));
+ EXPECT_EQ(size, str.length());
+
+ // Search three boundaries and two parts in the order.
+ size_t pos = 0;
+ pos = str.find(kTestMultipartBoundary);
+ EXPECT_NE(std::string::npos, pos);
+ pos += kTestMultipartBoundary.length();
+
+ pos = str.find(kTestData, pos);
+ EXPECT_NE(std::string::npos, pos);
+ pos += sizeof(kTestData);
+
+ pos = str.find(kTestMultipartBoundary, pos);
+ EXPECT_NE(std::string::npos, pos);
+ pos += kTestMultipartBoundary.length();
+
+ pos = str.find(kTestStreamContent, pos);
+ EXPECT_NE(std::string::npos, pos);
+ pos += sizeof(kTestStreamContent);
+
+ pos = str.find(kTestMultipartBoundary, pos);
+ EXPECT_NE(std::string::npos, pos);
+ pos += kTestMultipartBoundary.length();
+
+ pos = str.find(kTestMultipartBoundary, pos);
+ EXPECT_EQ(std::string::npos, pos);
+}
+
+} // namespace talk_base
diff --git a/talk/base/nat_unittest.cc b/talk/base/nat_unittest.cc
new file mode 100644
index 0000000..8f87b3a
--- /dev/null
+++ b/talk/base/nat_unittest.cc
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/virtualsocketserver.h"
+
+using namespace talk_base;
+
+bool CheckReceive(
+ TestClient* client, bool should_receive, const char* buf, size_t size) {
+ return (should_receive) ?
+ client->CheckNextPacket(buf, size, 0) :
+ client->CheckNoPacket();
+}
+
+TestClient* CreateTestClient(
+ SocketFactory* factory, const SocketAddress& local_addr) {
+ AsyncUDPSocket* socket = AsyncUDPSocket::Create(factory, local_addr);
+ return new TestClient(socket);
+}
+
+// Tests that when sending from internal_addr to external_addrs through the
+// NAT type specified by nat_type, all external addrs receive the sent packet
+// and, if exp_same is true, all use the same mapped-address on the NAT.
+void TestSend(
+ 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(0); // Auto-select a port
+ NATServer* nat = new NATServer(
+ nat_type, internal, server_addr, external, external_addrs[0]);
+ NATSocketFactory* natsf = new NATSocketFactory(internal,
+ nat->internal_address());
+
+ 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, out[0]->address());
+ SocketAddress trans_addr;
+ EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr));
+
+ for (int i = 1; i < 4; i++) {
+ in->SendTo(buf, len, out[i]->address());
+ SocketAddress trans_addr2;
+ EXPECT_TRUE(out[i]->CheckNextPacket(buf, len, &trans_addr2));
+ bool are_same = (trans_addr == trans_addr2);
+ ASSERT_EQ(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];
+}
+
+// Tests that when sending from external_addrs to internal_addr, the packet
+// is delivered according to the specified filter_ip and filter_port rules.
+void TestRecv(
+ 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(0); // Auto-select a port
+ NATServer* nat = new NATServer(
+ nat_type, internal, server_addr, external, external_addrs[0]);
+ NATSocketFactory* natsf = new NATSocketFactory(internal,
+ nat->internal_address());
+
+ 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, out[0]->address());
+ SocketAddress trans_addr;
+ EXPECT_TRUE(out[0]->CheckNextPacket(buf, len, &trans_addr));
+
+ out[1]->SendTo(buf, len, trans_addr);
+ EXPECT_TRUE(CheckReceive(in, !filter_ip, buf, len));
+
+ out[2]->SendTo(buf, len, trans_addr);
+ EXPECT_TRUE(CheckReceive(in, !filter_port, buf, len));
+
+ out[3]->SendTo(buf, len, trans_addr);
+ EXPECT_TRUE(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];
+}
+
+// Tests that NATServer allocates bindings properly.
+void TestBindings(
+ SocketServer* internal, const SocketAddress& internal_addr,
+ SocketServer* external, const SocketAddress external_addrs[4]) {
+ TestSend(internal, internal_addr, external, external_addrs,
+ NAT_OPEN_CONE, true);
+ TestSend(internal, internal_addr, external, external_addrs,
+ NAT_ADDR_RESTRICTED, true);
+ TestSend(internal, internal_addr, external, external_addrs,
+ NAT_PORT_RESTRICTED, true);
+ TestSend(internal, internal_addr, external, external_addrs,
+ NAT_SYMMETRIC, false);
+}
+
+// Tests that NATServer filters packets properly.
+void TestFilters(
+ SocketServer* internal, const SocketAddress& internal_addr,
+ SocketServer* external, const SocketAddress external_addrs[4]) {
+ TestRecv(internal, internal_addr, external, external_addrs,
+ NAT_OPEN_CONE, false, false);
+ TestRecv(internal, internal_addr, external, external_addrs,
+ NAT_ADDR_RESTRICTED, true, false);
+ TestRecv(internal, internal_addr, external, external_addrs,
+ NAT_PORT_RESTRICTED, true, true);
+ TestRecv(internal, internal_addr, external, external_addrs,
+ NAT_SYMMETRIC, true, true);
+}
+
+TEST(NatTest, TestPhysical) {
+ BasicNetworkManager network_manager;
+ network_manager.StartUpdating();
+ // Process pending messages so the network list is updated.
+ Thread::Current()->ProcessMessages(0);
+
+ std::vector<Network*> networks;
+ network_manager.GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_WARNING) << "Not enough network adapters for test.";
+ return;
+ }
+
+ SocketAddress int_addr("127.0.0.1", 0);
+ std::string ext_ip1 = "127.0.0.1";
+ std::string ext_ip2 = networks[0]->ip().ToString();
+
+ LOG(LS_INFO) << "selected ip " << ext_ip2;
+
+ SocketAddress ext_addrs[4] = {
+ SocketAddress(ext_ip1, 0),
+ SocketAddress(ext_ip2, 0),
+ SocketAddress(ext_ip1, 0),
+ SocketAddress(ext_ip2, 0)
+ };
+
+ PhysicalSocketServer* int_pss = new PhysicalSocketServer();
+ PhysicalSocketServer* ext_pss = new PhysicalSocketServer();
+
+ TestBindings(int_pss, int_addr, ext_pss, ext_addrs);
+ TestFilters(int_pss, int_addr, ext_pss, ext_addrs);
+}
+
+class TestVirtualSocketServer : public VirtualSocketServer {
+ public:
+ explicit TestVirtualSocketServer(SocketServer* ss)
+ : VirtualSocketServer(ss) {}
+ // Expose this publicly
+ IPAddress GetNextIP(int af) { return VirtualSocketServer::GetNextIP(af); }
+};
+
+TEST(NatTest, TestVirtual) {
+ TestVirtualSocketServer* int_vss = new TestVirtualSocketServer(
+ new PhysicalSocketServer());
+ TestVirtualSocketServer* ext_vss = new TestVirtualSocketServer(
+ new PhysicalSocketServer());
+
+ // TODO: IPv6ize this test when the NAT stuff is v6ed.
+ SocketAddress int_addr, ext_addrs[4];
+ int_addr.SetIP(int_vss->GetNextIP(int_addr.ipaddr().family()));
+ ext_addrs[0].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
+ ext_addrs[1].SetIP(ext_vss->GetNextIP(int_addr.ipaddr().family()));
+ ext_addrs[2].SetIP(ext_addrs[0].ipaddr());
+ ext_addrs[3].SetIP(ext_addrs[1].ipaddr());
+
+ TestBindings(int_vss, int_addr, ext_vss, ext_addrs);
+ TestFilters(int_vss, int_addr, ext_vss, ext_addrs);
+}
+
+
+// TODO: Finish this test
+class NatTcpTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ NatTcpTest() : connected_(false) {}
+ virtual void SetUp() {
+ int_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer());
+ ext_vss_ = new TestVirtualSocketServer(new PhysicalSocketServer());
+ nat_ = new NATServer(NAT_OPEN_CONE, int_vss_, SocketAddress(),
+ ext_vss_, SocketAddress());
+ natsf_ = new NATSocketFactory(int_vss_, nat_->internal_address());
+ }
+ void OnConnectEvent(AsyncSocket* socket) {
+ connected_ = true;
+ }
+ void OnAcceptEvent(AsyncSocket* socket) {
+ accepted_ = server_->Accept(NULL);
+ }
+ void OnCloseEvent(AsyncSocket* socket, int error) {
+ }
+ void ConnectEvents() {
+ server_->SignalReadEvent.connect(this, &NatTcpTest::OnAcceptEvent);
+ client_->SignalConnectEvent.connect(this, &NatTcpTest::OnConnectEvent);
+ }
+ TestVirtualSocketServer* int_vss_;
+ TestVirtualSocketServer* ext_vss_;
+ NATServer* nat_;
+ NATSocketFactory* natsf_;
+ AsyncSocket* client_;
+ AsyncSocket* server_;
+ AsyncSocket* accepted_;
+ bool connected_;
+};
+
+TEST_F(NatTcpTest, DISABLED_TestConnectOut) {
+ server_ = ext_vss_->CreateAsyncSocket(SOCK_STREAM);
+ server_->Bind(SocketAddress());
+ server_->Listen(5);
+
+ client_ = int_vss_->CreateAsyncSocket(SOCK_STREAM);
+ EXPECT_GE(0, client_->Bind(SocketAddress()));
+ EXPECT_GE(0, client_->Connect(server_->GetLocalAddress()));
+
+
+ ConnectEvents();
+
+ EXPECT_TRUE_WAIT(connected_, 1000);
+ EXPECT_EQ(client_->GetRemoteAddress(), server_->GetLocalAddress());
+ EXPECT_EQ(client_->GetRemoteAddress(), accepted_->GetLocalAddress());
+ EXPECT_EQ(client_->GetLocalAddress(), accepted_->GetRemoteAddress());
+
+ client_->Close();
+}
+//#endif
diff --git a/talk/base/natserver.cc b/talk/base/natserver.cc
new file mode 100644
index 0000000..d287059
--- /dev/null
+++ b/talk/base/natserver.cc
@@ -0,0 +1,190 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/natsocketfactory.h"
+#include "talk/base/natserver.h"
+#include "talk/base/logging.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 ^= HashIP(a.ipaddr());
+ 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.ipaddr() < a2.ipaddr()))
+ return true;
+ if (use_ip && (a2.ipaddr() < a1.ipaddr()))
+ 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.ipaddr(), 0) {
+ nat_ = NAT::Create(type);
+
+ server_socket_ = AsyncUDPSocket::Create(internal, 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(
+ AsyncPacketSocket* socket, const char* buf, size_t size,
+ const SocketAddress& addr) {
+
+ // Read the intended destination from the wire.
+ SocketAddress dest_addr;
+ int length = UnpackAddressFromNAT(buf, size, &dest_addr);
+
+ // 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 + length, size - length, dest_addr);
+}
+
+void NATServer::OnExternalPacket(
+ AsyncPacketSocket* socket, const char* buf, size_t size,
+ const SocketAddress& remote_addr) {
+
+ 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)) {
+ LOG(LS_INFO) << "Packet from " << remote_addr.ToString()
+ << " was filtered out by the NAT.";
+ return;
+ }
+
+ // Forward this packet to the internal address.
+ // First prepend the address in a quasi-STUN format.
+ scoped_array<char> real_buf(new char[size + kNATEncodedIPv6AddressSize]);
+ size_t addrlength = PackAddressForNAT(real_buf.get(),
+ size + kNATEncodedIPv6AddressSize,
+ remote_addr);
+ // Copy the data part after the address.
+ std::memcpy(real_buf.get() + addrlength, buf, size);
+ server_socket_->SendTo(real_buf.get(), size + addrlength,
+ iter->second->route.source());
+}
+
+void NATServer::Translate(const SocketAddressPair& route) {
+ AsyncUDPSocket* socket = AsyncUDPSocket::Create(external_, external_ip_);
+
+ if (!socket) {
+ LOG(LS_ERROR) << "Couldn't find a free port!";
+ return;
+ }
+
+ TransEntry* entry = new TransEntry(route, socket, nat_);
+ (*int_map_)[route] = entry;
+ (*ext_map_)[socket->GetLocalAddress()] = entry;
+ socket->SignalReadPacket.connect(this, &NATServer::OnExternalPacket);
+}
+
+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 whitelist;
+ delete socket;
+}
+
+} // namespace talk_base
diff --git a/talk/base/natserver.h b/talk/base/natserver.h
new file mode 100644
index 0000000..0a6083c
--- /dev/null
+++ b/talk/base/natserver.h
@@ -0,0 +1,121 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NATSERVER_H_
+#define TALK_BASE_NATSERVER_H_
+
+#include <map>
+#include <set>
+
+#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"
+
+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 {
+ explicit 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 {
+ explicit 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();
+
+ SocketAddress internal_address() const {
+ return server_socket_->GetLocalAddress();
+ }
+
+ // Packets received on one of the networks.
+ void OnInternalPacket(AsyncPacketSocket* socket, const char* buf,
+ size_t size, const SocketAddress& addr);
+ void OnExternalPacket(AsyncPacketSocket* socket, const char* buf,
+ size_t size, const SocketAddress& remote_addr);
+
+ 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;
+
+ /* 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);
+
+ NAT* nat_;
+ SocketFactory* internal_;
+ SocketFactory* external_;
+ SocketAddress external_ip_;
+ AsyncUDPSocket* server_socket_;
+ AsyncSocket* tcp_server_socket_;
+ InternalMap* int_map_;
+ ExternalMap* ext_map_;
+ DISALLOW_EVIL_CONSTRUCTORS(NATServer);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_NATSERVER_H_
diff --git a/talk/base/natserver_main.cc b/talk/base/natserver_main.cc
new file mode 100644
index 0000000..a748108
--- /dev/null
+++ b/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/talk/base/natsocketfactory.cc b/talk/base/natsocketfactory.cc
new file mode 100644
index 0000000..de2cf2b
--- /dev/null
+++ b/talk/base/natsocketfactory.cc
@@ -0,0 +1,488 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/natsocketfactory.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/virtualsocketserver.h"
+
+namespace talk_base {
+
+// Packs the given socketaddress into the buffer in buf, in the quasi-STUN
+// format that the natserver uses.
+// Returns 0 if an invalid address is passed.
+size_t PackAddressForNAT(char* buf, size_t buf_size,
+ const SocketAddress& remote_addr) {
+ const IPAddress& ip = remote_addr.ipaddr();
+ int family = ip.family();
+ buf[0] = 0;
+ buf[1] = family;
+ // Writes the port.
+ *(reinterpret_cast<uint16*>(&buf[2])) = htons(remote_addr.port());
+ if (family == AF_INET) {
+ ASSERT(buf_size >= kNATEncodedIPv4AddressSize);
+ in_addr v4addr = ip.ipv4_address();
+ std::memcpy(&buf[4], &v4addr, kNATEncodedIPv4AddressSize - 4);
+ return kNATEncodedIPv4AddressSize;
+ } else if (family == AF_INET6) {
+ ASSERT(buf_size >= kNATEncodedIPv6AddressSize);
+ in6_addr v6addr = ip.ipv6_address();
+ std::memcpy(&buf[4], &v6addr, kNATEncodedIPv6AddressSize - 4);
+ return kNATEncodedIPv6AddressSize;
+ }
+ return 0U;
+}
+
+// Decodes the remote address from a packet that has been encoded with the nat's
+// quasi-STUN format. Returns the length of the address (i.e., the offset into
+// data where the original packet starts).
+size_t UnpackAddressFromNAT(const char* buf, size_t buf_size,
+ SocketAddress* remote_addr) {
+ ASSERT(buf_size >= 8);
+ ASSERT(buf[0] == 0);
+ int family = buf[1];
+ uint16 port = ntohs(*(reinterpret_cast<const uint16*>(&buf[2])));
+ if (family == AF_INET) {
+ const in_addr* v4addr = reinterpret_cast<const in_addr*>(&buf[4]);
+ *remote_addr = SocketAddress(IPAddress(*v4addr), port);
+ return kNATEncodedIPv4AddressSize;
+ } else if (family == AF_INET6) {
+ ASSERT(buf_size >= 20);
+ const in6_addr* v6addr = reinterpret_cast<const in6_addr*>(&buf[4]);
+ *remote_addr = SocketAddress(IPAddress(*v6addr), port);
+ return kNATEncodedIPv6AddressSize;
+ }
+ return 0U;
+}
+
+
+// NATSocket
+class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+ explicit NATSocket(NATInternalSocketFactory* sf, int type)
+ : sf_(sf), type_(type), async_(true), connected_(false),
+ socket_(NULL), buf_(NULL), size_(0) {
+ }
+
+ virtual ~NATSocket() {
+ delete socket_;
+ delete[] buf_;
+ }
+
+ virtual SocketAddress GetLocalAddress() const {
+ return (socket_) ? socket_->GetLocalAddress() : SocketAddress();
+ }
+
+ virtual SocketAddress GetRemoteAddress() const {
+ return remote_addr_; // will be ANY if not connected
+ }
+
+ virtual int Bind(const SocketAddress& addr) {
+ if (socket_) { // already bound, bubble up error
+ return -1;
+ }
+
+ int result;
+ socket_ = sf_->CreateInternalSocket(type_, addr, &server_addr_);
+ result = (socket_) ? socket_->Bind(addr) : -1;
+ if (result >= 0) {
+ socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent);
+ socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent);
+ socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent);
+ } else {
+ server_addr_.Clear();
+ delete socket_;
+ socket_ = NULL;
+ }
+
+ return result;
+ }
+
+ virtual int Connect(const SocketAddress& addr) {
+ if (!socket_) { // socket must be bound, for now
+ return -1;
+ }
+
+ int result = 0;
+ if (type_ == SOCK_STREAM) {
+ result = socket_->Connect(server_addr_.IsAny() ? addr : server_addr_);
+ } else {
+ connected_ = true;
+ }
+
+ if (result >= 0) {
+ remote_addr_ = addr;
+ }
+
+ return result;
+ }
+
+ virtual int Send(const void* data, size_t size) {
+ ASSERT(connected_);
+ return SendTo(data, size, remote_addr_);
+ }
+
+ virtual int SendTo(const void* data, size_t size, const SocketAddress& addr) {
+ ASSERT(!connected_ || addr == remote_addr_);
+ if (server_addr_.IsAny() || type_ == SOCK_STREAM) {
+ return socket_->SendTo(data, size, addr);
+ }
+ // This array will be too large for IPv4 packets, but only by 12 bytes.
+ scoped_array<char> buf(new char[size + kNATEncodedIPv6AddressSize]);
+ size_t addrlength = PackAddressForNAT(buf.get(),
+ size + kNATEncodedIPv6AddressSize,
+ addr);
+ size_t encoded_size = size + addrlength;
+ std::memcpy(buf.get() + addrlength, data, size);
+ int result = socket_->SendTo(buf.get(), encoded_size, server_addr_);
+ if (result >= 0) {
+ ASSERT(result == static_cast<int>(encoded_size));
+ result = result - static_cast<int>(addrlength);
+ }
+ return result;
+ }
+
+ virtual int Recv(void* data, size_t size) {
+ SocketAddress addr;
+ return RecvFrom(data, size, &addr);
+ }
+
+ virtual int RecvFrom(void* data, size_t size, SocketAddress *out_addr) {
+ if (server_addr_.IsAny() || type_ == SOCK_STREAM) {
+ return socket_->RecvFrom(data, size, out_addr);
+ }
+ // Make sure we have enough room to read the requested amount plus the
+ // largest possible header address.
+ SocketAddress remote_addr;
+ Grow(size + kNATEncodedIPv6AddressSize);
+
+ // Read the packet from the socket.
+ int result = socket_->RecvFrom(buf_, size_, &remote_addr);
+ if (result >= 0) {
+ ASSERT(remote_addr == server_addr_);
+
+ // TODO: we need better framing so 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 addrlength =
+ UnpackAddressFromNAT(buf_, result, &real_remote_addr);
+ std::memcpy(data, buf_ + addrlength, result - addrlength);
+
+ // Make sure this packet should be delivered before returning it.
+ if (!connected_ || (real_remote_addr == remote_addr_)) {
+ if (out_addr)
+ *out_addr = real_remote_addr;
+ result = result - addrlength;
+ } else {
+ LOG(LS_ERROR) << "Dropping packet from unknown remote address: "
+ << real_remote_addr.ToString();
+ result = 0; // Tell the caller we didn't read anything
+ }
+ }
+
+ return result;
+ }
+
+ virtual int Close() {
+ int result = 0;
+ if (socket_) {
+ result = socket_->Close();
+ if (result >= 0) {
+ connected_ = false;
+ remote_addr_ = SocketAddress();
+ delete socket_;
+ socket_ = NULL;
+ }
+ }
+ return result;
+ }
+
+ virtual int Listen(int backlog) {
+ return socket_->Listen(backlog);
+ }
+ virtual AsyncSocket* Accept(SocketAddress *paddr) {
+ return socket_->Accept(paddr);
+ }
+ virtual int GetError() const {
+ return socket_->GetError();
+ }
+ virtual void SetError(int error) {
+ socket_->SetError(error);
+ }
+ virtual ConnState GetState() const {
+ return connected_ ? CS_CONNECTED : CS_CLOSED;
+ }
+ virtual int EstimateMTU(uint16* mtu) {
+ return socket_->EstimateMTU(mtu);
+ }
+ virtual int GetOption(Option opt, int* value) {
+ return socket_->GetOption(opt, value);
+ }
+ virtual int SetOption(Option opt, int value) {
+ return socket_->SetOption(opt, value);
+ }
+
+ void OnConnectEvent(AsyncSocket* socket) {
+ // If we're NATed, we need to send a request with the real addr to use.
+ ASSERT(socket == socket_);
+ if (server_addr_.IsAny()) {
+ connected_ = true;
+ SignalConnectEvent(this);
+ } else {
+ SendConnectRequest();
+ }
+ }
+ void OnReadEvent(AsyncSocket* socket) {
+ // If we're NATed, we need to process the connect reply.
+ ASSERT(socket == socket_);
+ if (type_ == SOCK_STREAM && !server_addr_.IsAny() && !connected_) {
+ HandleConnectReply();
+ } else {
+ SignalReadEvent(this);
+ }
+ }
+ void OnWriteEvent(AsyncSocket* socket) {
+ ASSERT(socket == socket_);
+ SignalWriteEvent(this);
+ }
+ void OnCloseEvent(AsyncSocket* socket, int error) {
+ ASSERT(socket == socket_);
+ SignalCloseEvent(this, error);
+ }
+
+ 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_];
+ }
+ }
+
+ // Sends the destination address to the server to tell it to connect.
+ void SendConnectRequest() {
+ char buf[256];
+ size_t length = PackAddressForNAT(buf, ARRAY_SIZE(buf), remote_addr_);
+ socket_->Send(buf, length);
+ }
+
+ // Handles the byte sent back from the server and fires the appropriate event.
+ void HandleConnectReply() {
+ char code;
+ socket_->Recv(&code, sizeof(code));
+ if (code == 0) {
+ SignalConnectEvent(this);
+ } else {
+ Close();
+ SignalCloseEvent(this, code);
+ }
+ }
+
+ NATInternalSocketFactory* sf_;
+ int type_;
+ bool async_;
+ bool connected_;
+ SocketAddress remote_addr_;
+ SocketAddress server_addr_; // address of the NAT server
+ AsyncSocket* socket_;
+ char* buf_;
+ size_t size_;
+};
+
+// NATSocketFactory
+NATSocketFactory::NATSocketFactory(SocketFactory* factory,
+ const SocketAddress& nat_addr)
+ : factory_(factory), nat_addr_(nat_addr) {
+}
+
+Socket* NATSocketFactory::CreateSocket(int type) {
+ return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) {
+ return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketFactory::CreateInternalSocket(int type,
+ const SocketAddress& local_addr, SocketAddress* nat_addr) {
+ *nat_addr = nat_addr_;
+ return factory_->CreateAsyncSocket(type);
+}
+
+// NATSocketServer
+NATSocketServer::NATSocketServer(SocketServer* server)
+ : server_(server), msg_queue_(NULL) {
+}
+
+NATSocketServer::Translator* NATSocketServer::GetTranslator(
+ const SocketAddress& ext_ip) {
+ return nats_.Get(ext_ip);
+}
+
+NATSocketServer::Translator* NATSocketServer::AddTranslator(
+ const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) {
+ // Fail if a translator already exists with this extternal address.
+ if (nats_.Get(ext_ip))
+ return NULL;
+
+ return nats_.Add(ext_ip, new Translator(this, type, int_ip, server_, ext_ip));
+}
+
+void NATSocketServer::RemoveTranslator(
+ const SocketAddress& ext_ip) {
+ nats_.Remove(ext_ip);
+}
+
+Socket* NATSocketServer::CreateSocket(int type) {
+ return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketServer::CreateAsyncSocket(int type) {
+ return new NATSocket(this, type);
+}
+
+AsyncSocket* NATSocketServer::CreateInternalSocket(int type,
+ const SocketAddress& local_addr, SocketAddress* nat_addr) {
+ AsyncSocket* socket = NULL;
+ Translator* nat = nats_.FindClient(local_addr);
+ if (nat) {
+ socket = nat->internal_factory()->CreateAsyncSocket(type);
+ *nat_addr = (type == SOCK_STREAM) ?
+ nat->internal_tcp_address() : nat->internal_address();
+ } else {
+ socket = server_->CreateAsyncSocket(type);
+ }
+ return socket;
+}
+
+// NATSocketServer::Translator
+NATSocketServer::Translator::Translator(
+ NATSocketServer* server, NATType type, const SocketAddress& int_ip,
+ SocketFactory* ext_factory, const SocketAddress& ext_ip)
+ : server_(server) {
+ // Create a new private network, and a NATServer running on the private
+ // network that bridges to the external network. Also tell the private
+ // network to use the same message queue as us.
+ VirtualSocketServer* internal_server = new VirtualSocketServer(server_);
+ internal_server->SetMessageQueue(server_->queue());
+ internal_factory_.reset(internal_server);
+ nat_server_.reset(new NATServer(type, internal_server, int_ip,
+ ext_factory, ext_ip));
+}
+
+
+NATSocketServer::Translator* NATSocketServer::Translator::GetTranslator(
+ const SocketAddress& ext_ip) {
+ return nats_.Get(ext_ip);
+}
+
+NATSocketServer::Translator* NATSocketServer::Translator::AddTranslator(
+ const SocketAddress& ext_ip, const SocketAddress& int_ip, NATType type) {
+ // Fail if a translator already exists with this extternal address.
+ if (nats_.Get(ext_ip))
+ return NULL;
+
+ AddClient(ext_ip);
+ return nats_.Add(ext_ip,
+ new Translator(server_, type, int_ip, server_, ext_ip));
+}
+void NATSocketServer::Translator::RemoveTranslator(
+ const SocketAddress& ext_ip) {
+ nats_.Remove(ext_ip);
+ RemoveClient(ext_ip);
+}
+
+bool NATSocketServer::Translator::AddClient(
+ const SocketAddress& int_ip) {
+ // Fail if a client already exists with this internal address.
+ if (clients_.find(int_ip) != clients_.end())
+ return false;
+
+ clients_.insert(int_ip);
+ return true;
+}
+
+void NATSocketServer::Translator::RemoveClient(
+ const SocketAddress& int_ip) {
+ std::set<SocketAddress>::iterator it = clients_.find(int_ip);
+ if (it != clients_.end()) {
+ clients_.erase(it);
+ }
+}
+
+NATSocketServer::Translator* NATSocketServer::Translator::FindClient(
+ const SocketAddress& int_ip) {
+ // See if we have the requested IP, or any of our children do.
+ return (clients_.find(int_ip) != clients_.end()) ?
+ this : nats_.FindClient(int_ip);
+}
+
+// NATSocketServer::TranslatorMap
+NATSocketServer::TranslatorMap::~TranslatorMap() {
+ for (TranslatorMap::iterator it = begin(); it != end(); ++it) {
+ delete it->second;
+ }
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::Get(
+ const SocketAddress& ext_ip) {
+ TranslatorMap::iterator it = find(ext_ip);
+ return (it != end()) ? it->second : NULL;
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::Add(
+ const SocketAddress& ext_ip, Translator* nat) {
+ (*this)[ext_ip] = nat;
+ return nat;
+}
+
+void NATSocketServer::TranslatorMap::Remove(
+ const SocketAddress& ext_ip) {
+ TranslatorMap::iterator it = find(ext_ip);
+ if (it != end()) {
+ delete it->second;
+ erase(it);
+ }
+}
+
+NATSocketServer::Translator* NATSocketServer::TranslatorMap::FindClient(
+ const SocketAddress& int_ip) {
+ Translator* nat = NULL;
+ for (TranslatorMap::iterator it = begin(); it != end() && !nat; ++it) {
+ nat = it->second->FindClient(int_ip);
+ }
+ return nat;
+}
+
+} // namespace talk_base
diff --git a/talk/base/natsocketfactory.h b/talk/base/natsocketfactory.h
new file mode 100644
index 0000000..d0566d3
--- /dev/null
+++ b/talk/base/natsocketfactory.h
@@ -0,0 +1,177 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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 <string>
+#include <map>
+#include <set>
+
+#include "talk/base/natserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+const size_t kNATEncodedIPv4AddressSize = 8U;
+const size_t kNATEncodedIPv6AddressSize = 20U;
+
+// Used by the NAT socket implementation.
+class NATInternalSocketFactory {
+ public:
+ virtual ~NATInternalSocketFactory() {}
+ virtual AsyncSocket* CreateInternalSocket(int type,
+ const SocketAddress& local_addr, SocketAddress* nat_addr) = 0;
+};
+
+// Creates sockets that will send all traffic through a NAT, using an existing
+// NATServer instance running at nat_addr. The actual data is sent using sockets
+// from a socket factory, given to the constructor.
+class NATSocketFactory : public SocketFactory, public NATInternalSocketFactory {
+ public:
+ NATSocketFactory(SocketFactory* factory, const SocketAddress& nat_addr);
+
+ // SocketFactory implementation
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+
+ // NATInternalSocketFactory implementation
+ virtual AsyncSocket* CreateInternalSocket(int type,
+ const SocketAddress& local_addr, SocketAddress* nat_addr);
+
+ private:
+ SocketFactory* factory_;
+ SocketAddress nat_addr_;
+ DISALLOW_EVIL_CONSTRUCTORS(NATSocketFactory);
+};
+
+// Creates sockets that will send traffic through a NAT depending on what
+// address they bind to. This can be used to simulate a client on a NAT sending
+// to a client that is not behind a NAT.
+// Note that the internal addresses of clients must be unique. This is because
+// there is only one socketserver per thread, and the Bind() address is used to
+// figure out which NAT (if any) the socket should talk to.
+//
+// Example with 3 NATs (2 cascaded), and 3 clients.
+// ss->AddTranslator("1.2.3.4", "192.168.0.1", NAT_ADDR_RESTRICTED);
+// ss->AddTranslator("99.99.99.99", "10.0.0.1", NAT_SYMMETRIC)->
+// AddTranslator("10.0.0.2", "192.168.1.1", NAT_OPEN_CONE);
+// ss->GetTranslator("1.2.3.4")->AddClient("1.2.3.4", "192.168.0.2");
+// ss->GetTranslator("99.99.99.99")->AddClient("10.0.0.3");
+// ss->GetTranslator("99.99.99.99")->GetTranslator("10.0.0.2")->
+// AddClient("192.168.1.2");
+class NATSocketServer : public SocketServer, public NATInternalSocketFactory {
+ public:
+ class Translator;
+ // holds a list of NATs
+ class TranslatorMap : private std::map<SocketAddress, Translator*> {
+ public:
+ ~TranslatorMap();
+ Translator* Get(const SocketAddress& ext_ip);
+ Translator* Add(const SocketAddress& ext_ip, Translator*);
+ void Remove(const SocketAddress& ext_ip);
+ Translator* FindClient(const SocketAddress& int_ip);
+ };
+
+ // a specific NAT
+ class Translator {
+ public:
+ Translator(NATSocketServer* server, NATType type,
+ const SocketAddress& int_addr, SocketFactory* ext_factory,
+ const SocketAddress& ext_addr);
+
+ SocketFactory* internal_factory() { return internal_factory_.get(); }
+ SocketAddress internal_address() const {
+ return nat_server_->internal_address();
+ }
+ SocketAddress internal_tcp_address() const {
+ return SocketAddress(); // nat_server_->internal_tcp_address();
+ }
+
+ Translator* GetTranslator(const SocketAddress& ext_ip);
+ Translator* AddTranslator(const SocketAddress& ext_ip,
+ const SocketAddress& int_ip, NATType type);
+ void RemoveTranslator(const SocketAddress& ext_ip);
+
+ bool AddClient(const SocketAddress& int_ip);
+ void RemoveClient(const SocketAddress& int_ip);
+
+ // Looks for the specified client in this or a child NAT.
+ Translator* FindClient(const SocketAddress& int_ip);
+
+ private:
+ NATSocketServer* server_;
+ scoped_ptr<SocketFactory> internal_factory_;
+ scoped_ptr<NATServer> nat_server_;
+ TranslatorMap nats_;
+ std::set<SocketAddress> clients_;
+ };
+
+ explicit NATSocketServer(SocketServer* ss);
+
+ SocketServer* socketserver() { return server_; }
+ MessageQueue* queue() { return msg_queue_; }
+
+ Translator* GetTranslator(const SocketAddress& ext_ip);
+ Translator* AddTranslator(const SocketAddress& ext_ip,
+ const SocketAddress& int_ip, NATType type);
+ void RemoveTranslator(const SocketAddress& ext_ip);
+
+ // SocketServer implementation
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+ virtual void SetMessageQueue(MessageQueue* queue) {
+ msg_queue_ = queue;
+ server_->SetMessageQueue(queue);
+ }
+ virtual bool Wait(int cms, bool process_io) {
+ return server_->Wait(cms, process_io);
+ }
+ virtual void WakeUp() {
+ server_->WakeUp();
+ }
+
+ // NATInternalSocketFactory implementation
+ virtual AsyncSocket* CreateInternalSocket(int type,
+ const SocketAddress& local_addr, SocketAddress* nat_addr);
+
+ private:
+ SocketServer* server_;
+ MessageQueue* msg_queue_;
+ TranslatorMap nats_;
+ DISALLOW_EVIL_CONSTRUCTORS(NATSocketServer);
+};
+
+// Free-standing NAT helper functions.
+size_t PackAddressForNAT(char* buf, size_t buf_size,
+ const SocketAddress& remote_addr);
+size_t UnpackAddressFromNAT(const char* buf, size_t buf_size,
+ SocketAddress* remote_addr);
+} // namespace talk_base
+
+#endif // TALK_BASE_NATSOCKETFACTORY_H_
diff --git a/talk/base/nattypes.cc b/talk/base/nattypes.cc
new file mode 100644
index 0000000..290c3ad
--- /dev/null
+++ b/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/talk/base/nattypes.h b/talk/base/nattypes.h
new file mode 100644
index 0000000..e9602c7
--- /dev/null
+++ b/talk/base/nattypes.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_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:
+ virtual ~NAT() { }
+
+ // 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/talk/base/nethelpers.cc b/talk/base/nethelpers.cc
index b408749..3961fc0 100644
--- a/talk/base/nethelpers.cc
+++ b/talk/base/nethelpers.cc
@@ -57,7 +57,7 @@
}
}
-#if defined(WIN32) || defined(ANDROID)
+#if defined(WIN32) || defined(ANDROID) || defined(OPENBSD)
static hostent* DeepCopyHostent(const hostent* ent) {
// Get the total number of bytes we need to copy, and allocate our buffer.
int num_aliases = 0, num_addrs = 0;
@@ -165,9 +165,16 @@
result = deep_copy;
#endif
*herrno = 0;
-#elif defined(OSX) || defined(IOS)
+#elif defined(OSX) || defined(IOS) || defined(FREEBSD)
// Mac OS returns an object with everything allocated.
result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#elif defined(OPENBSD)
+ hostent* ent = gethostbyname(hostname);
+ if (!ent) {
+ return NULL;
+ }
+ result = DeepCopyHostent(ent);
+ *herrno = 0;
#else
#error "I don't know how to do gethostbyname safely on your system."
#endif
@@ -177,7 +184,7 @@
// This function should mirror the above function, and free any resources
// allocated by the above.
void FreeHostEnt(hostent* host) {
-#if defined(OSX) || defined(IOS)
+#if defined(OSX) || defined(IOS) || defined(FREEBSD)
freehostent(host);
#elif defined(WIN32) || defined(POSIX)
free(host);
@@ -186,4 +193,20 @@
#endif
}
+const char* inet_ntop(int af, const void *src, char* dst, socklen_t size) {
+#ifdef WIN32
+ return win32_inet_ntop(af, src, dst, size);
+#else
+ return ::inet_ntop(af, src, dst, size);
+#endif
+}
+
+int inet_pton(int af, const char* src, void *dst) {
+#ifdef WIN32
+ return win32_inet_pton(af, src, dst);
+#else
+ return ::inet_pton(af, src, dst);
+#endif
+}
+
} // namespace talk_base
diff --git a/talk/base/nethelpers.h b/talk/base/nethelpers.h
index 4cba9a9..ca97738 100644
--- a/talk/base/nethelpers.h
+++ b/talk/base/nethelpers.h
@@ -71,6 +71,11 @@
hostent* SafeGetHostByName(const char* hostname, int* herrno);
void FreeHostEnt(hostent* host);
+// talk_base namespaced wrappers for inet_ntop and inet_pton so we can avoid
+// the windows-native versions of these.
+const char* inet_ntop(int af, const void *src, char* dst, socklen_t size);
+int inet_pton(int af, const char* src, void *dst);
+
} // namespace talk_base
#endif // TALK_BASE_NETHELPERS_H_
diff --git a/talk/base/network.cc b/talk/base/network.cc
index 2feb361..9d1a83d 100644
--- a/talk/base/network.cc
+++ b/talk/base/network.cc
@@ -64,34 +64,6 @@
// Fetch list of networks every two seconds.
const int kNetworksUpdateIntervalMs = 2000;
-#ifdef POSIX
-// Gets the default gateway for the specified interface.
-uint32 GetDefaultGateway(const std::string& name) {
-#ifdef OSX
- // TODO: /proc/net/route doesn't exist,
- // Use ioctl to get the routing table
- return 0xFFFFFFFF;
-#endif
-
- uint32 gateway_ip = 0;
-
- talk_base::FileStream fs;
- if (fs.Open("/proc/net/route", "r", NULL)) {
- std::string line;
- while (fs.ReadLine(&line) == talk_base::SR_SUCCESS && gateway_ip == 0) {
- char iface[16];
- unsigned int ip, gw;
- if (sscanf(line.c_str(), "%7s %8X %8X", iface, &ip, &gw) == 3 &&
- name == iface && ip == 0) {
- gateway_ip = ntohl(gw);
- }
- }
- }
-
- return gateway_ip;
-}
-#endif // POSIX
-
bool CompareNetworks(const Network* a, const Network* b) {
return a->name() < b->name();
}
@@ -146,11 +118,6 @@
network->set_ip(list[i]->ip());
}
- if (network->gateway_ip() != list[i]->gateway_ip()) {
- changed = true;
- network->set_gateway_ip(list[i]->gateway_ip());
- }
-
delete list[i];
}
@@ -199,10 +166,9 @@
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);
+ IPAddress ip(inaddr->sin_addr);
scoped_ptr<Network> network(
- new Network(ptr->ifr_name, ptr->ifr_name, ip,
- GetDefaultGateway(ptr->ifr_name)));
+ new Network(ptr->ifr_name, ptr->ifr_name, ip));
network->set_ignored(IsIgnoredNetwork(*network));
if (include_ignored || !network->ignored()) {
networks->push_back(network.release());
@@ -235,6 +201,8 @@
scoped_array<char> buf(new char[len]);
IP_ADAPTER_INFO *infos = reinterpret_cast<IP_ADAPTER_INFO *>(buf.get());
+ // TODO: GetAdaptersInfo is IPv4 only. Replace with GetAddressesInfo when
+ // IPv6 support is needed in Network.
DWORD ret = GetAdaptersInfo(infos, &len);
if (ret != NO_ERROR) {
LOG_ERR_EX(LS_ERROR, ret) << "GetAdaptersInfo failed";
@@ -259,13 +227,13 @@
name = ost.str();
count++;
#endif // !_DEBUG
-
- scoped_ptr<Network> network(new Network(name, info->Description,
- SocketAddress::StringToIP(info->IpAddressList.IpAddress.String),
- SocketAddress::StringToIP(info->GatewayList.IpAddress.String)));
- network->set_ignored(IsIgnoredNetwork(*network));
- if (include_ignored || !network->ignored()) {
- networks->push_back(network.release());
+ IPAddress ip;
+ if (IPFromString(info->IpAddressList.IpAddress.String, &ip)) {
+ scoped_ptr<Network> network(new Network(name, info->Description, ip));
+ network->set_ignored(IsIgnoredNetwork(*network));
+ if (include_ignored || !network->ignored()) {
+ networks->push_back(network.release());
+ }
}
}
@@ -292,7 +260,10 @@
#endif
// Ignore any networks with a 0.x.y.z IP
- return (network.ip() < 0x01000000);
+ if (network.ip().family() == AF_INET) {
+ return (network.ip().v4AddressAsHostOrderInteger() < 0x01000000);
+ }
+ return false;
}
void BasicNetworkManager::StartUpdating() {
@@ -342,17 +313,15 @@
const Network* network = list[i];
if (!network->ignored() || include_ignored) {
LOG(LS_INFO) << network->ToString() << ": " << network->description()
- << ", Gateway="
- << SocketAddress::IPToString(network->gateway_ip())
<< ((network->ignored()) ? ", Ignored" : "");
}
}
}
Network::Network(const std::string& name, const std::string& desc,
- uint32 ip, uint32 gateway_ip)
- : name_(name), description_(desc), ip_(ip), gateway_ip_(gateway_ip),
- ignored_(false), uniform_numerator_(0), uniform_denominator_(0),
+ const IPAddress& ip)
+ : name_(name), description_(desc), ip_(ip), ignored_(false),
+ uniform_numerator_(0), uniform_denominator_(0),
exponential_numerator_(0), exponential_denominator_(0) {
}
@@ -361,7 +330,7 @@
// Print out the first space-terminated token of the network desc, plus
// the IP address.
ss << "Net[" << description_.substr(0, description_.find(' '))
- << ":" << SocketAddress::IPToString(ip_) << "]";
+ << ":" << ip_ << "]";
return ss.str();
}
diff --git a/talk/base/network.h b/talk/base/network.h
index 4b315c3..bb5f15d 100644
--- a/talk/base/network.h
+++ b/talk/base/network.h
@@ -34,6 +34,7 @@
#include <vector>
#include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
#include "talk/base/messagehandler.h"
#include "talk/base/sigslot.h"
@@ -135,8 +136,10 @@
// Represents a Unix-type network interface, with a name and single address.
class Network {
public:
+ Network() : ip_(INADDR_ANY) {}
+
Network(const std::string& name, const std::string& description,
- uint32 ip, uint32 gateway_ip);
+ const IPAddress& ip);
// Returns the index of this network. This is considered the primary key
// that identifies each network.
@@ -147,15 +150,11 @@
const std::string& description() const { return description_; }
// Identifies the current IP address used by this network.
- uint32 ip() const { return ip_; }
- void set_ip(uint32 ip) { ip_ = ip; }
+ const IPAddress& ip() const { return ip_; }
+ void set_ip(const IPAddress& ip) { ip_ = ip; }
- // Identifies the current gateway IP address used by this network.
- uint32 gateway_ip() const { return gateway_ip_; }
- void set_gateway_ip(uint32 ip) { gateway_ip_ = ip; }
-
- // Indicates whether this network should be ignored, perhaps because the
- // IP/gateway is 0, or the interface is one we know is invalid.
+ // Indicates whether this network should be ignored, perhaps because
+ // the IP is 0, or the interface is one we know is invalid.
bool ignored() const { return ignored_; }
void set_ignored(bool ignored) { ignored_ = ignored; }
@@ -167,8 +166,7 @@
std::string name_;
std::string description_;
- uint32 ip_;
- uint32 gateway_ip_;
+ IPAddress ip_;
bool ignored_;
SessionList sessions_;
double uniform_numerator_;
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
index a55ba2f..d48abbb 100644
--- a/talk/base/network_unittest.cc
+++ b/talk/base/network_unittest.cc
@@ -33,15 +33,10 @@
// A network that should not be ignored.
static const Network kNetwork1("test1", "Test Network Adapter 1",
- 0x12345678, 0x12345601);
+ IPAddress(0x12345678));
// A network that should be ignored (IP is 0.1.0.4).
static const Network kNetwork2("test2", "Test Network Adapter 2",
- 0x00010004, 0x01000000);
-// A network that should not be ignored (IP is valid, but gateway is 0.0.0.1).
-// Previously, we attempted to ignore networks with no default gateway,
-// but if an explicit route is set, no default gateway is needed.
-static const Network kNetwork3("test3", "Test Network Adapter 3",
- 0x55667788, 0x01000000);
+ IPAddress(0x00010004));
class NetworkTest : public testing::Test, public sigslot::has_slots<> {
public:
@@ -63,6 +58,13 @@
return BasicNetworkManager::IsIgnoredNetwork(network);
}
+ NetworkManager::NetworkList GetNetworks(
+ const BasicNetworkManager& network_manager, bool include_ignored) {
+ NetworkManager::NetworkList list;
+ network_manager.CreateNetworks(include_ignored, &list);
+ return list;
+ }
+
protected:
bool callback_called_;
};
@@ -71,8 +73,7 @@
TEST_F(NetworkTest, TestNetworkConstruct) {
EXPECT_EQ("test1", kNetwork1.name());
EXPECT_EQ("Test Network Adapter 1", kNetwork1.description());
- EXPECT_EQ(0x12345678U, kNetwork1.ip());
- EXPECT_EQ(0x12345601U, kNetwork1.gateway_ip());
+ EXPECT_EQ(IPAddress(0x12345678U), kNetwork1.ip());
EXPECT_FALSE(kNetwork1.ignored());
}
@@ -80,7 +81,41 @@
TEST_F(NetworkTest, TestNetworkIgnore) {
EXPECT_FALSE(IsIgnoredNetwork(kNetwork1));
EXPECT_TRUE(IsIgnoredNetwork(kNetwork2));
- EXPECT_FALSE(IsIgnoredNetwork(kNetwork3));
+}
+
+TEST_F(NetworkTest, TestCreateNetworks) {
+ BasicNetworkManager manager;
+ NetworkManager::NetworkList result = GetNetworks(manager, true);
+ // We should be able to bind to any addresses we find.
+ // (Excluding IPv6 link-local for now, as we don't (yet) record scope ids.)
+ NetworkManager::NetworkList::iterator it;
+ for (it = result.begin();
+ it != result.end();
+ ++it) {
+ sockaddr_storage storage;
+ memset(&storage, 0, sizeof(storage));
+ IPAddress ip = (*it)->ip();
+ // This condition excludes FE80::/16, i.e. IPv6 link-local addresses. These
+ // require their scope id to be known. Remove when scope ids are supported.
+ if (!(ip.family() == AF_INET6 && IPIsPrivate(ip) && !IPIsLoopback(ip))) {
+ SocketAddress bindaddress(ip, 0);
+ // TODO: Make this use talk_base::AsyncSocket once it supports IPv6.
+ int fd = socket(ip.family(), SOCK_STREAM, IPPROTO_TCP);
+ if (fd > 0) {
+ size_t ipsize = bindaddress.ToSockAddrStorage(&storage);
+ EXPECT_GE(ipsize, 0U);
+ int success = ::bind(fd,
+ reinterpret_cast<sockaddr*>(&storage),
+ ipsize);
+ EXPECT_EQ(0, success);
+#ifdef WIN32
+ closesocket(fd);
+#else
+ close(fd);
+#endif
+ }
+ }
+ }
}
// Test that UpdateNetworks succeeds.
diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc
index 4208b79..ea40172 100644
--- a/talk/base/openssladapter.cc
+++ b/talk/base/openssladapter.cc
@@ -747,6 +747,7 @@
X509_free(certificate);
+ // This should only ever be turned on for debugging and development.
if (!ok && ignore_bad_cert) {
LOG(LS_WARNING) << "TLS certificate check FAILED. "
<< "Allowing connection anyway.";
@@ -840,6 +841,7 @@
}
}
+ // Should only be used for debugging and development.
if (!ok && stream->ignore_bad_cert()) {
LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
ok = 1;
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
index a9d94b2..68b113b 100644
--- a/talk/base/opensslidentity.cc
+++ b/talk/base/opensslidentity.cc
@@ -147,6 +147,7 @@
static void LogSSLErrors(const std::string& prefix) {
char error_buf[200];
unsigned long err;
+
while ((err = ERR_get_error())) {
ERR_error_string_n(err, error_buf, sizeof(error_buf));
LOG(LS_ERROR) << prefix << ": " << error_buf << "\n";
@@ -224,6 +225,74 @@
return NULL;
}
+bool OpenSSLCertificate::GetDigestLength(const std::string &algorithm,
+ std::size_t *length) {
+ const EVP_MD *md;
+
+ if (!GetDigestEVP(algorithm, &md))
+ return false;
+
+ *length = EVP_MD_size(md);
+
+ return true;
+}
+
+bool OpenSSLCertificate::ComputeDigest(const std::string &algorithm,
+ unsigned char *digest,
+ std::size_t size,
+ std::size_t *length) const {
+ return ComputeDigest(x509_, algorithm, digest, size, length);
+}
+
+bool OpenSSLCertificate::ComputeDigest(const X509 *x509,
+ const std::string &algorithm,
+ unsigned char *digest,
+ std::size_t size,
+ std::size_t *length) {
+ const EVP_MD *md;
+ unsigned int n;
+
+ if (!GetDigestEVP(algorithm, &md))
+ return false;
+
+ if (size < static_cast<size_t>(EVP_MD_size(md)))
+ return false;
+
+ X509_digest(x509, md, digest, &n);
+
+ *length = n;
+
+ return true;
+}
+
+
+bool OpenSSLCertificate::GetDigestEVP(const std::string &algorithm,
+ const EVP_MD **mdp) {
+ const EVP_MD *md;
+ if (algorithm == DIGEST_SHA_1) {
+ md = EVP_sha1();
+ }
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ else if (algorithm == DIGEST_SHA_224) {
+ md = EVP_sha224();
+ } else if (algorithm == DIGEST_SHA_256) {
+ md = EVP_sha256();
+ } else if (algorithm == DIGEST_SHA_384) {
+ md = EVP_sha384();
+ } else if (algorithm == DIGEST_SHA_512) {
+ md = EVP_sha512();
+ }
+#endif
+ else {
+ return false;
+ }
+
+ // Can't happen
+ ASSERT(EVP_MD_size(md) >= 20);
+ *mdp = md;
+ return true;
+}
+
OpenSSLCertificate::~OpenSSLCertificate() {
X509_free(x509_);
}
diff --git a/talk/base/opensslidentity.h b/talk/base/opensslidentity.h
index 7cac419..f642736 100644
--- a/talk/base/opensslidentity.h
+++ b/talk/base/opensslidentity.h
@@ -87,6 +87,22 @@
virtual std::string ToPEMString() const;
+ // Compute the digest of the certificate given algorithm
+ virtual bool ComputeDigest(const std::string &algorithm,
+ unsigned char *digest, std::size_t size,
+ std::size_t *length) const;
+
+ // Compute the digest of a certificate as an X509 *
+ static bool ComputeDigest(const X509 *x509,
+ const std::string &algorithm,
+ unsigned char *digest,
+ std::size_t size,
+ std::size_t *length);
+
+ // Helper function to get the length of a digest
+ static bool GetDigestLength(const std::string &algorithm,
+ std::size_t *length);
+
private:
explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
ASSERT(x509_ != NULL);
@@ -95,6 +111,10 @@
X509* x509_;
+ // Helper function to look up a digest
+ static bool GetDigestEVP(const std::string &algorithm,
+ const EVP_MD **md);
+
DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate);
};
@@ -132,6 +152,7 @@
DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity);
};
+
} // namespace talk_base
#endif // TALK_BASE_OPENSSLIDENTITY_H__
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
index 6a34e13..be061ad 100644
--- a/talk/base/opensslstreamadapter.cc
+++ b/talk/base/opensslstreamadapter.cc
@@ -40,15 +40,41 @@
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
+#include <vector>
+
#include "talk/base/common.h"
#include "talk/base/logging.h"
#include "talk/base/stream.h"
#include "talk/base/openssladapter.h"
#include "talk/base/opensslidentity.h"
#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
namespace talk_base {
+#if (OPENSSL_VERSION_NUMBER >= 0x10001000L)
+#define HAVE_DTLS_SRTP
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+#define HAVE_DTLS
+#endif
+
+#ifdef HAVE_DTLS_SRTP
+// SRTP cipher suite table
+struct SrtpCipherMapEntry {
+ const char* external_name;
+ const char* internal_name;
+};
+
+// This isn't elegant, but it's better than an external reference
+static SrtpCipherMapEntry SrtpCipherMap[] = {
+ {"AES_CM_128_HMAC_SHA1_80", "SRTP_AES128_CM_SHA1_80"},
+ {"AES_CM_128_HMAC_SHA1_32", "SRTP_AES128_CM_SHA1_32"},
+ {NULL, NULL}
+};
+#endif
+
//////////////////////////////////////////////////////////////////////
// StreamBIO
//////////////////////////////////////////////////////////////////////
@@ -166,7 +192,8 @@
role_(SSL_CLIENT),
ssl_read_needs_write_(false), ssl_write_needs_read_(false),
ssl_(NULL), ssl_ctx_(NULL),
- custom_verification_succeeded_(false) {
+ custom_verification_succeeded_(false),
+ ssl_mode_(SSL_MODE_TLS) {
}
OpenSSLStreamAdapter::~OpenSSLStreamAdapter() {
@@ -178,16 +205,130 @@
identity_.reset(static_cast<OpenSSLIdentity*>(identity));
}
-void OpenSSLStreamAdapter::SetServerRole() {
- role_ = SSL_SERVER;
+void OpenSSLStreamAdapter::SetServerRole(SSLRole role) {
+ role_ = role;
}
void OpenSSLStreamAdapter::SetPeerCertificate(SSLCertificate* cert) {
ASSERT(peer_certificate_.get() == NULL);
+ ASSERT(peer_certificate_digest_algorithm_.empty());
ASSERT(ssl_server_name_.empty());
peer_certificate_.reset(static_cast<OpenSSLCertificate*>(cert));
}
+bool OpenSSLStreamAdapter::SetPeerCertificateDigest(const std::string
+ &digest_alg,
+ const unsigned char*
+ digest_val,
+ size_t digest_len) {
+ ASSERT(peer_certificate_.get() == NULL);
+ ASSERT(peer_certificate_digest_algorithm_.size() == 0);
+ ASSERT(ssl_server_name_.empty());
+ size_t expected_len;
+
+ if (!OpenSSLCertificate::GetDigestLength(digest_alg, &expected_len)) {
+ LOG(LS_WARNING) << "Unknown digest algorithm: " << digest_alg;
+ return false;
+ }
+ if (expected_len != digest_len)
+ return false;
+
+ peer_certificate_digest_value_.SetData(digest_val, digest_len);
+ peer_certificate_digest_algorithm_ = digest_alg;
+
+ return true;
+}
+
+// Key Extractor interface
+bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+#ifdef HAVE_DTLS_SRTP
+ int i;
+
+ i = SSL_export_keying_material(ssl_, result, result_len,
+ label.c_str(), label.length(),
+ const_cast<uint8 *>(context),
+ context_len, use_context);
+
+ if (i != 1)
+ return false;
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::SetDtlsSrtpCiphers(
+ const std::vector<std::string>& ciphers) {
+ std::string internal_ciphers;
+
+ if (state_ != SSL_NONE)
+ return false;
+
+#ifdef HAVE_DTLS_SRTP
+ for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
+ cipher != ciphers.end(); ++cipher) {
+ bool found = false;
+ for (SrtpCipherMapEntry *entry = SrtpCipherMap; entry->internal_name;
+ ++entry) {
+ if (*cipher == entry->external_name) {
+ found = true;
+ if (!internal_ciphers.empty())
+ internal_ciphers += ":";
+ internal_ciphers += entry->internal_name;
+ break;
+ }
+ }
+
+ if (!found) {
+ LOG(LS_ERROR) << "Could not find cipher: " << *cipher;
+ return false;
+ }
+ }
+
+ if (internal_ciphers.empty())
+ return false;
+
+ srtp_ciphers_ = internal_ciphers;
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
+#ifdef HAVE_DTLS_SRTP
+ ASSERT(state_ == SSL_CONNECTED);
+ if (state_ != SSL_CONNECTED)
+ return false;
+
+ SRTP_PROTECTION_PROFILE *srtp_profile =
+ SSL_get_selected_srtp_profile(ssl_);
+
+ if (!srtp_profile)
+ return NULL;
+
+ for (SrtpCipherMapEntry *entry = SrtpCipherMap;
+ entry->internal_name; ++entry) {
+ if (!strcmp(entry->internal_name, srtp_profile->name)) {
+ *cipher = entry->external_name;
+ return true;
+ }
+ }
+
+ ASSERT(false); // This should never happen
+
+ return false;
+#else
+ return false;
+#endif
+}
+
int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) {
ASSERT(server_name != NULL && server_name[0] != '\0');
ssl_server_name_ = server_name;
@@ -200,6 +341,11 @@
return StartSSL();
}
+void OpenSSLStreamAdapter::SetMode(SSLMode mode) {
+ ASSERT(state_ == SSL_NONE);
+ ssl_mode_ = mode;
+}
+
//
// StreamInterface Implementation
//
@@ -238,7 +384,8 @@
ssl_write_needs_read_ = false;
int code = SSL_write(ssl_, data, data_len);
- switch (SSL_get_error(ssl_, code)) {
+ int ssl_error = SSL_get_error(ssl_, code);
+ switch (ssl_error) {
case SSL_ERROR_NONE:
LOG(LS_INFO) << " -- success";
ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
@@ -255,7 +402,7 @@
case SSL_ERROR_ZERO_RETURN:
default:
- Error("SSL_write", (code ? code : -1), false);
+ Error("SSL_write", (ssl_error ? ssl_error : -1), false);
if (error)
*error = ssl_error_code_;
return SR_ERROR;
@@ -298,12 +445,26 @@
ssl_read_needs_write_ = false;
int code = SSL_read(ssl_, data, data_len);
- switch (SSL_get_error(ssl_, code)) {
+ int ssl_error = SSL_get_error(ssl_, code);
+ switch (ssl_error) {
case SSL_ERROR_NONE:
LOG(LS_INFO) << " -- success";
ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
if (read)
*read = code;
+
+ if (ssl_mode_ == SSL_MODE_DTLS) {
+ // Enforce atomic reads -- this is a short read
+ unsigned int pending = SSL_pending(ssl_);
+
+ if (pending) {
+ LOG(LS_INFO) << " -- short DTLS read. flushing";
+ FlushInput(pending);
+ if (error)
+ *error = SSE_MSG_TRUNC;
+ return SR_ERROR;
+ }
+ }
return SR_SUCCESS;
case SSL_ERROR_WANT_READ:
LOG(LS_INFO) << " -- error want read";
@@ -318,7 +479,7 @@
break;
default:
LOG(LS_INFO) << " -- error " << code;
- Error("SSL_read", (code ? code : -1), false);
+ Error("SSL_read", (ssl_error ? ssl_error : -1), false);
if (error)
*error = ssl_error_code_;
return SR_ERROR;
@@ -326,6 +487,27 @@
// not reached
}
+void OpenSSLStreamAdapter::FlushInput(unsigned int left) {
+ unsigned char buf[2048];
+
+ while (left) {
+ // This should always succeed
+ int toread = (sizeof(buf) < left) ? sizeof(buf) : left;
+ int code = SSL_read(ssl_, buf, toread);
+
+ int ssl_error = SSL_get_error(ssl_, code);
+ ASSERT(ssl_error == SSL_ERROR_NONE);
+
+ if (ssl_error != SSL_ERROR_NONE) {
+ LOG(LS_INFO) << " -- error " << code;
+ Error("SSL_read", (ssl_error ? ssl_error : -1), false);
+ return;
+ }
+ LOG(LS_INFO) << " -- flushed " << code << " bytes";
+ left -= code;
+ }
+}
+
void OpenSSLStreamAdapter::Close() {
Cleanup();
ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
@@ -333,7 +515,7 @@
}
StreamState OpenSSLStreamAdapter::GetState() const {
- switch(state_) {
+ switch (state_) {
case SSL_WAIT:
case SSL_CONNECTING:
return SS_OPENING;
@@ -395,7 +577,7 @@
ASSERT(signal_error == 0);
signal_error = err;
}
- if(events_to_signal)
+ if (events_to_signal)
StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
}
@@ -420,7 +602,9 @@
ASSERT(state_ == SSL_CONNECTING);
// The underlying stream has open. If we are in peer-to-peer mode
// then a peer certificate must have been specified by now.
- ASSERT(!ssl_server_name_.empty() || peer_certificate_.get() != NULL);
+ ASSERT(!ssl_server_name_.empty() ||
+ peer_certificate_.get() != NULL ||
+ !peer_certificate_digest_algorithm_.empty());
LOG(LS_INFO) << "BeginSSL: "
<< (!ssl_server_name_.empty() ? ssl_server_name_ :
"with peer");
@@ -458,14 +642,19 @@
LOG(LS_INFO) << "ContinueSSL";
ASSERT(state_ == SSL_CONNECTING);
+ // Clear the DTLS timer
+ Thread::Current()->Clear(this, MSG_TIMEOUT);
+
int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_);
- switch (SSL_get_error(ssl_, code)) {
+ int ssl_error;
+ switch (ssl_error = SSL_get_error(ssl_, code)) {
case SSL_ERROR_NONE:
LOG(LS_INFO) << " -- success";
if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(),
peer_certificate_.get() != NULL
- ? peer_certificate_->x509() : NULL)) {
+ ? peer_certificate_->x509() : NULL,
+ peer_certificate_digest_algorithm_)) {
LOG(LS_ERROR) << "TLS post connection check failed";
return -1;
}
@@ -474,8 +663,17 @@
StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
break;
- case SSL_ERROR_WANT_READ:
- LOG(LS_INFO) << " -- error want read";
+ case SSL_ERROR_WANT_READ: {
+ LOG(LS_INFO) << " -- error want read";
+#ifdef HAVE_DTLS
+ struct timeval timeout;
+ if (DTLSv1_get_timeout(ssl_, &timeout)) {
+ int delay = timeout.tv_sec * 1000 + timeout.tv_usec/1000;
+
+ Thread::Current()->PostDelayed(delay, this, MSG_TIMEOUT, 0);
+ }
+#endif
+ }
break;
case SSL_ERROR_WANT_WRITE:
@@ -485,7 +683,7 @@
case SSL_ERROR_ZERO_RETURN:
default:
LOG(LS_INFO) << " -- error " << code;
- return (code != 0) ? code : -1;
+ return (ssl_error != 0) ? ssl_error : -1;
}
return 0;
@@ -519,11 +717,43 @@
}
identity_.reset();
peer_certificate_.reset();
+
+ // Clear the DTLS timer
+ Thread::Current()->Clear(this, MSG_TIMEOUT);
+}
+
+
+void OpenSSLStreamAdapter::OnMessage(Message* msg) {
+ // Process our own messages and then pass others to the superclass
+ if (MSG_TIMEOUT == msg->message_id) {
+ LOG(LS_INFO) << "DTLS timeout expired";
+#ifdef HAVE_DTLS
+ DTLSv1_handle_timeout(ssl_);
+#endif
+ ContinueSSL();
+ } else {
+ StreamInterface::OnMessage(msg);
+ }
}
SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() {
- SSL_CTX* ctx = SSL_CTX_new(role_ == SSL_CLIENT ? TLSv1_client_method()
- : TLSv1_server_method());
+ SSL_CTX *ctx = NULL;
+
+ if (role_ == SSL_CLIENT) {
+#ifdef HAVE_DTLS
+ ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
+ DTLSv1_client_method() : TLSv1_client_method());
+#else
+ ctx = SSL_CTX_new(TLSv1_client_method());
+#endif
+ } else {
+#ifdef HAVE_DTLS
+ ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
+ DTLSv1_server_method() : TLSv1_server_method());
+#else
+ ctx = SSL_CTX_new(TLSv1_server_method());
+#endif
+ }
if (ctx == NULL)
return NULL;
@@ -534,7 +764,7 @@
if (peer_certificate_.get() == NULL) { // traditional mode
// Add the root cert to the SSL context
- if(!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
+ if (!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
SSL_CTX_free(ctx);
return NULL;
}
@@ -553,6 +783,15 @@
SSL_CTX_set_verify_depth(ctx, 4);
SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+#ifdef HAVE_DTLS_SRTP
+ if (!srtp_ciphers_.empty()) {
+ if (SSL_CTX_set_tlsext_use_srtp(ctx, srtp_ciphers_.c_str())) {
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+ }
+#endif
+
return ctx;
}
@@ -596,6 +835,29 @@
LOG(LS_INFO) << "Accepted self-signed peer certificate authority";
ok = 1;
}
+ } else if (!ok && !stream->peer_certificate_digest_algorithm_.empty()) {
+ X509* cert = X509_STORE_CTX_get_current_cert(store);
+ int err = X509_STORE_CTX_get_error(store);
+
+ // peer-to-peer mode: allow the certificate to be self-signed,
+ // assuming it matches the digest that was specified.
+ if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ std::size_t digest_length;
+
+ if (OpenSSLCertificate::
+ ComputeDigest(cert,
+ stream->peer_certificate_digest_algorithm_,
+ digest, sizeof(digest),
+ &digest_length)) {
+ Buffer computed_digest(digest, digest_length);
+ if (computed_digest == stream->peer_certificate_digest_value_) {
+ LOG(LS_INFO) <<
+ "Accepted self-signed peer certificate authority";
+ ok = 1;
+ }
+ }
+ }
} else if (!ok && OpenSSLAdapter::custom_verify_callback_) {
// this applies only in traditional mode
void* cert =
@@ -619,10 +881,12 @@
// sample in chapter 5
bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl,
const char* server_name,
- const X509* peer_cert) {
+ const X509* peer_cert,
+ const std::string
+ &peer_digest) {
ASSERT(server_name != NULL);
bool ok;
- if(server_name[0] != '\0') { // traditional mode
+ if (server_name[0] != '\0') { // traditional mode
ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert());
if (ok) {
@@ -630,7 +894,7 @@
custom_verification_succeeded_);
}
} else { // peer-to-peer mode
- ASSERT(peer_cert != NULL);
+ ASSERT((peer_cert != NULL) || (!peer_digest.empty()));
// no server name validation
ok = true;
}
@@ -645,6 +909,30 @@
return ok;
}
+bool OpenSSLStreamAdapter::HaveDtls() {
+#ifdef HAVE_DTLS
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::HaveDtlsSrtp() {
+#ifdef HAVE_DTLS_SRTP
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::HaveExporter() {
+#ifdef HAVE_DTLS_SRTP
+ return true;
+#else
+ return false;
+#endif
+}
+
} // namespace talk_base
#endif // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/opensslstreamadapter.h b/talk/base/opensslstreamadapter.h
index 16ec751..8e92a10 100644
--- a/talk/base/opensslstreamadapter.h
+++ b/talk/base/opensslstreamadapter.h
@@ -29,6 +29,9 @@
#define TALK_BASE_OPENSSLSTREAMADAPTER_H__
#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
#include "talk/base/sslstreamadapter.h"
#include "talk/base/opensslidentity.h"
@@ -75,11 +78,17 @@
virtual ~OpenSSLStreamAdapter();
virtual void SetIdentity(SSLIdentity* identity);
- virtual void SetServerRole();
+
+ // Default argument is for compatibility
+ virtual void SetServerRole(SSLRole role = SSL_SERVER);
virtual void SetPeerCertificate(SSLCertificate* cert);
+ virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
+ const unsigned char* digest_val,
+ size_t digest_len);
virtual int StartSSLWithServer(const char* server_name);
virtual int StartSSLWithPeer();
+ virtual void SetMode(SSLMode mode);
virtual StreamResult Read(void* data, size_t data_len,
size_t* read, int* error);
@@ -88,6 +97,24 @@
virtual void Close();
virtual StreamState GetState() const;
+ // Key Extractor interface
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len);
+
+
+ // DTLS-SRTP interface
+ virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers);
+ virtual bool GetDtlsSrtpCipher(std::string* cipher);
+
+ // Capabilities interfaces
+ static bool HaveDtls();
+ static bool HaveDtlsSrtp();
+ static bool HaveExporter();
+
protected:
virtual void OnEvent(StreamInterface* stream, int events, int err);
@@ -102,9 +129,8 @@
SSL_ERROR, // some SSL error occurred, stream is closed
SSL_CLOSED // Clean close
};
- enum SSLRole {
- SSL_CLIENT, SSL_SERVER
- };
+
+ enum { MSG_TIMEOUT = MSG_MAX+1};
// The following three methods return 0 on success and a negative
// error code on failure. The error code may be from OpenSSL or -1
@@ -129,11 +155,18 @@
void Error(const char* context, int err, bool signal);
void Cleanup();
+ // Override MessageHandler
+ virtual void OnMessage(Message* msg);
+
+ // Flush the input buffers by reading left bytes (for DTLS)
+ void FlushInput(unsigned int left);
+
// SSL library configuration
SSL_CTX* SetupSSLContext();
// SSL verification check
bool SSLPostConnectionCheck(SSL* ssl, const char* server_name,
- const X509* peer_cert);
+ const X509* peer_cert,
+ const std::string& peer_digest);
// SSL certification verification error handler, called back from
// the openssl library. Returns an int interpreted as a boolean in
// the C style: zero means verification failure, non-zero means
@@ -151,17 +184,28 @@
SSL* ssl_;
SSL_CTX* ssl_ctx_;
- // in traditional mode, the server name that the server's certificate
- // must specify. Empty in peer-to-peer mode.
+
// Our key and certificate, mostly useful in peer-to-peer mode.
scoped_ptr<OpenSSLIdentity> identity_;
+ // in traditional mode, the server name that the server's certificate
+ // must specify. Empty in peer-to-peer mode.
std::string ssl_server_name_;
// In peer-to-peer mode, the certificate that the peer must
// present. Empty in traditional mode.
scoped_ptr<OpenSSLCertificate> peer_certificate_;
+ // In peer-to-peer mode, the digest of the certificate that
+ // the peer must present.
+ Buffer peer_certificate_digest_value_;
+ std::string peer_certificate_digest_algorithm_;
// OpenSSLAdapter::custom_verify_callback_ result
bool custom_verification_succeeded_;
+
+ // The DtlsSrtp ciphers
+ std::string srtp_ciphers_;
+
+ // Do DTLS or not
+ SSLMode ssl_mode_;
};
/////////////////////////////////////////////////////////////////////////////
diff --git a/talk/base/optionsfile.cc b/talk/base/optionsfile.cc
new file mode 100644
index 0000000..82a5c86
--- /dev/null
+++ b/talk/base/optionsfile.cc
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/optionsfile.h"
+
+#include <ctype.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+OptionsFile::OptionsFile(const std::string &path) : path_(path) {
+}
+
+bool OptionsFile::Load() {
+ options_.clear();
+ // Open file.
+ FileStream stream;
+ int err;
+ if (!stream.Open(path_, "r", &err)) {
+ LOG_F(LS_WARNING) << "Could not open file, err=" << err;
+ // We do not consider this an error because we expect there to be no file
+ // until the user saves a setting.
+ return true;
+ }
+ // Read in all its data.
+ std::string line;
+ StreamResult res;
+ for (;;) {
+ res = stream.ReadLine(&line);
+ if (res != SR_SUCCESS) {
+ break;
+ }
+ size_t equals_pos = line.find('=');
+ if (equals_pos == std::string::npos) {
+ // We do not consider this an error. Instead we ignore the line and
+ // keep going.
+ LOG_F(LS_WARNING) << "Ignoring malformed line in " << path_;
+ continue;
+ }
+ std::string key(line, 0, equals_pos);
+ std::string value(line, equals_pos + 1, line.length() - (equals_pos + 1));
+ options_[key] = value;
+ }
+ if (res != SR_EOS) {
+ LOG_F(LS_ERROR) << "Error when reading from file";
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool OptionsFile::Save() {
+ // Open file.
+ FileStream stream;
+ int err;
+ if (!stream.Open(path_, "w", &err)) {
+ LOG_F(LS_ERROR) << "Could not open file, err=" << err;
+ return false;
+ }
+ // Write out all the data.
+ StreamResult res = SR_SUCCESS;
+ size_t written;
+ int error;
+ for (OptionsMap::const_iterator i = options_.begin(); i != options_.end();
+ ++i) {
+ res = stream.WriteAll(i->first.c_str(), i->first.length(), &written,
+ &error);
+ if (res != SR_SUCCESS) {
+ break;
+ }
+ res = stream.WriteAll("=", 1, &written, &error);
+ if (res != SR_SUCCESS) {
+ break;
+ }
+ res = stream.WriteAll(i->second.c_str(), i->second.length(), &written,
+ &error);
+ if (res != SR_SUCCESS) {
+ break;
+ }
+ res = stream.WriteAll("\n", 1, &written, &error);
+ if (res != SR_SUCCESS) {
+ break;
+ }
+ }
+ if (res != SR_SUCCESS) {
+ LOG_F(LS_ERROR) << "Unable to write to file";
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool OptionsFile::IsLegalName(const std::string &name) {
+ for (size_t pos = 0; pos < name.length(); ++pos) {
+ if (name[pos] == '\n' || name[pos] == '\\' || name[pos] == '=') {
+ // Illegal character.
+ LOG(LS_WARNING) << "Ignoring operation for illegal option " << name;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool OptionsFile::IsLegalValue(const std::string &value) {
+ for (size_t pos = 0; pos < value.length(); ++pos) {
+ if (value[pos] == '\n' || value[pos] == '\\') {
+ // Illegal character.
+ LOG(LS_WARNING) << "Ignoring operation for illegal value " << value;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool OptionsFile::GetStringValue(const std::string& option,
+ std::string *out_val) const {
+ LOG(LS_VERBOSE) << "OptionsFile::GetStringValue "
+ << option;
+ if (!IsLegalName(option)) {
+ return false;
+ }
+ OptionsMap::const_iterator i = options_.find(option);
+ if (i == options_.end()) {
+ return false;
+ }
+ *out_val = i->second;
+ return true;
+}
+
+bool OptionsFile::GetIntValue(const std::string& option,
+ int *out_val) const {
+ LOG(LS_VERBOSE) << "OptionsFile::GetIntValue "
+ << option;
+ if (!IsLegalName(option)) {
+ return false;
+ }
+ OptionsMap::const_iterator i = options_.find(option);
+ if (i == options_.end()) {
+ return false;
+ }
+ return FromString(i->second, out_val);
+}
+
+bool OptionsFile::SetStringValue(const std::string& option,
+ const std::string& value) {
+ LOG(LS_VERBOSE) << "OptionsFile::SetStringValue "
+ << option << ":" << value;
+ if (!IsLegalName(option) || !IsLegalValue(value)) {
+ return false;
+ }
+ options_[option] = value;
+ return true;
+}
+
+bool OptionsFile::SetIntValue(const std::string& option,
+ int value) {
+ LOG(LS_VERBOSE) << "OptionsFile::SetIntValue "
+ << option << ":" << value;
+ if (!IsLegalName(option)) {
+ return false;
+ }
+ return ToString(value, &options_[option]);
+}
+
+bool OptionsFile::RemoveValue(const std::string& option) {
+ LOG(LS_VERBOSE) << "OptionsFile::RemoveValue " << option;
+ if (!IsLegalName(option)) {
+ return false;
+ }
+ options_.erase(option);
+ return true;
+}
+
+} // namespace talk_base
diff --git a/talk/base/optionsfile.h b/talk/base/optionsfile.h
new file mode 100644
index 0000000..9e5f457
--- /dev/null
+++ b/talk/base/optionsfile.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPTIONSFILE_H_
+#define TALK_BASE_OPTIONSFILE_H_
+
+#include <map>
+#include <string>
+
+namespace talk_base {
+
+// Implements storage of simple options in a text file on disk. This is
+// cross-platform, but it is intended mostly for Linux where there is no
+// first-class options storage system.
+class OptionsFile {
+ public:
+ OptionsFile(const std::string &path);
+
+ // Loads the file from disk, overwriting the in-memory values.
+ bool Load();
+ // Saves the contents in memory, overwriting the on-disk values.
+ bool Save();
+
+ bool GetStringValue(const std::string& option, std::string* out_val) const;
+ bool GetIntValue(const std::string& option, int* out_val) const;
+ bool SetStringValue(const std::string& option, const std::string& val);
+ bool SetIntValue(const std::string& option, int val);
+ bool RemoveValue(const std::string& option);
+
+ private:
+ typedef std::map<std::string, std::string> OptionsMap;
+
+ static bool IsLegalName(const std::string &name);
+ static bool IsLegalValue(const std::string &value);
+
+ std::string path_;
+ OptionsMap options_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPTIONSFILE_H_
diff --git a/talk/base/optionsfile_unittest.cc b/talk/base/optionsfile_unittest.cc
new file mode 100644
index 0000000..65861ff
--- /dev/null
+++ b/talk/base/optionsfile_unittest.cc
@@ -0,0 +1,178 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/optionsfile.h"
+
+namespace talk_base {
+
+#ifdef ANDROID
+static const char *kTestFile = "/sdcard/.testfile";
+#elif CHROMEOS
+static const char *kTestFile = "/tmp/.testfile";
+#else
+static const char *kTestFile = ".testfile";
+#endif
+
+static const std::string kTestOptionA = "test-option-a";
+static const std::string kTestOptionB = "test-option-b";
+static const std::string kTestString1 = "a string";
+static const std::string kTestString2 = "different string";
+static const std::string kOptionWithEquals = "foo=bar";
+static const std::string kOptionWithNewline = "foo\nbar";
+static const std::string kValueWithEquals = "baz=quux";
+static const std::string kValueWithNewline = "baz\nquux";
+static const std::string kEmptyString = "";
+static const char kOptionWithUtf8[] = {'O', 'p', 't', '\302', '\256', 'i', 'o',
+ 'n', '\342', '\204', '\242', '\0'}; // Opt(R)io(TM).
+static const char kValueWithUtf8[] = {'V', 'a', 'l', '\302', '\256', 'v', 'e',
+ '\342', '\204', '\242', '\0'}; // Val(R)ue(TM).
+static int kTestInt1 = 12345;
+static int kTestInt2 = 67890;
+static int kNegInt = -634;
+static int kZero = 0;
+
+TEST(OptionsFile, GetSetString) {
+ OptionsFile store(kTestFile);
+ // Clear contents of the file on disk.
+ EXPECT_TRUE(store.Save());
+ std::string out1, out2;
+ EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1));
+ EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2));
+ EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.SetStringValue(kTestOptionB, kTestString2));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1));
+ EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out2));
+ EXPECT_EQ(kTestString1, out1);
+ EXPECT_EQ(kTestString2, out2);
+ EXPECT_TRUE(store.RemoveValue(kTestOptionA));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.RemoveValue(kTestOptionB));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_FALSE(store.GetStringValue(kTestOptionA, &out1));
+ EXPECT_FALSE(store.GetStringValue(kTestOptionB, &out2));
+}
+
+TEST(OptionsFile, GetSetInt) {
+ OptionsFile store(kTestFile);
+ // Clear contents of the file on disk.
+ EXPECT_TRUE(store.Save());
+ int out1, out2;
+ EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1));
+ EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2));
+ EXPECT_TRUE(store.SetIntValue(kTestOptionA, kTestInt1));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.SetIntValue(kTestOptionB, kTestInt2));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+ EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2));
+ EXPECT_EQ(kTestInt1, out1);
+ EXPECT_EQ(kTestInt2, out2);
+ EXPECT_TRUE(store.RemoveValue(kTestOptionA));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.RemoveValue(kTestOptionB));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_FALSE(store.GetIntValue(kTestOptionA, &out1));
+ EXPECT_FALSE(store.GetIntValue(kTestOptionB, &out2));
+ EXPECT_TRUE(store.SetIntValue(kTestOptionA, kNegInt));
+ EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+ EXPECT_EQ(kNegInt, out1);
+ EXPECT_TRUE(store.SetIntValue(kTestOptionA, kZero));
+ EXPECT_TRUE(store.GetIntValue(kTestOptionA, &out1));
+ EXPECT_EQ(kZero, out1);
+}
+
+TEST(OptionsFile, Persist) {
+ {
+ OptionsFile store(kTestFile);
+ // Clear contents of the file on disk.
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+ EXPECT_TRUE(store.SetIntValue(kTestOptionB, kNegInt));
+ EXPECT_TRUE(store.Save());
+ }
+ {
+ OptionsFile store(kTestFile);
+ // Load the saved contents from above.
+ EXPECT_TRUE(store.Load());
+ std::string out1;
+ int out2;
+ EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out1));
+ EXPECT_TRUE(store.GetIntValue(kTestOptionB, &out2));
+ EXPECT_EQ(kTestString1, out1);
+ EXPECT_EQ(kNegInt, out2);
+ }
+}
+
+TEST(OptionsFile, SpecialCharacters) {
+ OptionsFile store(kTestFile);
+ // Clear contents of the file on disk.
+ EXPECT_TRUE(store.Save());
+ std::string out;
+ EXPECT_FALSE(store.SetStringValue(kOptionWithEquals, kTestString1));
+ EXPECT_FALSE(store.GetStringValue(kOptionWithEquals, &out));
+ EXPECT_FALSE(store.SetStringValue(kOptionWithNewline, kTestString1));
+ EXPECT_FALSE(store.GetStringValue(kOptionWithNewline, &out));
+ EXPECT_TRUE(store.SetStringValue(kOptionWithUtf8, kValueWithUtf8));
+ EXPECT_TRUE(store.SetStringValue(kTestOptionA, kTestString1));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+ EXPECT_EQ(kTestString1, out);
+ EXPECT_TRUE(store.GetStringValue(kOptionWithUtf8, &out));
+ EXPECT_EQ(kValueWithUtf8, out);
+ EXPECT_FALSE(store.SetStringValue(kTestOptionA, kValueWithNewline));
+ EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+ EXPECT_EQ(kTestString1, out);
+ EXPECT_TRUE(store.SetStringValue(kTestOptionA, kValueWithEquals));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetStringValue(kTestOptionA, &out));
+ EXPECT_EQ(kValueWithEquals, out);
+ EXPECT_TRUE(store.SetStringValue(kEmptyString, kTestString2));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetStringValue(kEmptyString, &out));
+ EXPECT_EQ(kTestString2, out);
+ EXPECT_TRUE(store.SetStringValue(kTestOptionB, kEmptyString));
+ EXPECT_TRUE(store.Save());
+ EXPECT_TRUE(store.Load());
+ EXPECT_TRUE(store.GetStringValue(kTestOptionB, &out));
+ EXPECT_EQ(kEmptyString, out);
+}
+
+} // namespace talk_base
diff --git a/talk/base/pathutils_unittest.cc b/talk/base/pathutils_unittest.cc
new file mode 100644
index 0000000..0a9739b
--- /dev/null
+++ b/talk/base/pathutils_unittest.cc
@@ -0,0 +1,65 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/pathutils.h"
+#include "talk/base/gunit.h"
+
+TEST(Pathname, ReturnsDotForEmptyPathname) {
+ const std::string kCWD =
+ std::string(".") + talk_base::Pathname::DefaultFolderDelimiter();
+
+ talk_base::Pathname path("/", "");
+ EXPECT_FALSE(path.empty());
+ EXPECT_FALSE(path.folder().empty());
+ EXPECT_TRUE (path.filename().empty());
+ EXPECT_FALSE(path.pathname().empty());
+ EXPECT_EQ(std::string("/"), path.pathname());
+
+ path.SetPathname("", "foo");
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE (path.folder().empty());
+ EXPECT_FALSE(path.filename().empty());
+ EXPECT_FALSE(path.pathname().empty());
+ EXPECT_EQ(std::string("foo"), path.pathname());
+
+ path.SetPathname("", "");
+ EXPECT_TRUE (path.empty());
+ EXPECT_TRUE (path.folder().empty());
+ EXPECT_TRUE (path.filename().empty());
+ EXPECT_FALSE(path.pathname().empty());
+ EXPECT_EQ(kCWD, path.pathname());
+
+ path.SetPathname(kCWD, "");
+ EXPECT_FALSE(path.empty());
+ EXPECT_FALSE(path.folder().empty());
+ EXPECT_TRUE (path.filename().empty());
+ EXPECT_FALSE(path.pathname().empty());
+ EXPECT_EQ(kCWD, path.pathname());
+
+ talk_base::Pathname path2("c:/foo bar.txt");
+ EXPECT_EQ(path2.url(), std::string("file:///c:/foo%20bar.txt"));
+}
diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc
index 9cb5f4f..59eb550 100644
--- a/talk/base/physicalsocketserver.cc
+++ b/talk/base/physicalsocketserver.cc
@@ -57,7 +57,7 @@
#include "talk/base/logging.h"
#include "talk/base/nethelpers.h"
#include "talk/base/physicalsocketserver.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/base/winping.h"
#include "talk/base/win32socketinit.h"
@@ -591,7 +591,13 @@
// for 128.
static const int kNumPosixSignals = 128;
- static PosixSignalHandler *Instance() { return &instance_; }
+ // There is just a single global instance. (Signal handlers do not get any
+ // sort of user-defined void * parameter, so they can't access anything that
+ // isn't global.)
+ static PosixSignalHandler* Instance() {
+ LIBJINGLE_DEFINE_STATIC_LOCAL(PosixSignalHandler, instance, ());
+ return &instance;
+ }
// Returns true if the given signal number is set.
bool IsSignalSet(int signum) const {
@@ -674,11 +680,6 @@
close(fd2);
}
- // There is just a single global instance. (Signal handlers do not get any
- // sort of user-defined void * parameter, so they can't access anything that
- // isn't global.)
- static PosixSignalHandler instance_;
-
int afd_[2];
// These are boolean flags that will be set in our signal handler and read
// and cleared from Wait(). There is a race involved in this, but it is
@@ -693,8 +694,6 @@
volatile uint8 received_signal_[kNumPosixSignals];
};
-PosixSignalHandler PosixSignalHandler::instance_;
-
class PosixSignalDispatcher : public Dispatcher {
public:
PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) {
@@ -1409,6 +1408,10 @@
return true;
}
+Dispatcher* PhysicalSocketServer::signal_dispatcher() {
+ return signal_dispatcher_.get();
+}
+
bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) {
struct sigaction act;
// It doesn't really matter what we set this mask to.
diff --git a/talk/base/physicalsocketserver.h b/talk/base/physicalsocketserver.h
index aeb8348..1b7a298 100644
--- a/talk/base/physicalsocketserver.h
+++ b/talk/base/physicalsocketserver.h
@@ -2,26 +2,26 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@@ -73,7 +73,7 @@
// A socket server that provides the real sockets of the underlying OS.
class PhysicalSocketServer : public SocketServer {
-public:
+ public:
PhysicalSocketServer();
virtual ~PhysicalSocketServer();
@@ -104,10 +104,13 @@
// Dispatching signals on multiple PhysicalSocketServers is not reliable.
// The signal mask is not modified. It is the caller's responsibily to
// maintain it as desired.
- bool SetPosixSignalHandler(int signum, void (*handler)(int));
+ virtual bool SetPosixSignalHandler(int signum, void (*handler)(int));
+
+ protected:
+ Dispatcher* signal_dispatcher();
#endif
-private:
+ private:
typedef std::vector<Dispatcher*> DispatcherList;
typedef std::vector<size_t*> IteratorList;
diff --git a/talk/base/physicalsocketserver_unittest.cc b/talk/base/physicalsocketserver_unittest.cc
new file mode 100644
index 0000000..fa51e55
--- /dev/null
+++ b/talk/base/physicalsocketserver_unittest.cc
@@ -0,0 +1,224 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <signal.h>
+#include <stdarg.h>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+class PhysicalSocketTest : public SocketTest {
+};
+
+TEST_F(PhysicalSocketTest, TestConnect) {
+ SocketTest::TestConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookup) {
+ SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectFail) {
+ SocketTest::TestConnectFail();
+}
+
+TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFail) {
+ SocketTest::TestConnectWithDnsLookupFail();
+}
+
+#ifdef OSX
+// This test crashes the OS X kernel on 10.6 (at bsd/netinet/tcp_subr.c:2118).
+TEST_F(PhysicalSocketTest, DISABLED_TestConnectWithClosedSocket) {
+#else
+TEST_F(PhysicalSocketTest, TestConnectWithClosedSocket) {
+#endif
+ SocketTest::TestConnectWithClosedSocket();
+}
+
+TEST_F(PhysicalSocketTest, TestServerCloseDuringConnect) {
+ SocketTest::TestServerCloseDuringConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestClientCloseDuringConnect) {
+ SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(PhysicalSocketTest, TestServerClose) {
+ SocketTest::TestServerClose();
+}
+
+TEST_F(PhysicalSocketTest, TestCloseInClosedCallback) {
+ SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(PhysicalSocketTest, TestSocketServerWait) {
+ SocketTest::TestSocketServerWait();
+}
+
+TEST_F(PhysicalSocketTest, TestTcp) {
+ SocketTest::TestTcp();
+}
+
+TEST_F(PhysicalSocketTest, TestUdp) {
+ SocketTest::TestUdp();
+}
+
+TEST_F(PhysicalSocketTest, TestGetSetOptions) {
+ SocketTest::TestGetSetOptions();
+}
+
+#ifdef POSIX
+
+class PosixSignalDeliveryTest : public testing::Test {
+ public:
+ static void RecordSignal(int signum) {
+ signals_received_.push_back(signum);
+ signaled_thread_ = Thread::Current();
+ }
+
+ protected:
+ void SetUp() {
+ ss_.reset(new PhysicalSocketServer());
+ }
+
+ void TearDown() {
+ ss_.reset(NULL);
+ signals_received_.clear();
+ signaled_thread_ = NULL;
+ }
+
+ bool ExpectSignal(int signum) {
+ if (signals_received_.empty()) {
+ LOG(LS_ERROR) << "ExpectSignal(): No signal received";
+ return false;
+ }
+ if (signals_received_[0] != signum) {
+ LOG(LS_ERROR) << "ExpectSignal(): Received signal " <<
+ signals_received_[0] << ", expected " << signum;
+ return false;
+ }
+ signals_received_.erase(signals_received_.begin());
+ return true;
+ }
+
+ bool ExpectNone() {
+ bool ret = signals_received_.empty();
+ if (!ret) {
+ LOG(LS_ERROR) << "ExpectNone(): Received signal " << signals_received_[0]
+ << ", expected none";
+ }
+ return ret;
+ }
+
+ static std::vector<int> signals_received_;
+ static Thread *signaled_thread_;
+
+ scoped_ptr<PhysicalSocketServer> ss_;
+};
+
+std::vector<int> PosixSignalDeliveryTest::signals_received_;
+Thread *PosixSignalDeliveryTest::signaled_thread_ = NULL;
+
+// Test receiving a synchronous signal while not in Wait() and then entering
+// Wait() afterwards.
+TEST_F(PosixSignalDeliveryTest, RaiseThenWait) {
+ ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+ raise(SIGTERM);
+ EXPECT_TRUE(ss_->Wait(0, true));
+ EXPECT_TRUE(ExpectSignal(SIGTERM));
+ EXPECT_TRUE(ExpectNone());
+}
+
+// Test that we can handle getting tons of repeated signals and that we see all
+// the different ones.
+TEST_F(PosixSignalDeliveryTest, InsanelyManySignals) {
+ ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+ ss_->SetPosixSignalHandler(SIGINT, &RecordSignal);
+ for (int i = 0; i < 10000; ++i) {
+ raise(SIGTERM);
+ }
+ raise(SIGINT);
+ EXPECT_TRUE(ss_->Wait(0, true));
+ // Order will be lowest signal numbers first.
+ EXPECT_TRUE(ExpectSignal(SIGINT));
+ EXPECT_TRUE(ExpectSignal(SIGTERM));
+ EXPECT_TRUE(ExpectNone());
+}
+
+// Test that a signal during a Wait() call is detected.
+TEST_F(PosixSignalDeliveryTest, SignalDuringWait) {
+ ss_->SetPosixSignalHandler(SIGALRM, &RecordSignal);
+ alarm(1);
+ EXPECT_TRUE(ss_->Wait(1500, true));
+ EXPECT_TRUE(ExpectSignal(SIGALRM));
+ EXPECT_TRUE(ExpectNone());
+}
+
+class RaiseSigTermRunnable : public Runnable {
+ void Run(Thread *thread) {
+ thread->socketserver()->Wait(1000, false);
+
+ // Allow SIGTERM. This will be the only thread with it not masked so it will
+ // be delivered to us.
+ sigset_t mask;
+ sigemptyset(&mask);
+ pthread_sigmask(SIG_SETMASK, &mask, NULL);
+
+ // Raise it.
+ raise(SIGTERM);
+ }
+};
+
+// Test that it works no matter what thread the kernel chooses to give the
+// signal to (since it's not guaranteed to be the one that Wait() runs on).
+TEST_F(PosixSignalDeliveryTest, SignalOnDifferentThread) {
+ ss_->SetPosixSignalHandler(SIGTERM, &RecordSignal);
+ // Mask out SIGTERM so that it can't be delivered to this thread.
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGTERM);
+ EXPECT_EQ(0, pthread_sigmask(SIG_SETMASK, &mask, NULL));
+ // Start a new thread that raises it. It will have to be delivered to that
+ // thread. Our implementation should safely handle it and dispatch
+ // RecordSignal() on this thread.
+ scoped_ptr<Thread> thread(new Thread());
+ thread->Start(new RaiseSigTermRunnable());
+ EXPECT_TRUE(ss_->Wait(1500, true));
+ EXPECT_TRUE(ExpectSignal(SIGTERM));
+ EXPECT_EQ(Thread::Current(), signaled_thread_);
+ EXPECT_TRUE(ExpectNone());
+}
+
+#endif
+
+} // namespace talk_base
diff --git a/talk/base/posix.cc b/talk/base/posix.cc
new file mode 100644
index 0000000..3502f6d
--- /dev/null
+++ b/talk/base/posix.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/posix.h"
+
+#include <sys/wait.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef LINUX
+#include "talk/base/linuxfdwalk.h"
+#endif
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+#ifdef LINUX
+static void closefds(void *close_errors, int fd) {
+ if (fd <= 2) {
+ // We leave stdin/out/err open to the browser's terminal, if any.
+ return;
+ }
+ if (close(fd) < 0) {
+ *static_cast<bool *>(close_errors) = true;
+ }
+}
+#endif
+
+enum {
+ EXIT_FLAG_CHDIR_ERRORS = 1 << 0,
+#ifdef LINUX
+ EXIT_FLAG_FDWALK_ERRORS = 1 << 1,
+ EXIT_FLAG_CLOSE_ERRORS = 1 << 2,
+#endif
+ EXIT_FLAG_SECOND_FORK_FAILED = 1 << 3,
+};
+
+bool RunAsDaemon(const char *file, const char *const argv[]) {
+ // Fork intermediate child to daemonize.
+ pid_t pid = fork();
+ if (pid < 0) {
+ LOG_ERR(LS_ERROR) << "fork()";
+ return false;
+ } else if (!pid) {
+ // Child.
+
+ // We try to close all fds and change directory to /, but if that fails we
+ // keep going because it's not critical.
+ int exit_code = 0;
+ if (chdir("/") < 0) {
+ exit_code |= EXIT_FLAG_CHDIR_ERRORS;
+ }
+#ifdef LINUX
+ bool close_errors = false;
+ if (fdwalk(&closefds, &close_errors) < 0) {
+ exit_code |= EXIT_FLAG_FDWALK_ERRORS;
+ }
+ if (close_errors) {
+ exit_code |= EXIT_FLAG_CLOSE_ERRORS;
+ }
+#endif
+
+ // Fork again to become a daemon.
+ pid = fork();
+ // It is important that everything here use _exit() and not exit(), because
+ // exit() would call the destructors of all global variables in the whole
+ // process, which is both unnecessary and unsafe.
+ if (pid < 0) {
+ exit_code |= EXIT_FLAG_SECOND_FORK_FAILED;
+ _exit(exit_code); // if second fork failed
+ } else if (!pid) {
+ // Child.
+ // Successfully daemonized. Run command.
+ // POSIX requires the args to be typed as non-const for historical
+ // reasons, but it mandates that the actual implementation be const, so
+ // the cast is safe.
+ execvp(file, const_cast<char *const *>(argv));
+ _exit(255); // if execvp failed
+ }
+
+ // Parent.
+ // Successfully spawned process, but report any problems to the parent where
+ // we can log them.
+ _exit(exit_code);
+ }
+
+ // Parent. Reap intermediate child.
+ int status;
+ pid_t child = waitpid(pid, &status, 0);
+ if (child < 0) {
+ LOG_ERR(LS_ERROR) << "Error in waitpid()";
+ return false;
+ }
+ if (child != pid) {
+ // Should never happen (see man page).
+ LOG(LS_ERROR) << "waitpid() chose wrong child???";
+ return false;
+ }
+ if (!WIFEXITED(status)) {
+ LOG(LS_ERROR) << "Intermediate child killed uncleanly"; // Probably crashed
+ return false;
+ }
+
+ int exit_code = WEXITSTATUS(status);
+ if (exit_code & EXIT_FLAG_CHDIR_ERRORS) {
+ LOG(LS_WARNING) << "Child reported probles calling chdir()";
+ }
+#ifdef LINUX
+ if (exit_code & EXIT_FLAG_FDWALK_ERRORS) {
+ LOG(LS_WARNING) << "Child reported problems calling fdwalk()";
+ }
+ if (exit_code & EXIT_FLAG_CLOSE_ERRORS) {
+ LOG(LS_WARNING) << "Child reported problems calling close()";
+ }
+#endif
+ if (exit_code & EXIT_FLAG_SECOND_FORK_FAILED) {
+ LOG(LS_ERROR) << "Failed to daemonize";
+ // This means the command was not launched, so failure.
+ return false;
+ }
+ return true;
+}
+
+} // namespace talk_base
diff --git a/talk/base/posix.h b/talk/base/posix.h
new file mode 100644
index 0000000..15c2c90
--- /dev/null
+++ b/talk/base/posix.h
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_POSIX_H_
+#define TALK_BASE_POSIX_H_
+
+namespace talk_base {
+
+// Runs the given executable name as a daemon, so that it executes concurrently
+// with this process. Upon completion, the daemon process will automatically be
+// reaped by init(8), so an error exit status or a failure to start the
+// executable are not reported. Returns true if the daemon process was forked
+// successfully, else false.
+bool RunAsDaemon(const char *file, const char *const argv[]);
+
+} // namespace talk_base
+
+#endif // TALK_BASE_POSIX_H_
diff --git a/talk/base/proxy_unittest.cc b/talk/base/proxy_unittest.cc
new file mode 100644
index 0000000..e1a51f3
--- /dev/null
+++ b/talk/base/proxy_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/proxyserver.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testechoserver.h"
+#include "talk/base/virtualsocketserver.h"
+
+using talk_base::Socket;
+using talk_base::Thread;
+using talk_base::SocketAddress;
+
+static const SocketAddress kSocksProxyIntAddr("1.2.3.4", 1080);
+static const SocketAddress kSocksProxyExtAddr("1.2.3.5", 0);
+static const SocketAddress kHttpsProxyIntAddr("1.2.3.4", 443);
+static const SocketAddress kHttpsProxyExtAddr("1.2.3.5", 0);
+static const SocketAddress kBogusProxyIntAddr("1.2.3.4", 999);
+
+// Used to run a proxy detect on the current thread. Otherwise we would need
+// to make both threads share the same VirtualSocketServer.
+class AutoDetectProxyRunner : public talk_base::AutoDetectProxy {
+ public:
+ explicit AutoDetectProxyRunner(const std::string& agent)
+ : AutoDetectProxy(agent) {}
+ void Run() {
+ DoWork();
+ Thread::Current()->Restart(); // needed to reset the messagequeue
+ }
+};
+
+// Sets up a virtual socket server and HTTPS/SOCKS5 proxy servers.
+class ProxyTest : public testing::Test {
+ public:
+ ProxyTest() : ss_(new talk_base::VirtualSocketServer(NULL)) {
+ Thread::Current()->set_socketserver(ss_.get());
+ socks_.reset(new talk_base::SocksProxyServer(
+ ss_.get(), kSocksProxyIntAddr, ss_.get(), kSocksProxyExtAddr));
+ https_.reset(new talk_base::HttpListenServer());
+ https_->Listen(kHttpsProxyIntAddr);
+ }
+ ~ProxyTest() {
+ Thread::Current()->set_socketserver(NULL);
+ }
+
+ talk_base::SocketServer* ss() { return ss_.get(); }
+
+ talk_base::ProxyType DetectProxyType(const SocketAddress& address) {
+ talk_base::ProxyType type;
+ AutoDetectProxyRunner* detect = new AutoDetectProxyRunner("unittest/1.0");
+ detect->set_proxy(address);
+ detect->Run(); // blocks until done
+ type = detect->proxy().type;
+ detect->Destroy(false);
+ return type;
+ }
+
+ private:
+ talk_base::scoped_ptr<talk_base::SocketServer> ss_;
+ talk_base::scoped_ptr<talk_base::SocksProxyServer> socks_;
+ // TODO: Make this a real HTTPS proxy server.
+ talk_base::scoped_ptr<talk_base::HttpListenServer> https_;
+};
+
+// Tests whether we can use a SOCKS5 proxy to connect to a server.
+TEST_F(ProxyTest, TestSocks5Connect) {
+ talk_base::AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM);
+ talk_base::AsyncSocksProxySocket* proxy_socket =
+ new talk_base::AsyncSocksProxySocket(socket, kSocksProxyIntAddr,
+ "", talk_base::CryptString());
+
+ talk_base::TestEchoServer server(Thread::Current(), SocketAddress());
+
+ talk_base::AsyncTCPSocket* packet_socket = talk_base::AsyncTCPSocket::Create(
+ proxy_socket, SocketAddress(), server.address());
+ EXPECT_TRUE(packet_socket != NULL);
+ talk_base::TestClient client(packet_socket);
+
+ EXPECT_EQ(Socket::CS_CONNECTING, proxy_socket->GetState());
+ EXPECT_TRUE(client.CheckConnected());
+ EXPECT_EQ(Socket::CS_CONNECTED, proxy_socket->GetState());
+ EXPECT_EQ(server.address(), client.remote_address());
+ client.Send("foo", 3);
+ EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL));
+ EXPECT_TRUE(client.CheckNoPacket());
+}
+
+/*
+// Tests whether we can use a HTTPS proxy to connect to a server.
+TEST_F(ProxyTest, TestHttpsConnect) {
+ AsyncSocket* socket = ss()->CreateAsyncSocket(SOCK_STREAM);
+ AsyncHttpsProxySocket* proxy_socket = new AsyncHttpsProxySocket(
+ socket, "unittest/1.0", kHttpsProxyIntAddress, "", CryptString());
+ TestClient client(new AsyncTCPSocket(proxy_socket));
+ TestEchoServer server(Thread::Current(), SocketAddress());
+
+ EXPECT_TRUE(client.Connect(server.address()));
+ EXPECT_TRUE(client.CheckConnected());
+ EXPECT_EQ(server.address(), client.remote_address());
+ client.Send("foo", 3);
+ EXPECT_TRUE(client.CheckNextPacket("foo", 3, NULL));
+ EXPECT_TRUE(client.CheckNoPacket());
+}
+*/
+
+// Tests whether we can autodetect a SOCKS5 proxy.
+TEST_F(ProxyTest, TestAutoDetectSocks5) {
+ EXPECT_EQ(talk_base::PROXY_SOCKS5, DetectProxyType(kSocksProxyIntAddr));
+}
+
+/*
+// Tests whether we can autodetect a HTTPS proxy.
+TEST_F(ProxyTest, TestAutoDetectHttps) {
+ EXPECT_EQ(talk_base::PROXY_HTTPS, DetectProxyType(kHttpsProxyIntAddr));
+}
+*/
+
+// Tests whether we fail properly for no proxy.
+TEST_F(ProxyTest, TestAutoDetectBogus) {
+ EXPECT_EQ(talk_base::PROXY_UNKNOWN, DetectProxyType(kBogusProxyIntAddr));
+}
diff --git a/talk/base/proxydetect.cc b/talk/base/proxydetect.cc
index 66213ee..4bfae29 100644
--- a/talk/base/proxydetect.cc
+++ b/talk/base/proxydetect.cc
@@ -233,7 +233,9 @@
m = 0;
uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m);
SocketAddress addr(url.host(), 0);
- return !addr.IsUnresolved() && ((addr.ip() & mask) == (ip & mask));
+ // TODO: Support IPv6 proxyitems. This code block is IPv4 only anyway.
+ return !addr.IsUnresolved() &&
+ ((addr.ipaddr().v4AddressAsHostOrderInteger() & mask) == (ip & mask));
}
// .foo.com
diff --git a/talk/base/proxydetect_unittest.cc b/talk/base/proxydetect_unittest.cc
new file mode 100644
index 0000000..4855650
--- /dev/null
+++ b/talk/base/proxydetect_unittest.cc
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/fileutils_mock.h"
+#include "talk/base/proxydetect.h"
+
+namespace talk_base {
+
+static const std::string kFirefoxProfilesIni =
+ "[Profile0]\n"
+ "Name=default\n"
+ "IsRelative=1\n"
+ "Path=Profiles/2de53ejb.default\n"
+ "Default=1\n";
+
+static const std::string kFirefoxHeader =
+ "# Mozilla User Preferences\n"
+ "\n"
+ "/* Some Comments\n"
+ "*\n"
+ "*/\n"
+ "\n";
+
+static const std::string kFirefoxCorruptHeader =
+ "iuahueqe32164";
+
+static const std::string kProxyAddress = "proxy.net.com";
+static const int kProxyPort = 9999;
+
+// Mocking out platform specific path to firefox prefs file.
+class FirefoxPrefsFileSystem : public FakeFileSystem {
+ public:
+ explicit FirefoxPrefsFileSystem(const std::vector<File>& all_files) :
+ FakeFileSystem(all_files) {
+ }
+ virtual FileStream* OpenFile(const Pathname& filename,
+ const std::string& mode) {
+ // TODO: We could have a platform dependent check of paths here.
+ std::string name = filename.basename();
+ name.append(filename.extension());
+ EXPECT_TRUE(name.compare("prefs.js") == 0 ||
+ name.compare("profiles.ini") == 0);
+ FileStream* stream = FakeFileSystem::OpenFile(name, mode);
+ return stream;
+ }
+};
+
+class ProxyDetectTest : public testing::Test {
+};
+
+bool GetProxyInfo(const std::string prefs, ProxyInfo* info) {
+ std::vector<talk_base::FakeFileSystem::File> files;
+ files.push_back(talk_base::FakeFileSystem::File("profiles.ini",
+ kFirefoxProfilesIni));
+ files.push_back(talk_base::FakeFileSystem::File("prefs.js", prefs));
+ talk_base::FilesystemScope fs(new talk_base::FirefoxPrefsFileSystem(files));
+ return GetProxySettingsForUrl("Firefox", "www.google.com", *info, false);
+}
+
+// Verifies that an empty Firefox prefs file results in no proxy detected.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxEmptyPrefs) {
+ ProxyInfo proxy_info;
+ EXPECT_TRUE(GetProxyInfo(kFirefoxHeader, &proxy_info));
+ EXPECT_EQ(PROXY_NONE, proxy_info.type);
+}
+
+// Verifies that corrupted prefs file results in no proxy detected.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxCorruptedPrefs) {
+ ProxyInfo proxy_info;
+ EXPECT_TRUE(GetProxyInfo(kFirefoxCorruptHeader, &proxy_info));
+ EXPECT_EQ(PROXY_NONE, proxy_info.type);
+}
+
+// Verifies that SOCKS5 proxy is detected if configured. SOCKS uses a
+// handshake protocol to inform the proxy software about the
+// connection that the client is trying to make and may be used for
+// any form of TCP or UDP socket connection.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySocks) {
+ ProxyInfo proxy_info;
+ SocketAddress proxy_address("proxy.socks.com", 6666);
+ std::string prefs(kFirefoxHeader);
+ prefs.append("user_pref(\"network.proxy.socks\", \"proxy.socks.com\");\n");
+ prefs.append("user_pref(\"network.proxy.socks_port\", 6666);\n");
+ prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+ EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+ EXPECT_EQ(PROXY_SOCKS5, proxy_info.type);
+ EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verified that SSL proxy is detected if configured. SSL proxy is an
+// extention of a HTTP proxy to support secure connections.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxySsl) {
+ ProxyInfo proxy_info;
+ SocketAddress proxy_address("proxy.ssl.com", 7777);
+ std::string prefs(kFirefoxHeader);
+
+ prefs.append("user_pref(\"network.proxy.ssl\", \"proxy.ssl.com\");\n");
+ prefs.append("user_pref(\"network.proxy.ssl_port\", 7777);\n");
+ prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+ EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+ EXPECT_EQ(PROXY_HTTPS, proxy_info.type);
+ EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verifies that a HTTP proxy is detected if configured.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyHttp) {
+ ProxyInfo proxy_info;
+ SocketAddress proxy_address("proxy.http.com", 8888);
+ std::string prefs(kFirefoxHeader);
+
+ prefs.append("user_pref(\"network.proxy.http\", \"proxy.http.com\");\n");
+ prefs.append("user_pref(\"network.proxy.http_port\", 8888);\n");
+ prefs.append("user_pref(\"network.proxy.type\", 1);\n");
+
+ EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+ EXPECT_EQ(PROXY_HTTPS, proxy_info.type);
+ EXPECT_EQ(proxy_address, proxy_info.address);
+}
+
+// Verifies detection of automatic proxy detection.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAuto) {
+ ProxyInfo proxy_info;
+ std::string prefs(kFirefoxHeader);
+
+ prefs.append("user_pref(\"network.proxy.type\", 4);\n");
+
+ EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+ EXPECT_EQ(PROXY_NONE, proxy_info.type);
+ EXPECT_TRUE(proxy_info.autodetect);
+ EXPECT_TRUE(proxy_info.autoconfig_url.empty());
+}
+
+// Verifies detection of automatic proxy detection using a static url
+// to config file.
+TEST_F(ProxyDetectTest, DISABLED_TestFirefoxProxyAutoUrl) {
+ ProxyInfo proxy_info;
+ std::string prefs(kFirefoxHeader);
+
+ prefs.append(
+ "user_pref(\"network.proxy.autoconfig_url\", \"http://a/b.pac\");\n");
+ prefs.append("user_pref(\"network.proxy.type\", 2);\n");
+
+ EXPECT_TRUE(GetProxyInfo(prefs, &proxy_info));
+
+ EXPECT_FALSE(proxy_info.autodetect);
+ EXPECT_EQ(PROXY_NONE, proxy_info.type);
+ EXPECT_EQ(0, proxy_info.autoconfig_url.compare("http://a/b.pac"));
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxyserver.cc b/talk/base/proxyserver.cc
new file mode 100644
index 0000000..d764fd7
--- /dev/null
+++ b/talk/base/proxyserver.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/base/proxyserver.h"
+
+#include <algorithm>
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// ProxyServer
+ProxyServer::ProxyServer(
+ SocketFactory* int_factory, const SocketAddress& int_addr,
+ SocketFactory* ext_factory, const SocketAddress& ext_ip)
+ : ext_factory_(ext_factory), ext_ip_(ext_ip.ipaddr(), 0), // strip off port
+ server_socket_(int_factory->CreateAsyncSocket(SOCK_STREAM)) {
+ server_socket_->Bind(int_addr);
+ server_socket_->Listen(5);
+ server_socket_->SignalReadEvent.connect(this, &ProxyServer::OnAcceptEvent);
+}
+
+ProxyServer::~ProxyServer() {
+ for (BindingList::iterator it = bindings_.begin();
+ it != bindings_.end(); ++it) {
+ delete (*it);
+ }
+}
+
+void ProxyServer::OnAcceptEvent(AsyncSocket* socket) {
+ ASSERT(socket == server_socket_.get());
+ AsyncSocket* int_socket = socket->Accept(NULL);
+ AsyncProxyServerSocket* wrapped_socket = WrapSocket(int_socket);
+ AsyncSocket* ext_socket = ext_factory_->CreateAsyncSocket(SOCK_STREAM);
+ ext_socket->Bind(ext_ip_);
+ bindings_.push_back(new ProxyBinding(wrapped_socket, ext_socket));
+}
+
+void ProxyServer::OnBindingDestroyed(ProxyBinding* binding) {
+ BindingList::iterator it =
+ std::find(bindings_.begin(), bindings_.end(), binding);
+ delete (*it);
+ bindings_.erase(it);
+}
+
+// ProxyBinding
+ProxyBinding::ProxyBinding(AsyncProxyServerSocket* int_socket,
+ AsyncSocket* ext_socket)
+ : int_socket_(int_socket), ext_socket_(ext_socket), connected_(false),
+ out_buffer_(kBufferSize), in_buffer_(kBufferSize) {
+ int_socket_->SignalConnectRequest.connect(this,
+ &ProxyBinding::OnConnectRequest);
+ int_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnInternalRead);
+ int_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnInternalWrite);
+ int_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnInternalClose);
+ ext_socket_->SignalConnectEvent.connect(this,
+ &ProxyBinding::OnExternalConnect);
+ ext_socket_->SignalReadEvent.connect(this, &ProxyBinding::OnExternalRead);
+ ext_socket_->SignalWriteEvent.connect(this, &ProxyBinding::OnExternalWrite);
+ ext_socket_->SignalCloseEvent.connect(this, &ProxyBinding::OnExternalClose);
+}
+
+void ProxyBinding::OnConnectRequest(AsyncProxyServerSocket* socket,
+ const SocketAddress& addr) {
+ ASSERT(!connected_);
+ ext_socket_->Connect(addr);
+ // TODO: handle errors here
+}
+
+void ProxyBinding::OnInternalRead(AsyncSocket* socket) {
+ Read(int_socket_.get(), &out_buffer_);
+ Write(ext_socket_.get(), &out_buffer_);
+}
+
+void ProxyBinding::OnInternalWrite(AsyncSocket* socket) {
+ Write(int_socket_.get(), &in_buffer_);
+}
+
+void ProxyBinding::OnInternalClose(AsyncSocket* socket, int err) {
+ Destroy();
+}
+
+void ProxyBinding::OnExternalConnect(AsyncSocket* socket) {
+ connected_ = true;
+ int_socket_->SendConnectResult(0, socket->GetRemoteAddress());
+}
+
+void ProxyBinding::OnExternalRead(AsyncSocket* socket) {
+ Read(ext_socket_.get(), &in_buffer_);
+ Write(int_socket_.get(), &in_buffer_);
+}
+
+void ProxyBinding::OnExternalWrite(AsyncSocket* socket) {
+ Write(ext_socket_.get(), &out_buffer_);
+}
+
+void ProxyBinding::OnExternalClose(AsyncSocket* socket, int err) {
+ if (!connected_) {
+ int_socket_->SendConnectResult(err, SocketAddress());
+ }
+ Destroy();
+}
+
+void ProxyBinding::Read(AsyncSocket* socket, FifoBuffer* buffer) {
+ // Only read if the buffer is empty.
+ size_t size;
+ int read;
+ if (buffer->GetBuffered(&size) && size == 0) {
+ void* p = buffer->GetWriteBuffer(&size);
+ read = socket->Recv(p, size);
+ buffer->ConsumeWriteBuffer(_max(read, 0));
+ }
+}
+
+void ProxyBinding::Write(AsyncSocket* socket, FifoBuffer* buffer) {
+ size_t size;
+ int written;
+ const void* p = buffer->GetReadData(&size);
+ written = socket->Send(p, size);
+ buffer->ConsumeReadData(_max(written, 0));
+}
+
+void ProxyBinding::Destroy() {
+ SignalDestroyed(this);
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxyserver.h b/talk/base/proxyserver.h
new file mode 100644
index 0000000..8e1ab6b
--- /dev/null
+++ b/talk/base/proxyserver.h
@@ -0,0 +1,113 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_PROXYSERVER_H_
+#define TALK_BASE_PROXYSERVER_H_
+
+#include <list>
+#include "talk/base/asyncsocket.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class SocketFactory;
+
+// ProxyServer is a base class that allows for easy construction of proxy
+// servers. With its helper class ProxyBinding, it contains all the necessary
+// logic for receiving and bridging connections. The specific client-server
+// proxy protocol is implemented by an instance of the AsyncProxyServerSocket
+// class; children of ProxyServer implement WrapSocket appropriately to return
+// the correct protocol handler.
+
+class ProxyBinding : public sigslot::has_slots<> {
+ public:
+ ProxyBinding(AsyncProxyServerSocket* in_socket, AsyncSocket* out_socket);
+ sigslot::signal1<ProxyBinding*> SignalDestroyed;
+
+ private:
+ void OnConnectRequest(AsyncProxyServerSocket* socket,
+ const SocketAddress& addr);
+ void OnInternalRead(AsyncSocket* socket);
+ void OnInternalWrite(AsyncSocket* socket);
+ void OnInternalClose(AsyncSocket* socket, int err);
+ void OnExternalConnect(AsyncSocket* socket);
+ void OnExternalRead(AsyncSocket* socket);
+ void OnExternalWrite(AsyncSocket* socket);
+ void OnExternalClose(AsyncSocket* socket, int err);
+
+ static void Read(AsyncSocket* socket, FifoBuffer* buffer);
+ static void Write(AsyncSocket* socket, FifoBuffer* buffer);
+ void Destroy();
+
+ static const int kBufferSize = 4096;
+ scoped_ptr<AsyncProxyServerSocket> int_socket_;
+ scoped_ptr<AsyncSocket> ext_socket_;
+ bool connected_;
+ FifoBuffer out_buffer_;
+ FifoBuffer in_buffer_;
+ DISALLOW_EVIL_CONSTRUCTORS(ProxyBinding);
+};
+
+class ProxyServer : public sigslot::has_slots<> {
+ public:
+ ProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
+ SocketFactory* ext_factory, const SocketAddress& ext_ip);
+ virtual ~ProxyServer();
+
+ protected:
+ void OnAcceptEvent(AsyncSocket* socket);
+ virtual AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) = 0;
+ void OnBindingDestroyed(ProxyBinding* binding);
+
+ private:
+ typedef std::list<ProxyBinding*> BindingList;
+ SocketFactory* ext_factory_;
+ SocketAddress ext_ip_;
+ scoped_ptr<AsyncSocket> server_socket_;
+ BindingList bindings_;
+ DISALLOW_EVIL_CONSTRUCTORS(ProxyServer);
+};
+
+// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS.
+class SocksProxyServer : public ProxyServer {
+ public:
+ SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
+ SocketFactory* ext_factory, const SocketAddress& ext_ip)
+ : ProxyServer(int_factory, int_addr, ext_factory, ext_ip) {
+ }
+ protected:
+ AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) {
+ return new AsyncSocksProxyServerSocket(socket);
+ }
+ DISALLOW_EVIL_CONSTRUCTORS(SocksProxyServer);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PROXYSERVER_H_
diff --git a/talk/base/ratetracker.cc b/talk/base/ratetracker.cc
index d5207cd..383df93 100644
--- a/talk/base/ratetracker.cc
+++ b/talk/base/ratetracker.cc
@@ -26,7 +26,7 @@
*/
#include "talk/base/ratetracker.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
namespace talk_base {
diff --git a/talk/base/ratetracker_unittest.cc b/talk/base/ratetracker_unittest.cc
new file mode 100644
index 0000000..979d907
--- /dev/null
+++ b/talk/base/ratetracker_unittest.cc
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/ratetracker.h"
+
+namespace talk_base {
+
+class RateTrackerForTest : public RateTracker {
+ public:
+ RateTrackerForTest() : time_(0) {}
+ virtual uint32 Time() const { return time_; }
+ void AdvanceTime(uint32 delta) { time_ += delta; }
+
+ private:
+ uint32 time_;
+};
+
+TEST(RateTrackerTest, TestBasics) {
+ RateTrackerForTest tracker;
+ EXPECT_EQ(0U, tracker.total_units());
+ EXPECT_EQ(0U, tracker.units_second());
+
+ // Add a sample.
+ tracker.Update(1234);
+ // Advance the clock by 100 ms.
+ tracker.AdvanceTime(100);
+ // total_units should advance, but units_second should stay 0.
+ EXPECT_EQ(1234U, tracker.total_units());
+ EXPECT_EQ(0U, tracker.units_second());
+
+ // Repeat.
+ tracker.Update(1234);
+ tracker.AdvanceTime(100);
+ EXPECT_EQ(1234U * 2, tracker.total_units());
+ EXPECT_EQ(0U, tracker.units_second());
+
+ // Advance the clock by 800 ms, so we've elapsed a full second.
+ // units_second should now be filled in properly.
+ tracker.AdvanceTime(800);
+ EXPECT_EQ(1234U * 2, tracker.total_units());
+ EXPECT_EQ(1234U * 2, tracker.units_second());
+
+ // Poll the tracker again immediately. The reported rate should stay the same.
+ EXPECT_EQ(1234U * 2, tracker.total_units());
+ EXPECT_EQ(1234U * 2, tracker.units_second());
+
+ // Do nothing and advance by a second. We should drop down to zero.
+ tracker.AdvanceTime(1000);
+ EXPECT_EQ(1234U * 2, tracker.total_units());
+ EXPECT_EQ(0U, tracker.units_second());
+
+ // Send a bunch of data at a constant rate for 5.5 "seconds".
+ // We should report the rate properly.
+ for (int i = 0; i < 5500; i += 100) {
+ tracker.Update(9876U);
+ tracker.AdvanceTime(100);
+ }
+ EXPECT_EQ(9876U * 10, tracker.units_second());
+
+ // Advance the clock by 500 ms. Since we sent nothing over this half-second,
+ // the reported rate should be reduced by half.
+ tracker.AdvanceTime(500);
+ EXPECT_EQ(9876U * 5, tracker.units_second());
+}
+
+} // namespace talk_base
diff --git a/talk/base/refcount.h b/talk/base/refcount.h
new file mode 100644
index 0000000..ce49d9e
--- /dev/null
+++ b/talk/base/refcount.h
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_BASE_REFCOUNT_H_
+#define TALK_APP_BASE_REFCOUNT_H_
+
+#include <cstring>
+
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+// Reference count interface.
+class RefCountInterface {
+ public:
+ virtual int AddRef() = 0;
+ virtual int Release() = 0;
+};
+
+template <class T>
+class RefCountedObject : public T {
+ public:
+ RefCountedObject() : ref_count_(0) {
+ }
+
+ template<typename P>
+ explicit RefCountedObject(P p) : T(p), ref_count_(0) {
+ }
+
+ template<typename P1, typename P2>
+ RefCountedObject(P1 p1, P2 p2) : T(p1, p2), ref_count_(0) {
+ }
+
+ template<typename P1, typename P2, typename P3>
+ RefCountedObject(P1 p1, P2 p2, P3 p3) : T(p1, p2, p3), ref_count_(0) {
+ }
+
+ template<typename P1, typename P2, typename P3, typename P4>
+ RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4)
+ : T(p1, p2, p3, p4), ref_count_(0) {
+ }
+
+ template<typename P1, typename P2, typename P3, typename P4, typename P5>
+ RefCountedObject(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5)
+ : T(p1, p2, p3, p4, p5), ref_count_(0) {
+ }
+
+ virtual int AddRef() {
+ return talk_base::AtomicOps::Increment(&ref_count_);
+ }
+
+ virtual int Release() {
+ int count = talk_base::AtomicOps::Decrement(&ref_count_);
+ if (!count) {
+ delete this;
+ }
+ return count;
+ }
+
+ protected:
+ int ref_count_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_APP_BASE_REFCOUNT_H_
diff --git a/talk/base/rollingaccumulator.h b/talk/base/rollingaccumulator.h
new file mode 100644
index 0000000..3868031
--- /dev/null
+++ b/talk/base/rollingaccumulator.h
@@ -0,0 +1,116 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_ROLLINGACCUMULATOR_H_
+#define TALK_BASE_ROLLINGACCUMULATOR_H_
+
+#include <vector>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+// RollingAccumulator stores and reports statistics
+// over N most recent samples.
+//
+// T is assumed to be an int, long, double or float.
+template<typename T>
+class RollingAccumulator {
+ public:
+ explicit RollingAccumulator(size_t max_count)
+ : count_(0),
+ next_index_(0),
+ sum_(0.0),
+ sum_2_(0.0),
+ samples_(max_count) {
+ }
+ ~RollingAccumulator() {
+ }
+
+ int max_count() const {
+ return samples_.size();
+ }
+
+ int count() const {
+ return count_;
+ }
+
+ void AddSample(T sample) {
+ if (count_ == max_count()) {
+ // Remove oldest sample.
+ T sample_to_remove = samples_[next_index_];
+ sum_ -= sample_to_remove;
+ sum_2_ -= sample_to_remove * sample_to_remove;
+ } else {
+ // Increase count of samples.
+ ++count_;
+ }
+ // Add new sample.
+ samples_[next_index_] = sample;
+ sum_ += sample;
+ sum_2_ += sample * sample;
+ // Update next_index_.
+ next_index_ = (next_index_ + 1) % max_count();
+ }
+
+ T ComputeSum() const {
+ return static_cast<T>(sum_);
+ }
+
+ T ComputeMean() const {
+ if (count_ == 0) {
+ return static_cast<T>(0);
+ }
+ return static_cast<T>(sum_ / count_);
+ }
+
+ // Compute estimated variance. Estimation is more accurate
+ // as the number of samples grows.
+ T ComputeVariance() const {
+ if (count_ == 0) {
+ return static_cast<T>(0);
+ }
+ // Var = E[x^2] - (E[x])^2
+ double count_inv = 1.0 / count_;
+ double mean_2 = sum_2_ * count_inv;
+ double mean = sum_ * count_inv;
+ return static_cast<T>(mean_2 - (mean * mean));
+ }
+
+ private:
+ int count_;
+ int next_index_;
+ double sum_; // Sum(x)
+ double sum_2_; // Sum(x*x)
+ std::vector<T> samples_;
+
+ DISALLOW_COPY_AND_ASSIGN(RollingAccumulator);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ROLLINGACCUMULATOR_H_
diff --git a/talk/base/rollingaccumulator_unittest.cc b/talk/base/rollingaccumulator_unittest.cc
new file mode 100644
index 0000000..3630e5b
--- /dev/null
+++ b/talk/base/rollingaccumulator_unittest.cc
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/rollingaccumulator.h"
+
+namespace talk_base {
+
+static bool IsApproximate(double expected, double value, double eps) {
+ return expected - eps <= value && value <= expected + eps;
+}
+
+TEST(RollingAccumulatorTest, TestZeroSamples) {
+ RollingAccumulator<int> accum(10);
+
+ EXPECT_EQ(0, accum.count());
+ EXPECT_EQ(0, accum.ComputeMean());
+ EXPECT_EQ(0, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, TestSomeSamples) {
+ RollingAccumulator<int> accum(10);
+ for (int i = 0; i < 4; ++i) {
+ accum.AddSample(i);
+ }
+
+ EXPECT_EQ(4, accum.count());
+ EXPECT_EQ(6, accum.ComputeSum());
+ EXPECT_EQ(1, accum.ComputeMean());
+ EXPECT_EQ(1, accum.ComputeVariance());
+}
+
+TEST(RollingAccumulatorTest, TestRollingSamples) {
+ RollingAccumulator<int> accum(10);
+ for (int i = 0; i < 12; ++i) {
+ accum.AddSample(i);
+ }
+
+ EXPECT_EQ(10, accum.count());
+ EXPECT_EQ(65, accum.ComputeSum());
+ EXPECT_EQ(6, accum.ComputeMean());
+ EXPECT_TRUE(IsApproximate(9, accum.ComputeVariance(), 1));
+}
+
+TEST(RollingAccumulatorTest, TestRollingSamplesDouble) {
+ RollingAccumulator<double> accum(10);
+ for (int i = 0; i < 23; ++i) {
+ accum.AddSample(5 * i);
+ }
+
+ EXPECT_EQ(10, accum.count());
+ EXPECT_TRUE(IsApproximate(875.0, accum.ComputeSum(), 1E-6));
+ EXPECT_TRUE(IsApproximate(87.5, accum.ComputeMean(), 1E-6));
+ EXPECT_TRUE(IsApproximate(229.166667, accum.ComputeVariance(), 25));
+}
+
+} // namespace talk_base
diff --git a/talk/base/scoped_autorelease_pool.h b/talk/base/scoped_autorelease_pool.h
new file mode 100644
index 0000000..611f811
--- /dev/null
+++ b/talk/base/scoped_autorelease_pool.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Automatically initialize and and free an autoreleasepool. Never allocate
+// an instance of this class using "new" - that will result in a compile-time
+// error. Only use it as a stack object.
+//
+// Note: NSAutoreleasePool docs say that you should not normally need to
+// declare an NSAutoreleasePool as a member of an object - but there's nothing
+// that indicates it will be a problem, as long as the stack lifetime of the
+// pool exactly matches the stack lifetime of the object.
+
+#ifndef TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
+#define TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
+
+#if defined(IOS) || defined(OSX)
+
+#include "talk/base/common.h"
+
+// This header may be included from Obj-C files or C++ files.
+#ifdef __OBJC__
+@class NSAutoreleasePool;
+#else
+class NSAutoreleasePool;
+#endif
+
+namespace talk_base {
+
+class ScopedAutoreleasePool {
+ public:
+ ScopedAutoreleasePool();
+ ~ScopedAutoreleasePool();
+
+ private:
+ // Declaring private overrides of new and delete here enforces the "only use
+ // as a stack object" discipline.
+ //
+ // Note: new is declared as "throw()" to get around a gcc warning about new
+ // returning NULL, but this method will never get called and therefore will
+ // never actually throw any exception.
+ void* operator new(size_t size) throw() { return NULL; }
+ void operator delete (void* ptr) {}
+
+ NSAutoreleasePool* pool_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ScopedAutoreleasePool);
+};
+
+} // namespace talk_base
+
+#endif // IOS || OSX
+#endif // TALK_BASE_SCOPED_AUTORELEASE_POOL_H__
diff --git a/talk/base/scoped_autorelease_pool.mm b/talk/base/scoped_autorelease_pool.mm
new file mode 100644
index 0000000..4009c7b
--- /dev/null
+++ b/talk/base/scoped_autorelease_pool.mm
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "talk/base/scoped_autorelease_pool.h"
+
+namespace talk_base {
+
+ScopedAutoreleasePool::ScopedAutoreleasePool() {
+ pool_ = [[NSAutoreleasePool alloc] init];
+}
+
+ScopedAutoreleasePool::~ScopedAutoreleasePool() {
+ [pool_ drain];
+}
+
+} // namespace talk_base
diff --git a/talk/base/scoped_ref_ptr.h b/talk/base/scoped_ref_ptr.h
new file mode 100644
index 0000000..3ce72cb
--- /dev/null
+++ b/talk/base/scoped_ref_ptr.h
@@ -0,0 +1,162 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Originally these classes are from Chromium.
+// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup
+
+//
+// A smart pointer class for reference counted objects. Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference. Sample usage:
+//
+// class MyFoo : public RefCounted<MyFoo> {
+// ...
+// };
+//
+// void some_function() {
+// scoped_refptr<MyFoo> foo = new MyFoo();
+// foo->Method(param);
+// // |foo| is released when this function returns
+// }
+//
+// void some_other_function() {
+// scoped_refptr<MyFoo> foo = new MyFoo();
+// ...
+// foo = NULL; // explicitly releases |foo|
+// ...
+// if (foo)
+// foo->Method(param);
+// }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+// {
+// scoped_refptr<MyFoo> a = new MyFoo();
+// scoped_refptr<MyFoo> b;
+//
+// b.swap(a);
+// // now, |b| references the MyFoo object, and |a| references NULL.
+// }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+// {
+// scoped_refptr<MyFoo> a = new MyFoo();
+// scoped_refptr<MyFoo> b;
+//
+// b = a;
+// // now, |a| and |b| each own a reference to the same MyFoo object.
+// }
+//
+
+#ifndef TALK_BASE_SCOPED_REF_PTR_H_
+#define TALK_BASE_SCOPED_REF_PTR_H_
+
+namespace talk_base {
+
+template <class T>
+class scoped_refptr {
+ public:
+ scoped_refptr() : ptr_(NULL) {
+ }
+
+ scoped_refptr(T* p) : ptr_(p) {
+ if (ptr_)
+ ptr_->AddRef();
+ }
+
+ scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
+ if (ptr_)
+ ptr_->AddRef();
+ }
+
+ template <typename U>
+ scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
+ if (ptr_)
+ ptr_->AddRef();
+ }
+
+ ~scoped_refptr() {
+ if (ptr_)
+ ptr_->Release();
+ }
+
+ T* get() const { return ptr_; }
+ operator T*() const { return ptr_; }
+ T* operator->() const { return ptr_; }
+
+ // Release a pointer.
+ // The return value is the current pointer held by this object.
+ // If this object holds a NULL pointer, the return value is NULL.
+ // After this operation, this object will hold a NULL pointer,
+ // and will not own the object any more.
+ T* release() {
+ T* retVal = ptr_;
+ ptr_ = NULL;
+ return retVal;
+ }
+
+ scoped_refptr<T>& operator=(T* p) {
+ // AddRef first so that self assignment should work
+ if (p)
+ p->AddRef();
+ if (ptr_ )
+ ptr_ ->Release();
+ ptr_ = p;
+ return *this;
+ }
+
+ scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
+ return *this = r.ptr_;
+ }
+
+ template <typename U>
+ scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
+ return *this = r.get();
+ }
+
+ void swap(T** pp) {
+ T* p = ptr_;
+ ptr_ = *pp;
+ *pp = p;
+ }
+
+ void swap(scoped_refptr<T>& r) {
+ swap(&r.ptr_);
+ }
+
+ protected:
+ T* ptr_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SCOPED_REF_PTR_H_
diff --git a/talk/base/sharedexclusivelock.cc b/talk/base/sharedexclusivelock.cc
new file mode 100644
index 0000000..0b0439a
--- /dev/null
+++ b/talk/base/sharedexclusivelock.cc
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/sharedexclusivelock.h"
+
+namespace talk_base {
+
+SharedExclusiveLock::SharedExclusiveLock()
+ : shared_count_is_zero_(true, true),
+ shared_count_(0) {
+}
+
+void SharedExclusiveLock::LockExclusive() {
+ cs_exclusive_.Enter();
+ shared_count_is_zero_.Wait(talk_base::kForever);
+}
+
+void SharedExclusiveLock::UnlockExclusive() {
+ cs_exclusive_.Leave();
+}
+
+void SharedExclusiveLock::LockShared() {
+ CritScope exclusive_scope(&cs_exclusive_);
+ CritScope shared_scope(&cs_shared_);
+ if (++shared_count_ == 1) {
+ shared_count_is_zero_.Reset();
+ }
+}
+
+void SharedExclusiveLock::UnlockShared() {
+ CritScope shared_scope(&cs_shared_);
+ if (--shared_count_ == 0) {
+ shared_count_is_zero_.Set();
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/sharedexclusivelock.h b/talk/base/sharedexclusivelock.h
new file mode 100644
index 0000000..2bdd854
--- /dev/null
+++ b/talk/base/sharedexclusivelock.h
@@ -0,0 +1,93 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SHAREDEXCLUSIVELOCK_H_
+#define TALK_BASE_SHAREDEXCLUSIVELOCK_H_
+
+#include "talk/base/constructormagic.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/event.h"
+
+namespace talk_base {
+
+// This class provides shared-exclusive lock. It can be used in cases like
+// multiple-readers/single-writer model.
+class SharedExclusiveLock {
+ public:
+ SharedExclusiveLock();
+
+ // Locking/unlocking methods. It is encouraged to use SharedScope or
+ // ExclusiveScope for protection.
+ void LockExclusive();
+ void UnlockExclusive();
+ void LockShared();
+ void UnlockShared();
+
+ private:
+ talk_base::CriticalSection cs_exclusive_;
+ talk_base::CriticalSection cs_shared_;
+ talk_base::Event shared_count_is_zero_;
+ int shared_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedExclusiveLock);
+};
+
+class SharedScope {
+ public:
+ explicit SharedScope(SharedExclusiveLock* lock) : lock_(lock) {
+ lock_->LockShared();
+ }
+
+ ~SharedScope() {
+ lock_->UnlockShared();
+ }
+
+ private:
+ SharedExclusiveLock* lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedScope);
+};
+
+class ExclusiveScope {
+ public:
+ explicit ExclusiveScope(SharedExclusiveLock* lock) : lock_(lock) {
+ lock_->LockExclusive();
+ }
+
+ ~ExclusiveScope() {
+ lock_->UnlockExclusive();
+ }
+
+ private:
+ SharedExclusiveLock* lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExclusiveScope);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SHAREDEXCLUSIVELOCK_H_
diff --git a/talk/base/sharedexclusivelock_unittest.cc b/talk/base/sharedexclusivelock_unittest.cc
new file mode 100644
index 0000000..46b7fdf
--- /dev/null
+++ b/talk/base/sharedexclusivelock_unittest.cc
@@ -0,0 +1,234 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sharedexclusivelock.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+static const uint32 kMsgRead = 0;
+static const uint32 kMsgWrite = 0;
+static const int kNoWaitThresholdInMs = 10;
+static const int kWaitThresholdInMs = 80;
+static const int kProcessTimeInMs = 100;
+static const int kProcessTimeoutInMs = 5000;
+
+class SharedExclusiveTask : public MessageHandler {
+ public:
+ SharedExclusiveTask(SharedExclusiveLock* shared_exclusive_lock,
+ int* value,
+ bool* done)
+ : shared_exclusive_lock_(shared_exclusive_lock),
+ waiting_time_in_ms_(0),
+ value_(value),
+ done_(done) {
+ worker_thread_.reset(new Thread());
+ worker_thread_->Start();
+ }
+
+ int waiting_time_in_ms() const { return waiting_time_in_ms_; }
+
+ protected:
+ scoped_ptr<Thread> worker_thread_;
+ SharedExclusiveLock* shared_exclusive_lock_;
+ int waiting_time_in_ms_;
+ int* value_;
+ bool* done_;
+};
+
+class ReadTask : public SharedExclusiveTask {
+ public:
+ ReadTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done)
+ : SharedExclusiveTask(shared_exclusive_lock, value, done) {
+ }
+
+ void PostRead(int* value) {
+ worker_thread_->Post(this, kMsgRead, new TypedMessageData<int*>(value));
+ }
+
+ private:
+ virtual void OnMessage(Message* message) {
+ ASSERT(talk_base::Thread::Current() == worker_thread_.get());
+ ASSERT(message != NULL);
+ ASSERT(message->message_id == kMsgRead);
+
+ TypedMessageData<int*>* message_data =
+ static_cast<TypedMessageData<int*>*>(message->pdata);
+
+ uint32 start_time = Time();
+ {
+ SharedScope ss(shared_exclusive_lock_);
+ waiting_time_in_ms_ = TimeDiff(Time(), start_time);
+
+ Thread::SleepMs(kProcessTimeInMs);
+ *message_data->data() = *value_;
+ *done_ = true;
+ }
+ delete message->pdata;
+ message->pdata = NULL;
+ }
+};
+
+class WriteTask : public SharedExclusiveTask {
+ public:
+ WriteTask(SharedExclusiveLock* shared_exclusive_lock, int* value, bool* done)
+ : SharedExclusiveTask(shared_exclusive_lock, value, done) {
+ }
+
+ void PostWrite(int value) {
+ worker_thread_->Post(this, kMsgWrite, new TypedMessageData<int>(value));
+ }
+
+ private:
+ virtual void OnMessage(Message* message) {
+ ASSERT(talk_base::Thread::Current() == worker_thread_.get());
+ ASSERT(message != NULL);
+ ASSERT(message->message_id == kMsgWrite);
+
+ TypedMessageData<int>* message_data =
+ static_cast<TypedMessageData<int>*>(message->pdata);
+
+ uint32 start_time = Time();
+ {
+ ExclusiveScope es(shared_exclusive_lock_);
+ waiting_time_in_ms_ = TimeDiff(Time(), start_time);
+
+ Thread::SleepMs(kProcessTimeInMs);
+ *value_ = message_data->data();
+ *done_ = true;
+ }
+ delete message->pdata;
+ message->pdata = NULL;
+ }
+};
+
+// Unit test for SharedExclusiveLock.
+class SharedExclusiveLockTest
+ : public testing::Test {
+ public:
+ SharedExclusiveLockTest() : value_(0) {
+ }
+
+ virtual void SetUp() {
+ shared_exclusive_lock_.reset(new SharedExclusiveLock());
+ }
+
+ protected:
+ scoped_ptr<SharedExclusiveLock> shared_exclusive_lock_;
+ int value_;
+};
+
+TEST_F(SharedExclusiveLockTest, TestSharedShared) {
+ int value0, value1;
+ bool done0, done1;
+ ReadTask reader0(shared_exclusive_lock_.get(), &value_, &done0);
+ ReadTask reader1(shared_exclusive_lock_.get(), &value_, &done1);
+
+ // Test shared locks can be shared without waiting.
+ {
+ SharedScope ss(shared_exclusive_lock_.get());
+ value_ = 1;
+ done0 = false;
+ done1 = false;
+ reader0.PostRead(&value0);
+ reader1.PostRead(&value1);
+ Thread::SleepMs(kProcessTimeInMs);
+ }
+
+ EXPECT_TRUE_WAIT(done0, kProcessTimeoutInMs);
+ EXPECT_EQ(1, value0);
+ EXPECT_LE(reader0.waiting_time_in_ms(), kNoWaitThresholdInMs);
+ EXPECT_TRUE_WAIT(done1, kProcessTimeoutInMs);
+ EXPECT_EQ(1, value1);
+ EXPECT_LE(reader1.waiting_time_in_ms(), kNoWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestSharedExclusive) {
+ bool done;
+ WriteTask writer(shared_exclusive_lock_.get(), &value_, &done);
+
+ // Test exclusive lock needs to wait for shared lock.
+ {
+ SharedScope ss(shared_exclusive_lock_.get());
+ value_ = 1;
+ done = false;
+ writer.PostWrite(2);
+ Thread::SleepMs(kProcessTimeInMs);
+ EXPECT_EQ(1, value_);
+ }
+
+ EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+ EXPECT_EQ(2, value_);
+ EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestExclusiveShared) {
+ int value;
+ bool done;
+ ReadTask reader(shared_exclusive_lock_.get(), &value_, &done);
+
+ // Test shared lock needs to wait for exclusive lock.
+ {
+ ExclusiveScope es(shared_exclusive_lock_.get());
+ value_ = 1;
+ done = false;
+ reader.PostRead(&value);
+ Thread::SleepMs(kProcessTimeInMs);
+ value_ = 2;
+ }
+
+ EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+ EXPECT_EQ(2, value);
+ EXPECT_GE(reader.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+TEST_F(SharedExclusiveLockTest, TestExclusiveExclusive) {
+ bool done;
+ WriteTask writer(shared_exclusive_lock_.get(), &value_, &done);
+
+ // Test exclusive lock needs to wait for exclusive lock.
+ {
+ ExclusiveScope es(shared_exclusive_lock_.get());
+ value_ = 1;
+ done = false;
+ writer.PostWrite(2);
+ Thread::SleepMs(kProcessTimeInMs);
+ EXPECT_EQ(1, value_);
+ }
+
+ EXPECT_TRUE_WAIT(done, kProcessTimeoutInMs);
+ EXPECT_EQ(2, value_);
+ EXPECT_GE(writer.waiting_time_in_ms(), kWaitThresholdInMs);
+}
+
+} // namespace talk_base
diff --git a/talk/base/socket.h b/talk/base/socket.h
index a55b3dc..ad8eee6 100644
--- a/talk/base/socket.h
+++ b/talk/base/socket.h
@@ -180,9 +180,10 @@
enum Option {
OPT_DONTFRAGMENT,
- OPT_RCVBUF, // receive buffer size
- OPT_SNDBUF, // send buffer size
- OPT_NODELAY // whether Nagle algorithm is enabled
+ OPT_RCVBUF, // receive buffer size
+ OPT_SNDBUF, // send buffer size
+ OPT_NODELAY, // whether Nagle algorithm is enabled
+ OPT_IPV6_V6ONLY // Whether the socket is IPv6 only.
};
virtual int GetOption(Option opt, int* value) = 0;
virtual int SetOption(Option opt, int value) = 0;
diff --git a/talk/base/socket_unittest.cc b/talk/base/socket_unittest.cc
new file mode 100644
index 0000000..8d5b3c5
--- /dev/null
+++ b/talk/base/socket_unittest.cc
@@ -0,0 +1,731 @@
+/*
+ * libjingle
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/socket_unittest.h"
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+static const SocketAddress kEmptyAddr;
+static const SocketAddress kLoopbackAddr(IPAddress(INADDR_LOOPBACK), 0);
+
+void SocketTest::TestConnect() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState());
+ EXPECT_EQ(kEmptyAddr, client->GetLocalAddress());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+ EXPECT_EQ(AsyncSocket::CS_CONNECTING, server->GetState());
+
+ // Ensure no pending server connections, since we haven't done anything yet.
+ EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+ EXPECT_EQ(kEmptyAddr, accept_addr);
+
+ // Attempt connect to listening socket.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+ EXPECT_NE(kEmptyAddr, client->GetLocalAddress()); // Implicit Bind
+ EXPECT_NE(server->GetLocalAddress(), client->GetLocalAddress());
+
+ // Client is connecting, outcome not yet determined.
+ EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+ // Server has pending connection, accept it.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ EXPECT_NE(kEmptyAddr, accept_addr);
+ EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+ // Connected from server perspective, check the addresses are correct.
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+ EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+ // Connected from client perspective, check the addresses are correct.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+}
+
+void SocketTest::TestConnectWithDnsLookup() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connect to listening socket.
+ SocketAddress dns_addr(server->GetLocalAddress());
+ dns_addr.SetIP("localhost");
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+ // TODO: Bind when doing DNS lookup.
+ //EXPECT_NE(kEmptyAddr, client->GetLocalAddress()); // Implicit Bind
+
+ // Client is connecting, outcome not yet determined.
+ EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+ // Server has pending connection, accept it.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ EXPECT_NE(kEmptyAddr, accept_addr);
+ EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+ // Connected from server perspective, check the addresses are correct.
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+ EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+ // Connected from client perspective, check the addresses are correct.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+}
+
+void SocketTest::TestConnectFail() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server, but don't listen yet.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+
+ // Attempt connect to a non-existent socket.
+ // We don't connect to the server socket created above, since on
+ // MacOS it takes about 75 seconds to get back an error!
+ SocketAddress bogus_addr(IPAddress(INADDR_LOOPBACK), 65535);
+ EXPECT_EQ(0, client->Connect(bogus_addr));
+
+ // Wait for connection to fail (ECONNREFUSED).
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+ EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+
+ // Should be no pending server connections.
+ EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+ EXPECT_EQ(kEmptyAddr, accept_addr);
+}
+
+void SocketTest::TestConnectWithDnsLookupFail() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server, but don't listen yet.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+
+ // Attempt connect to a non-existent host.
+ // We don't connect to the server socket created above, since on
+ // MacOS it takes about 75 seconds to get back an error!
+ SocketAddress bogus_dns_addr("not-a-real-hostname", 65535);
+ EXPECT_EQ(0, client->Connect(bogus_dns_addr));
+
+ // Wait for connection to fail (EHOSTNOTFOUND).
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+ EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+
+ // Should be no pending server connections.
+ EXPECT_FALSE(sink.Check(server.get(), testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+ EXPECT_EQ(kEmptyAddr, accept_addr);
+}
+
+void SocketTest::TestConnectWithClosedSocket() {
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Create a client and put in to CS_CLOSED state.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ EXPECT_EQ(0, client->Close());
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, client->GetState());
+
+ // Connect() should reinitialize the socket, and put it in to CS_CONNECTING.
+ EXPECT_EQ(0, client->Connect(SocketAddress(server->GetLocalAddress())));
+ EXPECT_EQ(AsyncSocket::CS_CONNECTING, client->GetState());
+}
+
+void SocketTest::TestServerCloseDuringConnect() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connect to listening socket.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Close down the server while the socket is in the accept queue.
+ EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout);
+ server->Close();
+
+ // This should fail the connection for the client. Clean up.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_ERROR));
+ client->Close();
+}
+
+void SocketTest::TestClientCloseDuringConnect() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connect to listening socket.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Close down the client while the socket is in the accept queue.
+ EXPECT_TRUE_WAIT(sink.Check(server.get(), testing::SSE_READ), kTimeout);
+ client->Close();
+
+ // The connection should still be able to be accepted.
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+
+ // The accepted socket should then close (possibly with err, timing-related)
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, accepted->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(accepted.get(), testing::SSE_CLOSE) ||
+ sink.Check(accepted.get(), testing::SSE_ERROR));
+
+ // The client should not get a close event.
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+}
+
+void SocketTest::TestServerClose() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connection.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Accept connection.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+
+ // Both sides are now connected.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+ // Send data to the client, and then close the connection.
+ EXPECT_EQ(1, accepted->Send("a", 1));
+ accepted->Close();
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState());
+
+ // Expect that the client is notified, and has not yet closed.
+ EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState());
+
+ // Ensure the data can be read.
+ char buffer[10];
+ EXPECT_EQ(1, client->Recv(buffer, sizeof(buffer)));
+ EXPECT_EQ('a', buffer[0]);
+
+ // Now we should close, but the remote address will remain.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_NE(kEmptyAddr, client->GetRemoteAddress());
+
+ // The closer should not get a close signal.
+ EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(kEmptyAddr, accepted->GetRemoteAddress());
+
+ // And the closee should only get a single signal.
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+
+ // Close down the client and ensure all is good.
+ client->Close();
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(kEmptyAddr, client->GetRemoteAddress());
+}
+
+class SocketCloser : public sigslot::has_slots<> {
+ public:
+ void OnClose(AsyncSocket* socket, int error) {
+ socket->Close(); // Deleting here would blow up the vector of handlers
+ // for the socket's signal.
+ }
+};
+
+void SocketTest::TestCloseInClosedCallback() {
+ testing::StreamSink sink;
+ SocketCloser closer;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+ client->SignalCloseEvent.connect(&closer, &SocketCloser::OnClose);
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connection.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Accept connection.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+
+ // Both sides are now connected.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+ // Send data to the client, and then close the connection.
+ accepted->Close();
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, accepted->GetState());
+
+ // Expect that the client is notified, and has not yet closed.
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, client->GetState());
+
+ // Now we should be closed and invalidated
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_TRUE(Socket::CS_CLOSED == client->GetState());
+}
+
+class Sleeper : public MessageHandler {
+ public:
+ Sleeper() {}
+ void OnMessage(Message* msg) {
+ Thread::Current()->SleepMs(500);
+ }
+};
+
+void SocketTest::TestSocketServerWait() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create & connect server and client sockets.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, accepted->GetState());
+ EXPECT_EQ(server->GetLocalAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(client->GetLocalAddress(), accepted->GetRemoteAddress());
+
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client.get(), testing::SSE_CLOSE));
+ EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+
+ // Do an i/o operation, triggering an eventual callback.
+ EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+ char buf[1024] = {0};
+
+ EXPECT_EQ(1024, client->Send(buf, 1024));
+ EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+
+ // Shouldn't signal when blocked in a thread Send, where process_io is false.
+ scoped_ptr<Thread> thread(new Thread());
+ thread->Start();
+ Sleeper sleeper;
+ TypedMessageData<AsyncSocket*> data(client.get());
+ thread->Send(&sleeper, 0, &data);
+ EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_READ));
+
+ // But should signal when process_io is true.
+ EXPECT_TRUE_WAIT((sink.Check(accepted.get(), testing::SSE_READ)), kTimeout);
+ EXPECT_LT(0, accepted->Recv(buf, 1024));
+}
+
+void SocketTest::TestTcp() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create test data.
+ const size_t kDataSize = 1024 * 1024;
+ scoped_array<char> send_buffer(new char[kDataSize]);
+ scoped_array<char> recv_buffer(new char[kDataSize]);
+ size_t send_pos = 0, recv_pos = 0;
+ for (size_t i = 0; i < kDataSize; ++i) {
+ send_buffer[i] = i;
+ recv_buffer[i] = 0;
+ }
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connection.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Accept connection.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+
+ // Both sides are now connected.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+ // Send and receive a bunch of data.
+ bool send_waiting_for_writability = false;
+ bool send_expect_success = true;
+ bool recv_waiting_for_readability = true;
+ bool recv_expect_success = false;
+ int data_in_flight = 0;
+ while (recv_pos < kDataSize) {
+ // Send as much as we can if we've been cleared to send.
+ while (!send_waiting_for_writability && send_pos < kDataSize) {
+ int tosend = kDataSize - send_pos;
+ int sent = accepted->Send(send_buffer.get() + send_pos, tosend);
+ if (send_expect_success) {
+ // The first Send() after connecting or getting writability should
+ // succeed and send some data.
+ EXPECT_GT(sent, 0);
+ send_expect_success = false;
+ }
+ if (sent >= 0) {
+ EXPECT_LE(sent, tosend);
+ send_pos += sent;
+ data_in_flight += sent;
+ } else {
+ ASSERT_TRUE(accepted->IsBlocking());
+ send_waiting_for_writability = true;
+ }
+ }
+
+ // Read all the sent data.
+ while (data_in_flight > 0) {
+ if (recv_waiting_for_readability) {
+ // Wait until data is available.
+ EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+ recv_waiting_for_readability = false;
+ recv_expect_success = true;
+ }
+
+ // Receive as much as we can get in a single recv call.
+ int rcvd = client->Recv(recv_buffer.get() + recv_pos,
+ kDataSize - recv_pos);
+
+ if (recv_expect_success) {
+ // The first Recv() after getting readability should succeed and receive
+ // some data.
+ EXPECT_GT(rcvd, 0);
+ recv_expect_success = false;
+ }
+ if (rcvd >= 0) {
+ EXPECT_LE(rcvd, data_in_flight);
+ recv_pos += rcvd;
+ data_in_flight -= rcvd;
+ } else {
+ ASSERT_TRUE(client->IsBlocking());
+ recv_waiting_for_readability = true;
+ }
+ }
+
+ // Once all that we've sent has been rcvd, expect to be able to send again.
+ if (send_waiting_for_writability) {
+ EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE),
+ kTimeout);
+ send_waiting_for_writability = false;
+ send_expect_success = true;
+ }
+ }
+
+ // The received data matches the sent data.
+ EXPECT_EQ(kDataSize, send_pos);
+ EXPECT_EQ(kDataSize, recv_pos);
+ EXPECT_EQ(0, memcmp(recv_buffer.get(), send_buffer.get(), kDataSize));
+
+ // Close down.
+ accepted->Close();
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CLOSED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_CLOSE));
+ client->Close();
+}
+
+void SocketTest::TestSingleFlowControlCallback() {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+
+ // Create client.
+ scoped_ptr<AsyncSocket> client(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(client.get());
+
+ // Create server and listen.
+ scoped_ptr<AsyncSocket> server(ss_->CreateAsyncSocket(SOCK_STREAM));
+ sink.Monitor(server.get());
+ EXPECT_EQ(0, server->Bind(kLoopbackAddr));
+ EXPECT_EQ(0, server->Listen(5));
+
+ // Attempt connection.
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Accept connection.
+ EXPECT_TRUE_WAIT((sink.Check(server.get(), testing::SSE_READ)), kTimeout);
+ scoped_ptr<AsyncSocket> accepted(server->Accept(&accept_addr));
+ ASSERT_TRUE(NULL != accepted.get());
+ sink.Monitor(accepted.get());
+
+ // Both sides are now connected.
+ EXPECT_EQ_WAIT(AsyncSocket::CS_CONNECTED, client->GetState(), kTimeout);
+ EXPECT_TRUE(sink.Check(client.get(), testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+ EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+ // Fill the socket buffer.
+ char buf[1024 * 16] = {0};
+ while (accepted->Send(&buf, ARRAY_SIZE(buf)) != -1) {}
+ EXPECT_TRUE(accepted->IsBlocking());
+
+ // Expect no writable callbacks
+ EXPECT_FALSE(sink.Check(accepted.get(), testing::SSE_WRITE));
+
+ // Wait until data is available.
+ EXPECT_TRUE_WAIT(sink.Check(client.get(), testing::SSE_READ), kTimeout);
+
+ // Pull some data.
+ client->Recv(buf, ARRAY_SIZE(buf));
+
+ // Expect at least one additional writable callback.
+ EXPECT_TRUE_WAIT(sink.Check(accepted.get(), testing::SSE_WRITE), kTimeout);
+
+ // Adding data in response to the writeable callback shouldn't cause infinite
+ // callbacks.
+ int extras = 0;
+ for (int i = 0; i < 100; ++i) {
+ accepted->Send(&buf, ARRAY_SIZE(buf));
+ talk_base::Thread::Current()->ProcessMessages(1);
+ if (sink.Check(accepted.get(), testing::SSE_WRITE)) {
+ extras++;
+ }
+ }
+ EXPECT_LT(extras, 2);
+
+ // Close down.
+ accepted->Close();
+ client->Close();
+}
+
+void SocketTest::TestUdp() {
+ // Test basic bind and connect behavior.
+ SocketAddress addr1(kLoopbackAddr);
+ AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState());
+ EXPECT_EQ(0, socket->Bind(addr1));
+ addr1 = socket->GetLocalAddress();
+ EXPECT_EQ(0, socket->Connect(addr1));
+ EXPECT_EQ(AsyncSocket::CS_CONNECTED, socket->GetState());
+ socket->Close();
+ EXPECT_EQ(AsyncSocket::CS_CLOSED, socket->GetState());
+ delete socket;
+
+ // Test send/receive behavior.
+ scoped_ptr<TestClient> client1(new TestClient(
+ AsyncUDPSocket::Create(ss_, addr1)));
+ scoped_ptr<TestClient> client2(new TestClient(
+ AsyncUDPSocket::Create(ss_, SocketAddress())));
+
+ SocketAddress addr2;
+ EXPECT_EQ(3, client2->SendTo("foo", 3, addr1));
+ EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr2));
+
+ SocketAddress addr3;
+ EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr2));
+ EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr3));
+ EXPECT_EQ(addr3, addr1);
+
+ // TODO: figure out what the intent is here
+ for (int i = 0; i < 10; ++i) {
+ client2.reset(new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress())));
+
+ SocketAddress addr4;
+ EXPECT_EQ(3, client2->SendTo("foo", 3, addr1));
+ EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &addr4));
+ EXPECT_EQ(addr4.ipaddr(), addr2.ipaddr());
+
+ SocketAddress addr5;
+ EXPECT_EQ(6, client1->SendTo("bizbaz", 6, addr4));
+ EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &addr5));
+ EXPECT_EQ(addr5, addr1);
+
+ addr2 = addr4;
+ }
+}
+
+void SocketTest::TestGetSetOptions() {
+ talk_base::scoped_ptr<AsyncSocket> socket(ss_->CreateAsyncSocket(SOCK_DGRAM));
+ socket->Bind(kLoopbackAddr);
+
+ // Check SNDBUF/RCVBUF.
+ const int desired_size = 12345;
+#if defined(LINUX) || defined(ANDROID)
+ // Yes, really. It's in the kernel source.
+ const int expected_size = desired_size * 2;
+#else // !LINUX && !ANDROID
+ const int expected_size = desired_size;
+#endif // !LINUX && !ANDROID
+ int recv_size = 0;
+ int send_size = 0;
+ // get the initial sizes
+ ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size));
+ ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size));
+ // set our desired sizes
+ ASSERT_NE(-1, socket->SetOption(Socket::OPT_RCVBUF, desired_size));
+ ASSERT_NE(-1, socket->SetOption(Socket::OPT_SNDBUF, desired_size));
+ // get the sizes again
+ ASSERT_NE(-1, socket->GetOption(Socket::OPT_RCVBUF, &recv_size));
+ ASSERT_NE(-1, socket->GetOption(Socket::OPT_SNDBUF, &send_size));
+ // make sure they are right
+ ASSERT_EQ(expected_size, recv_size);
+ ASSERT_EQ(expected_size, send_size);
+
+ // Check that we can't set NODELAY on a UDP socket.
+ int current_nd, desired_nd = 1;
+ ASSERT_EQ(-1, socket->GetOption(Socket::OPT_NODELAY, ¤t_nd));
+ ASSERT_EQ(-1, socket->SetOption(Socket::OPT_NODELAY, desired_nd));
+
+ // Try estimating MTU.
+ talk_base::scoped_ptr<AsyncSocket>
+ mtu_socket(ss_->CreateAsyncSocket(SOCK_DGRAM));
+ mtu_socket->Bind(kLoopbackAddr);
+ uint16 mtu;
+ // should fail until we connect
+ ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu));
+ mtu_socket->Connect(kLoopbackAddr);
+#if defined(WIN32)
+ // now it should succeed
+ ASSERT_NE(-1, mtu_socket->EstimateMTU(&mtu));
+ ASSERT_GE(mtu, 1492); // should be at least the 1492 "plateau" on localhost
+#elif defined(OSX)
+ // except on OSX, where it's not yet implemented
+ ASSERT_EQ(-1, mtu_socket->EstimateMTU(&mtu));
+#else
+ // and the behavior seems unpredictable on Linux, failing on the build machine
+ // but succeeding on my Ubiquity instance.
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/socket_unittest.h b/talk/base/socket_unittest.h
new file mode 100644
index 0000000..481f073
--- /dev/null
+++ b/talk/base/socket_unittest.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SOCKET_UNITTEST_H_
+#define TALK_BASE_SOCKET_UNITTEST_H_
+
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Generic socket tests, to be used when testing individual socketservers.
+// Derive your specific test class from SocketTest, install your
+// socketserver, and call the SocketTest test methods.
+class SocketTest : public testing::Test {
+ protected:
+ SocketTest() : ss_(NULL) {}
+ virtual void SetUp() { ss_ = Thread::Current()->socketserver(); }
+ void TestConnect();
+ void TestConnectWithDnsLookup();
+ void TestConnectFail();
+ void TestConnectWithDnsLookupFail();
+ void TestConnectWithClosedSocket();
+ void TestServerCloseDuringConnect();
+ void TestClientCloseDuringConnect();
+ void TestServerClose();
+ void TestCloseInClosedCallback();
+ void TestSocketServerWait();
+ void TestTcp();
+ void TestSingleFlowControlCallback();
+ void TestUdp();
+ void TestGetSetOptions();
+
+ static const int kTimeout = 5000; // ms
+ SocketServer* ss_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKET_UNITTEST_H_
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
index cbb0805..a4c7275 100644
--- a/talk/base/socketaddress.cc
+++ b/talk/base/socketaddress.cc
@@ -25,10 +25,15 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/base/socketaddress.h"
+
#ifdef POSIX
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#if defined(OPENBSD)
+#include <netinet/in_systm.h>
+#endif
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
@@ -41,22 +46,17 @@
#include "talk/base/common.h"
#include "talk/base/logging.h"
#include "talk/base/nethelpers.h"
-#include "talk/base/socketaddress.h"
#ifdef WIN32
-// Win32 doesn't provide inet_aton, so we add our own version here.
-// Since inet_addr returns 0xFFFFFFFF on error, if we get this value
-// we need to test the input to see if the address really was 255.255.255.255.
-// This is slightly fragile, but better than doing nothing.
-int inet_aton(const char* cp, struct in_addr* inp) {
- inp->s_addr = inet_addr(cp);
- return (inp->s_addr == INADDR_NONE &&
- strcmp(cp, "255.255.255.255") != 0) ? 0 : 1;
-}
-#endif // WIN32
+#include "talk/base/win32.h"
+#endif
namespace talk_base {
+// Address family constants for STUN (see RFC 5389).
+static const int kStunFamilyIPv4 = 1;
+static const int kStunFamilyIPv6 = 2;
+
SocketAddress::SocketAddress() {
Clear();
}
@@ -66,7 +66,12 @@
SetPort(port);
}
-SocketAddress::SocketAddress(uint32 ip, int port) {
+SocketAddress::SocketAddress(uint32 ip_as_host_order_integer, int port) {
+ SetIP(IPAddress(ip_as_host_order_integer));
+ SetPort(port);
+}
+
+SocketAddress::SocketAddress(const IPAddress& ip, int port) {
SetIP(ip);
SetPort(port);
}
@@ -77,36 +82,52 @@
void SocketAddress::Clear() {
hostname_.clear();
- ip_ = 0;
+ literal_ = false;
+ ip_ = IPAddress(INADDR_ANY);
port_ = 0;
}
bool SocketAddress::IsNil() const {
- return hostname_.empty() && (0 == ip_) && (0 == port_);
+ return hostname_.empty() && IPIsAny(ip_) && 0 == port_;
}
bool SocketAddress::IsComplete() const {
- return (0 != ip_) && (0 != port_);
+ return (!IPIsAny(ip_)) && (0 != port_);
}
SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
hostname_ = addr.hostname_;
ip_ = addr.ip_;
port_ = addr.port_;
+ literal_ = addr.literal_;
return *this;
}
-void SocketAddress::SetIP(uint32 ip) {
+void SocketAddress::SetIP(uint32 ip_as_host_order_integer) {
hostname_.clear();
+ literal_ = false;
+ ip_ = IPAddress(ip_as_host_order_integer);
+}
+
+void SocketAddress::SetIP(const IPAddress& ip) {
+ hostname_.clear();
+ literal_ = false;
ip_ = ip;
}
void SocketAddress::SetIP(const std::string& hostname) {
hostname_ = hostname;
- ip_ = StringToIP(hostname);
+ literal_ = IPFromString(hostname, &ip_);
+ if (!literal_) {
+ ip_ = IPAddress(INADDR_ANY);
+ }
}
-void SocketAddress::SetResolvedIP(uint32 ip) {
+void SocketAddress::SetResolvedIP(uint32 ip_as_host_order_integer) {
+ ip_ = IPAddress(ip_as_host_order_integer);
+}
+
+void SocketAddress::SetResolvedIP(const IPAddress& ip) {
ip_ = ip;
}
@@ -116,6 +137,10 @@
}
uint32 SocketAddress::ip() const {
+ return ip_.v4AddressAsHostOrderInteger();
+}
+
+const IPAddress& SocketAddress::ipaddr() const {
return ip_;
}
@@ -124,9 +149,15 @@
}
std::string SocketAddress::IPAsString() const {
- if (!hostname_.empty())
+ // If the hostname was a literal IP string, it may need to have square
+ // brackets added (for SocketAddress::ToString()).
+ if (!literal_ && !hostname_.empty())
return hostname_;
- return IPToString(ip_);
+ if (ip_.family() == AF_INET6) {
+ return "[" + ip_.ToString() + "]";
+ } else {
+ return ip_.ToString();
+ }
}
std::string SocketAddress::PortAsString() const {
@@ -144,11 +175,24 @@
}
bool SocketAddress::FromString(const std::string& str) {
- std::string::size_type pos = str.find(':');
- if (std::string::npos == pos)
- return false;
- SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
- SetIP(str.substr(0, pos));
+ if (str.at(0) == '[') {
+ std::string::size_type closebracket = str.rfind(']');
+ if (closebracket != std::string::npos) {
+ std::string::size_type colon = str.find(':', closebracket);
+ if (colon != std::string::npos && colon > closebracket) {
+ SetPort(strtoul(str.substr(colon + 1).c_str(), NULL, 10));
+ SetIP(str.substr(1, closebracket - 1));
+ } else {
+ return false;
+ }
+ }
+ } else {
+ std::string::size_type pos = str.find(':');
+ if (std::string::npos == pos)
+ return false;
+ SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
+ SetIP(str.substr(0, pos));
+ }
return true;
}
@@ -158,28 +202,25 @@
}
bool SocketAddress::IsAnyIP() const {
- return (ip_ == 0);
+ return IPIsAny(ip_);
}
bool SocketAddress::IsLoopbackIP() const {
- if (0 == ip_) {
- return (0 == stricmp(hostname_.c_str(), "localhost"));
- } else {
- return ((ip_ >> 24) == 127);
- }
+ return IPIsLoopback(ip_) || (IPIsAny(ip_) &&
+ 0 == strcmp(hostname_.c_str(), "localhost"));
}
bool SocketAddress::IsLocalIP() const {
if (IsLoopbackIP())
return true;
- std::vector<uint32> ips;
- if (0 == ip_) {
+ std::vector<IPAddress> ips;
+ if (IPIsAny(ip_)) {
if (!hostname_.empty()
&& (0 == stricmp(hostname_.c_str(), GetHostname().c_str()))) {
return true;
}
- } else if (GetLocalIPs(ips)) {
+ } else if (GetLocalIPs(&ips)) {
for (size_t i = 0; i < ips.size(); ++i) {
if (ips[i] == ip_) {
return true;
@@ -190,15 +231,11 @@
}
bool SocketAddress::IsPrivateIP() const {
- return ((ip_ >> 24) == 127) ||
- ((ip_ >> 24) == 10) ||
- ((ip_ >> 20) == ((172 << 4) | 1)) ||
- ((ip_ >> 16) == ((192 << 8) | 168)) ||
- ((ip_ >> 16) == ((169 << 8) | 254));
+ return IPIsPrivate(ip_);
}
bool SocketAddress::IsUnresolvedIP() const {
- return IsAny() && !hostname_.empty();
+ return IsAny() && !literal_ && !hostname_.empty();
}
bool SocketAddress::ResolveIP(bool force, int* error) {
@@ -210,9 +247,10 @@
LOG_F(LS_VERBOSE) << "(" << hostname_ << ")";
int errcode = 0;
if (hostent* pHost = SafeGetHostByName(hostname_.c_str(), &errcode)) {
- ip_ = NetworkToHost32(*reinterpret_cast<uint32*>(pHost->h_addr_list[0]));
- LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
- << IPToString(ip_);
+ if (IPFromHostEnt(pHost, &ip_)) {
+ LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
+ << ip_.ToString();
+ }
FreeHostEnt(pHost);
} else {
LOG_F(LS_ERROR) << "(" << hostname_ << ") err: " << errcode;
@@ -221,7 +259,7 @@
*error = errcode;
}
}
- return (ip_ != 0);
+ return (!IPIsAny(ip_));
}
bool SocketAddress::operator==(const SocketAddress& addr) const {
@@ -235,7 +273,7 @@
return false;
// We only check hostnames if both IPs are zero. This matches EqualIPs()
- if (addr.ip_ == 0) {
+ if (addr.IsAnyIP()) {
if (hostname_ < addr.hostname_)
return true;
else if (addr.hostname_ < hostname_)
@@ -246,7 +284,8 @@
}
bool SocketAddress::EqualIPs(const SocketAddress& addr) const {
- return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_));
+ return (ip_ == addr.ip_) &&
+ ((!IPIsAny(ip_)) || (hostname_ == addr.hostname_));
}
bool SocketAddress::EqualPorts(const SocketAddress& addr) const {
@@ -255,41 +294,23 @@
size_t SocketAddress::Hash() const {
size_t h = 0;
- h ^= ip_;
+ h ^= HashIP(ip_);
h ^= port_ | (port_ << 16);
return h;
}
-size_t SocketAddress::Size_() const {
- return sizeof(ip_) + sizeof(port_) + 2;
-}
-
-bool SocketAddress::Write_(char* buf, int len) const {
- if (len < static_cast<int>(Size_()))
- return false;
- buf[0] = 0;
- buf[1] = AF_INET;
- SetBE16(buf + 2, port_);
- SetBE32(buf + 4, ip_);
- return true;
-}
-
-bool SocketAddress::Read_(const char* buf, int len) {
- if (len < static_cast<int>(Size_()) || buf[1] != AF_INET)
- return false;
- port_ = GetBE16(buf + 2);
- ip_ = GetBE32(buf + 4);
- return true;
-}
-
void SocketAddress::ToSockAddr(sockaddr_in* saddr) const {
memset(saddr, 0, sizeof(*saddr));
+ if (ip_.family() != AF_INET) {
+ saddr->sin_family = AF_UNSPEC;
+ return;
+ }
saddr->sin_family = AF_INET;
saddr->sin_port = HostToNetwork16(port_);
- if (0 == ip_) {
+ if (IPIsAny(ip_)) {
saddr->sin_addr.s_addr = INADDR_ANY;
} else {
- saddr->sin_addr.s_addr = HostToNetwork32(ip_);
+ saddr->sin_addr = ip_.ipv4_address();
}
}
@@ -298,29 +319,76 @@
return false;
SetIP(NetworkToHost32(saddr.sin_addr.s_addr));
SetPort(NetworkToHost16(saddr.sin_port));
+ literal_ = false;
return true;
}
-std::string SocketAddress::IPToString(uint32 ip) {
+static size_t ToSockAddrStorageHelper(sockaddr_storage* addr,
+ IPAddress ip, int port, int scope_id) {
+ memset(addr, 0, sizeof(sockaddr_storage));
+ addr->ss_family = ip.family();
+ if (addr->ss_family == AF_INET6) {
+ sockaddr_in6* saddr = reinterpret_cast<sockaddr_in6*>(addr);
+ saddr->sin6_addr = ip.ipv6_address();
+ saddr->sin6_port = HostToNetwork16(port);
+ saddr->sin6_scope_id = scope_id;
+ return sizeof(sockaddr_in6);
+ } else if (addr->ss_family == AF_INET) {
+ sockaddr_in* saddr = reinterpret_cast<sockaddr_in*>(addr);
+ saddr->sin_addr = ip.ipv4_address();
+ saddr->sin_port = HostToNetwork16(port);
+ return sizeof(sockaddr_in);
+ }
+ return 0;
+}
+
+size_t SocketAddress::ToDualStackSockAddrStorage(sockaddr_storage *addr) const {
+ return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_, scope_id_);
+}
+
+size_t SocketAddress::ToSockAddrStorage(sockaddr_storage* addr) const {
+ return ToSockAddrStorageHelper(addr, ip_, port_, scope_id_);
+}
+
+std::string SocketAddress::IPToString(uint32 ip_as_host_order_integer) {
std::ostringstream ost;
- ost << ((ip >> 24) & 0xff);
+ ost << ((ip_as_host_order_integer >> 24) & 0xff);
ost << '.';
- ost << ((ip >> 16) & 0xff);
+ ost << ((ip_as_host_order_integer >> 16) & 0xff);
ost << '.';
- ost << ((ip >> 8) & 0xff);
+ ost << ((ip_as_host_order_integer >> 8) & 0xff);
ost << '.';
- ost << ((ip >> 0) & 0xff);
+ ost << ((ip_as_host_order_integer >> 0) & 0xff);
return ost.str();
}
bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) {
in_addr addr;
- if (inet_aton(hostname.c_str(), &addr) == 0)
+ if (talk_base::inet_pton(AF_INET, hostname.c_str(), &addr) == 0)
return false;
*ip = NetworkToHost32(addr.s_addr);
return true;
}
+bool SocketAddress::StringToIP(const std::string& hostname, IPAddress* ip) {
+ in_addr addr4;
+ if (talk_base::inet_pton(AF_INET, hostname.c_str(), &addr4) > 0) {
+ if (ip) {
+ *ip = IPAddress(addr4);
+ }
+ return true;
+ }
+
+ in6_addr addr6;
+ if (talk_base::inet_pton(AF_INET6, hostname.c_str(), &addr6) > 0) {
+ if (ip) {
+ *ip = IPAddress(addr6);
+ }
+ return true;
+ }
+ return false;
+}
+
uint32 SocketAddress::StringToIP(const std::string& hostname) {
uint32 ip = 0;
StringToIP(hostname, &ip);
@@ -334,25 +402,48 @@
return "";
}
-bool SocketAddress::GetLocalIPs(std::vector<uint32>& ips) {
- ips.clear();
+bool SocketAddress::GetLocalIPs(std::vector<IPAddress>* ips) {
+ if (!ips) {
+ return false;
+ }
+ ips->clear();
- const std::string hostname = GetHostname();
+ const std::string hostname = SocketAddress::GetHostname();
if (hostname.empty())
return false;
int errcode;
if (hostent* pHost = SafeGetHostByName(hostname.c_str(), &errcode)) {
for (size_t i = 0; pHost->h_addr_list[i]; ++i) {
- uint32 ip =
- NetworkToHost32(*reinterpret_cast<uint32 *>(pHost->h_addr_list[i]));
- ips.push_back(ip);
+ IPAddress ip;
+ if (IPFromHostEnt(pHost, i, &ip)) {
+ ips->push_back(ip);
+ }
}
FreeHostEnt(pHost);
- return !ips.empty();
+ return !ips->empty();
}
LOG(LS_ERROR) << "gethostbyname err: " << errcode;
return false;
}
+bool SocketAddressFromSockAddrStorage(const sockaddr_storage& addr,
+ SocketAddress* out) {
+ if (!out) {
+ return false;
+ }
+ if (addr.ss_family == AF_INET) {
+ const sockaddr_in* saddr = reinterpret_cast<const sockaddr_in*>(&addr);
+ *out = SocketAddress(IPAddress(saddr->sin_addr),
+ NetworkToHost16(saddr->sin_port));
+ return true;
+ } else if (addr.ss_family == AF_INET6) {
+ const sockaddr_in6* saddr = reinterpret_cast<const sockaddr_in6*>(&addr);
+ *out = SocketAddress(IPAddress(saddr->sin6_addr),
+ NetworkToHost16(saddr->sin6_port));
+ out->SetScopeID(saddr->sin6_scope_id);
+ return true;
+ }
+ return false;
+}
} // namespace talk_base
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
index 4af80c9..9bd1fad 100644
--- a/talk/base/socketaddress.h
+++ b/talk/base/socketaddress.h
@@ -32,28 +32,31 @@
#include <vector>
#include <iosfwd>
#include "talk/base/basictypes.h"
+#include "talk/base/ipaddress.h"
+
#undef SetPort
struct sockaddr_in;
+struct sockaddr_storage;
namespace talk_base {
-// Records an IP address and port, which are 32 and 16 bit integers,
-// respectively, both in <b>host byte-order</b>.
+// Records an IP address and port.
class SocketAddress {
public:
// Creates a nil address.
SocketAddress();
- // Creates the address with the given host and port. If use_dns is true,
- // the hostname will be immediately resolved to an IP (which may block for
- // several seconds if DNS is not available). Alternately, set use_dns to
- // false, and then call Resolve() to complete resolution later, or use
- // SetResolvedIP to set the IP explictly.
+ // Creates the address with the given host and port. Host may be a
+ // literal IP string or a hostname to be resolved later with ResolveIP().
SocketAddress(const std::string& hostname, int port);
// Creates the address with the given IP and port.
- SocketAddress(uint32 ip, int port);
+ // IP is given as an integer in host byte order. V4 only, to be deprecated.
+ SocketAddress(uint32 ip_as_host_order_integer, int port);
+
+ // Creates the address with the given IP and port.
+ SocketAddress(const IPAddress& ip, int port);
// Creates a copy of the given address.
SocketAddress(const SocketAddress& addr);
@@ -70,8 +73,12 @@
// 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
+ // IP is given as an integer in host byte order. V4 only, to be deprecated..
+ void SetIP(uint32 ip_as_host_order_integer);
+
// Changes the IP of this address to the given one, and clears the hostname.
- void SetIP(uint32 ip);
+ void SetIP(const IPAddress& ip);
// Changes the hostname of this address to the given one.
// Does not resolve the address; use Resolve to do so.
@@ -79,41 +86,59 @@
// Sets the IP address while retaining the hostname. Useful for bypassing
// DNS for a pre-resolved IP.
- void SetResolvedIP(uint32 ip);
+ // IP is given as an integer in host byte order. V4 only, to be deprecated.
+ void SetResolvedIP(uint32 ip_as_host_order_integer);
+
+ // Sets the IP address while retaining the hostname. Useful for bypassing
+ // DNS for a pre-resolved IP.
+ void SetResolvedIP(const IPAddress& ip);
// Changes the port of this address to the given one.
void SetPort(int port);
- // Returns the hostname
+ // Returns the hostname.
const std::string& hostname() const { return hostname_; }
- // Returns the IP address.
+ // Returns the IP address as a host byte order integer.
+ // Returns 0 for non-v4 addresses.
uint32 ip() const;
+ const IPAddress& ipaddr() const;
+
// Returns the port part of this address.
uint16 port() const;
- // Returns the IP address in dotted form.
+ // Returns the scope ID associated with this address. Scope IDs are a
+ // necessary addition to IPv6 link-local addresses, with different network
+ // interfaces having different scope-ids for their link-local addresses.
+ // IPv4 address do not have scope_ids and sockaddr_in structures do not have
+ // a field for them.
+ int scope_id() const {return scope_id_; }
+ void SetScopeID(int id) { scope_id_ = id; }
+
+ // Returns the IP address (or hostname) in printable form.
std::string IPAsString() const;
- // Returns the port as a string
+ // Returns the port as a string.
std::string PortAsString() const;
- // Returns hostname:port
+ // Returns hostname:port or [hostname]:port.
std::string ToString() const;
- // Parses hostname:port
+ // Parses hostname:port and [hostname]:port.
bool FromString(const std::string& str);
friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr);
- // Determines whether this represents a missing / any IP address. Hostname
- // and/or port may be set.
+ // Determines whether this represents a missing / any IP address.
+ // That is, 0.0.0.0 or ::.
+ // Hostname and/or port may be set.
bool IsAnyIP() const;
inline bool IsAny() const { return IsAnyIP(); } // deprecated
- // Determines whether the IP address refers to a loopback address, i.e. within
- // the range 127.0.0.0/8.
+ // Determines whether the IP address refers to a loopback address.
+ // For v4 addresses this means the address is in the range 127.0.0.0/8.
+ // For v6 addresses this means the address is ::1.
bool IsLoopbackIP() const;
// Determines wither the IP address refers to any adapter on the local
@@ -121,7 +146,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.
+ // For v4: 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12.
+ // For v6: FE80::/16 and ::1.
bool IsPrivateIP() const;
// Determines whether the hostname has been resolved to an IP.
@@ -131,6 +157,7 @@
// Attempt to resolve a hostname to IP address.
// Returns false if resolution is required but failed, and sets error.
// 'force' will cause re-resolution of hostname.
+ // TODO: Deprecate this function.
bool ResolveIP(bool force = false, int* error = NULL);
// Determines whether this address is identical to the given one.
@@ -151,41 +178,54 @@
// Hashes this address into a small number.
size_t Hash() const;
- // Returns the size of this address when written.
- size_t Size_() const;
-
- // Writes this address into the given buffer, according to RFC 3489.
- bool Write_(char* buf, int len) const;
-
- // Reads this address from the given buffer, according to RFC 3489.
- bool Read_(const char* buf, int len);
-
// Write this address to a sockaddr_in.
+ // If IPv6, will zero out the sockaddr_in and sets family to AF_UNSPEC.
void ToSockAddr(sockaddr_in* saddr) const;
// Read this address from a sockaddr_in.
bool FromSockAddr(const sockaddr_in& saddr);
- // Converts the IP address given in compact form into dotted form.
- static std::string IPToString(uint32 ip);
+ // Read and write the address to/from a sockaddr_storage.
+ // Dual stack version always sets family to AF_INET6, and maps v4 addresses.
+ // The other version doesn't map, and outputs an AF_INET address for
+ // v4 or mapped addresses, and AF_INET6 addresses for others.
+ // Returns the size of the sockaddr_in or sockaddr_in6 structure that is
+ // written to the sockaddr_storage, or zero on failure.
+ size_t ToDualStackSockAddrStorage(sockaddr_storage* saddr) const;
+ size_t ToSockAddrStorage(sockaddr_storage* saddr) const;
+
+ // Converts the IP address given in 'compact form' into dotted form.
+ // IP is given as an integer in host byte order. V4 only, to be deprecated.
+ // TODO: Deprecate this.
+ static std::string IPToString(uint32 ip_as_host_order_integer);
// Converts the IP address given in dotted form into compact form.
- // Only dotted names (A.B.C.D) are resolved.
+ // Only dotted names (A.B.C.D) are converted.
+ // Output integer is returned in host byte order.
+ // TODO: Deprecate, replace wth agnostic versions.
static bool StringToIP(const std::string& str, uint32* ip);
- static uint32 StringToIP(const std::string& str); // deprecated
+ static uint32 StringToIP(const std::string& str);
- // Get local machine's hostname
- static std::string GetHostname();
+ // Converts the IP address given in printable form into an IPAddress.
+ static bool StringToIP(const std::string& str, IPAddress* ip);
- // Get a list of the local machine's ip addresses
- static bool GetLocalIPs(std::vector<uint32>& ips);
+ // Get a list of the local machine's ip addresses.
+ // TODO: Move to nethelpers or similar (doesn't belong in socketaddress).
+ static bool GetLocalIPs(std::vector<IPAddress>* ips);
private:
+ // Get local machine's hostname.
+ static std::string GetHostname();
std::string hostname_;
- uint32 ip_;
+ IPAddress ip_;
uint16 port_;
+ int scope_id_;
+ bool literal_; // Indicates that 'hostname_' contains a literal IP string.
};
+bool SocketAddressFromSockAddrStorage(const sockaddr_storage& saddr,
+ SocketAddress* out);
+
} // namespace talk_base
#endif // TALK_BASE_SOCKETADDRESS_H_
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
index 95b0071..5ef05b5 100644
--- a/talk/base/socketaddress_unittest.cc
+++ b/talk/base/socketaddress_unittest.cc
@@ -31,41 +31,65 @@
#include "talk/base/gunit.h"
#include "talk/base/socketaddress.h"
+#include "talk/base/ipaddress.h"
namespace talk_base {
+const in6_addr kTestV6Addr = { { {0x20, 0x01, 0x0d, 0xb8,
+ 0x10, 0x20, 0x30, 0x40,
+ 0x50, 0x60, 0x70, 0x80,
+ 0x90, 0xA0, 0xB0, 0xC0} } };
+const in6_addr kMappedV4Addr = { { {0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0xFF,
+ 0x01, 0x02, 0x03, 0x04} } };
+const std::string kTestV6AddrString = "2001:db8:1020:3040:5060:7080:90a0:b0c0";
+const std::string kTestV6AddrFullString =
+ "[2001:db8:1020:3040:5060:7080:90a0:b0c0]:5678";
+
TEST(SocketAddressTest, TestDefaultCtor) {
SocketAddress addr;
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
EXPECT_EQ(0, addr.port());
EXPECT_EQ("", addr.hostname());
EXPECT_EQ("0.0.0.0:0", addr.ToString());
}
TEST(SocketAddressTest, TestIPPortCtor) {
- SocketAddress addr(0x01020304, 5678);
+ SocketAddress addr(IPAddress(0x01020304), 5678);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
-TEST(SocketAddressTest, TestStringPortCtor) {
+TEST(SocketAddressTest, TestIPv4StringPortCtor) {
SocketAddress addr("1.2.3.4", 5678);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("1.2.3.4", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
+TEST(SocketAddressTest, TestIPv6StringPortCtor) {
+ SocketAddress addr2(kTestV6AddrString, 1234);
+ IPAddress tocheck(kTestV6Addr);
+
+ EXPECT_FALSE(addr2.IsUnresolvedIP());
+ EXPECT_EQ(tocheck, addr2.ipaddr());
+ EXPECT_EQ(1234, addr2.port());
+ EXPECT_EQ(kTestV6AddrString, addr2.hostname());
+ EXPECT_EQ("[" + kTestV6AddrString + "]:1234", addr2.ToString());
+}
+
TEST(SocketAddressTest, TestSpecialStringPortCtor) {
// inet_addr doesn't handle this address properly.
SocketAddress addr("255.255.255.255", 5678);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0xFFFFFFFFU, addr.ip());
+ EXPECT_EQ(IPAddress(0xFFFFFFFFU), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("255.255.255.255", addr.hostname());
EXPECT_EQ("255.255.255.255:5678", addr.ToString());
@@ -74,7 +98,7 @@
TEST(SocketAddressTest, TestHostnamePortCtor) {
SocketAddress addr("a.b.com", 5678);
EXPECT_TRUE(addr.IsUnresolvedIP());
- EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("a.b.com", addr.hostname());
EXPECT_EQ("a.b.com:5678", addr.ToString());
@@ -84,7 +108,7 @@
SocketAddress from("1.2.3.4", 5678);
SocketAddress addr(from);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("1.2.3.4", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
@@ -92,68 +116,77 @@
TEST(SocketAddressTest, TestAssign) {
SocketAddress from("1.2.3.4", 5678);
- SocketAddress addr(0x88888888, 9999);
+ SocketAddress addr(IPAddress(0x88888888), 9999);
addr = from;
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("1.2.3.4", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
TEST(SocketAddressTest, TestSetIPPort) {
- SocketAddress addr(0x88888888, 9999);
- addr.SetIP(0x01020304);
+ SocketAddress addr(IPAddress(0x88888888), 9999);
+ addr.SetIP(IPAddress(0x01020304));
addr.SetPort(5678);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
TEST(SocketAddressTest, TestSetIPFromString) {
- SocketAddress addr(0x88888888, 9999);
+ SocketAddress addr(IPAddress(0x88888888), 9999);
addr.SetIP("1.2.3.4");
addr.SetPort(5678);
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("1.2.3.4", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
TEST(SocketAddressTest, TestSetIPFromHostname) {
- SocketAddress addr(0x88888888, 9999);
+ SocketAddress addr(IPAddress(0x88888888), 9999);
addr.SetIP("a.b.com");
addr.SetPort(5678);
EXPECT_TRUE(addr.IsUnresolvedIP());
- EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("a.b.com", addr.hostname());
EXPECT_EQ("a.b.com:5678", addr.ToString());
- addr.SetResolvedIP(0x01020304);
+ addr.SetResolvedIP(IPAddress(0x01020304));
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ("a.b.com", addr.hostname());
EXPECT_EQ("a.b.com:5678", addr.ToString());
}
-TEST(SocketAddressTest, TestFromString) {
+TEST(SocketAddressTest, TestFromIPv4String) {
SocketAddress addr;
EXPECT_TRUE(addr.FromString("1.2.3.4:5678"));
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("1.2.3.4", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
+TEST(SocketAddressTest, TestFromIPv6String) {
+ SocketAddress addr;
+ EXPECT_TRUE(addr.FromString(kTestV6AddrFullString));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ(kTestV6AddrString, addr.hostname());
+ EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+}
+
TEST(SocketAddressTest, TestFromHostname) {
SocketAddress addr;
EXPECT_TRUE(addr.FromString("a.b.com:5678"));
EXPECT_TRUE(addr.IsUnresolvedIP());
- EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(IPAddress(INADDR_ANY), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("a.b.com", addr.hostname());
EXPECT_EQ("a.b.com:5678", addr.ToString());
@@ -165,22 +198,61 @@
from.ToSockAddr(&addr_in);
EXPECT_TRUE(addr.FromSockAddr(addr_in));
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
}
-TEST(SocketAddressTest, TestToFromBuffer) {
+TEST(SocketAddressTest, TestToFromSockAddrStorage) {
SocketAddress from("1.2.3.4", 5678), addr;
- char buf[8];
- EXPECT_TRUE(from.Write_(buf, sizeof(buf)));
- EXPECT_TRUE(addr.Read_(buf, sizeof(buf)));
+ sockaddr_storage addr_storage;
+ from.ToSockAddrStorage(&addr_storage);
+ EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(IPAddress(0x01020304U), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("", addr.hostname());
EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+
+ addr.Clear();
+ from.ToDualStackSockAddrStorage(&addr_storage);
+ EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(IPAddress(kMappedV4Addr), addr.ipaddr());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("[::ffff:1.2.3.4]:5678", addr.ToString());
+
+ addr.Clear();
+ memset(&addr_storage, 0, sizeof(sockaddr_storage));
+ from = SocketAddress(kTestV6AddrString, 5678);
+ from.SetScopeID(6);
+ from.ToSockAddrStorage(&addr_storage);
+ EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+ EXPECT_EQ(6, addr.scope_id());
+
+ addr.Clear();
+ from.ToDualStackSockAddrStorage(&addr_storage);
+ EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+ EXPECT_EQ(6, addr.scope_id());
+
+ addr = from;
+ addr_storage.ss_family = AF_UNSPEC;
+ EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+ EXPECT_EQ(from, addr);
+
+ EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, NULL));
}
TEST(SocketAddressTest, TestGoodResolve) {
@@ -191,7 +263,7 @@
EXPECT_EQ(0, error);
EXPECT_FALSE(addr.IsUnresolvedIP());
EXPECT_TRUE(addr.IsLoopbackIP());
- EXPECT_EQ(0x7F000001U, addr.ip());
+ EXPECT_EQ(IPAddress(INADDR_LOOPBACK), addr.ipaddr());
EXPECT_EQ(5678, addr.port());
EXPECT_EQ("localhost", addr.hostname());
EXPECT_EQ("localhost:5678", addr.ToString());
@@ -206,4 +278,74 @@
EXPECT_TRUE(addr.IsUnresolvedIP());
}
+bool AreEqual(const SocketAddress& addr1,
+ const SocketAddress& addr2) {
+ return addr1 == addr2 && addr2 == addr1 &&
+ !(addr1 != addr2) && !(addr2 != addr1);
+}
+
+bool AreUnequal(const SocketAddress& addr1,
+ const SocketAddress& addr2) {
+ return !(addr1 == addr2) && !(addr2 == addr1) &&
+ addr1 != addr2 && addr2 != addr1;
+}
+
+TEST(SocketAddressTest, TestEqualityOperators) {
+ SocketAddress addr1("1.2.3.4", 5678);
+ SocketAddress addr2("1.2.3.4", 5678);
+ EXPECT_PRED2(AreEqual, addr1, addr2);
+
+ addr2 = SocketAddress("0.0.0.1", 5678);
+ EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+ addr2 = SocketAddress("1.2.3.4", 1234);
+ EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+ addr2 = SocketAddress(kTestV6AddrString, 5678);
+ EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+ addr1 = SocketAddress(kTestV6AddrString, 5678);
+ EXPECT_PRED2(AreEqual, addr1, addr2);
+
+ addr2 = SocketAddress(kTestV6AddrString, 1234);
+ EXPECT_PRED2(AreUnequal, addr1, addr2);
+
+ addr2 = SocketAddress("fe80::1", 5678);
+ EXPECT_PRED2(AreUnequal, addr1, addr2);
+}
+
+bool IsLessThan(const SocketAddress& addr1,
+ const SocketAddress& addr2) {
+ return addr1 < addr2 &&
+ !(addr2 < addr1) &&
+ !(addr1 == addr2);
+}
+
+TEST(SocketAddressTest, TestComparisonOperator) {
+ SocketAddress addr1("1.2.3.4", 5678);
+ SocketAddress addr2("1.2.3.4", 5678);
+
+ EXPECT_FALSE(addr1 < addr2);
+ EXPECT_FALSE(addr2 < addr1);
+
+ addr2 = SocketAddress("1.2.3.4", 5679);
+ EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+ addr2 = SocketAddress("2.2.3.4", 49152);
+ EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+ addr2 = SocketAddress(kTestV6AddrString, 5678);
+ EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+ addr1 = SocketAddress("fe80::1", 5678);
+ EXPECT_PRED2(IsLessThan, addr2, addr1);
+
+ addr2 = SocketAddress("fe80::1", 5679);
+ EXPECT_PRED2(IsLessThan, addr1, addr2);
+
+ addr2 = SocketAddress("fe80::1", 5678);
+ EXPECT_FALSE(addr1 < addr2);
+ EXPECT_FALSE(addr2 < addr1);
+}
+
} // namespace talk_base
diff --git a/talk/base/ssladapter.h b/talk/base/ssladapter.h
index 687cc24..1583dc2 100644
--- a/talk/base/ssladapter.h
+++ b/talk/base/ssladapter.h
@@ -25,8 +25,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef TALK_BASE_SSLADAPTER_H__
-#define TALK_BASE_SSLADAPTER_H__
+#ifndef TALK_BASE_SSLADAPTER_H_
+#define TALK_BASE_SSLADAPTER_H_
#include "talk/base/asyncsocket.h"
@@ -35,8 +35,8 @@
///////////////////////////////////////////////////////////////////////////////
class SSLAdapter : public AsyncSocketAdapter {
-public:
- SSLAdapter(AsyncSocket* socket)
+ public:
+ explicit SSLAdapter(AsyncSocket* socket)
: AsyncSocketAdapter(socket), ignore_bad_cert_(false) { }
bool ignore_bad_cert() const { return ignore_bad_cert_; }
@@ -50,7 +50,7 @@
// Create the default SSL adapter for this platform
static SSLAdapter* Create(AsyncSocket* socket);
-private:
+ private:
// If true, the server certificate need not match the configured hostname.
bool ignore_bad_cert_;
};
@@ -73,4 +73,4 @@
} // namespace talk_base
-#endif // TALK_BASE_SSLADAPTER_H__
+#endif // TALK_BASE_SSLADAPTER_H_
diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc
index 665e700..0cb52f1 100644
--- a/talk/base/sslidentity.cc
+++ b/talk/base/sslidentity.cc
@@ -54,6 +54,13 @@
namespace talk_base {
+// From RFC 4572
+const char DIGEST_SHA_1[] = "sha-1";
+const char DIGEST_SHA_224[] = "sha-224";
+const char DIGEST_SHA_256[] = "sha-256";
+const char DIGEST_SHA_384[] = "sha-384";
+const char DIGEST_SHA_512[] = "sha-512";
+
SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string,
int* pem_length) {
return OpenSSLCertificate::FromPEMString(pem_string, pem_length);
diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h
index ed996b4..37e3080 100644
--- a/talk/base/sslidentity.h
+++ b/talk/base/sslidentity.h
@@ -34,6 +34,13 @@
namespace talk_base {
+// Definitions for the digest algorithms
+extern const char DIGEST_SHA_1[];
+extern const char DIGEST_SHA_224[];
+extern const char DIGEST_SHA_256[];
+extern const char DIGEST_SHA_384[];
+extern const char DIGEST_SHA_512[];
+
// Abstract interface overridden by SSL library specific
// implementations.
@@ -61,6 +68,11 @@
// Returns a PEM encoded string representation of the certificate.
virtual std::string ToPEMString() const = 0;
+
+ // Compute the digest of the certificate given algorithm
+ virtual bool ComputeDigest(const std::string &algorithm,
+ unsigned char *digest, std::size_t size,
+ std::size_t *length) const = 0;
};
// Our identity in an SSL negotiation: a keypair and certificate (both
diff --git a/talk/base/sslidentity_unittest.cc b/talk/base/sslidentity_unittest.cc
new file mode 100644
index 0000000..b6200cd
--- /dev/null
+++ b/talk/base/sslidentity_unittest.cc
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * Portions Copyright 2011, RTFM, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/sslidentity.h"
+
+const char kTestCertificate[] = "-----BEGIN CERTIFICATE-----\n"
+ "MIIB6TCCAVICAQYwDQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCQVUxEzARBgNV\n"
+ "BAgTClF1ZWVuc2xhbmQxGjAYBgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRswGQYD\n"
+ "VQQDExJUZXN0IENBICgxMDI0IGJpdCkwHhcNMDAxMDE2MjIzMTAzWhcNMDMwMTE0\n"
+ "MjIzMTAzWjBjMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFuZDEaMBgG\n"
+ "A1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxIzAhBgNVBAMTGlNlcnZlciB0ZXN0IGNl\n"
+ "cnQgKDUxMiBiaXQpMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ+zw4Qnlf8SMVIP\n"
+ "Fe9GEcStgOY2Ww/dgNdhjeD8ckUJNP5VZkVDTGiXav6ooKXfX3j/7tdkuD8Ey2//\n"
+ "Kv7+ue0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCT0grFQeZaqYb5EYfk20XixZV4\n"
+ "GmyAbXMftG1Eo7qGiMhYzRwGNWxEYojf5PZkYZXvSqZ/ZXHXa4g59jK/rJNnaVGM\n"
+ "k+xIX8mxQvlV0n5O9PIha5BX5teZnkHKgL8aKKLKW1BK7YTngsfSzzaeame5iKfz\n"
+ "itAE+OjGF+PFKbwX8Q==\n"
+ "-----END CERTIFICATE-----\n";
+
+const unsigned char kTestCertSha1[] = {0xA6, 0xC8, 0x59, 0xEA,
+ 0xC3, 0x7E, 0x6D, 0x33,
+ 0xCF, 0xE2, 0x69, 0x9D,
+ 0x74, 0xE6, 0xF6, 0x8A,
+ 0x9E, 0x47, 0xA7, 0xCA};
+
+class SSLIdentityTest : public testing::Test {
+ public:
+ SSLIdentityTest() :
+ identity1_(NULL), identity2_(NULL) {
+ }
+
+ ~SSLIdentityTest() {
+ }
+
+ virtual void SetUp() {
+ identity1_.reset(talk_base::SSLIdentity::Generate("test1"));
+ identity2_.reset(talk_base::SSLIdentity::Generate("test2"));
+
+ ASSERT_TRUE(identity1_.get() != NULL);
+ ASSERT_TRUE(identity2_.get() != NULL);
+
+ test_cert_.reset(
+ talk_base::SSLCertificate::FromPEMString(kTestCertificate, 0));
+ ASSERT_TRUE(test_cert_.get() != NULL);
+ }
+
+ void TestDigest(const std::string &algorithm, size_t expected_len,
+ const unsigned char *expected_digest = NULL) {
+ unsigned char digest1[64];
+ unsigned char digest1b[64];
+ unsigned char digest2[64];
+ size_t digest1_len;
+ size_t digest1b_len;
+ size_t digest2_len;
+ bool rv;
+
+ rv = identity1_->certificate().ComputeDigest(algorithm,
+ digest1, sizeof(digest1),
+ &digest1_len);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(expected_len, digest1_len);
+
+ rv = identity1_->certificate().ComputeDigest(algorithm,
+ digest1b, sizeof(digest1b),
+ &digest1b_len);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(expected_len, digest1b_len);
+ EXPECT_EQ(0, memcmp(digest1, digest1b, expected_len));
+
+
+ rv = identity2_->certificate().ComputeDigest(algorithm,
+ digest2, sizeof(digest2),
+ &digest2_len);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(expected_len, digest2_len);
+ EXPECT_NE(0, memcmp(digest1, digest2, expected_len));
+
+ // If we have an expected hash for the test cert, check it.
+ if (expected_digest != NULL) {
+ unsigned char digest3[64];
+ size_t digest3_len;
+
+ rv = test_cert_->ComputeDigest(algorithm, digest3, sizeof(digest3),
+ &digest3_len);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(expected_len, digest3_len);
+ EXPECT_EQ(0, memcmp(digest3, expected_digest, expected_len));
+ }
+ }
+
+ private:
+ talk_base::scoped_ptr<talk_base::SSLIdentity> identity1_;
+ talk_base::scoped_ptr<talk_base::SSLIdentity> identity2_;
+ talk_base::scoped_ptr<talk_base::SSLCertificate> test_cert_;
+};
+
+TEST_F(SSLIdentityTest, DigestSHA1) {
+ TestDigest(talk_base::DIGEST_SHA_1, 20, kTestCertSha1);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA224) {
+ TestDigest(talk_base::DIGEST_SHA_224, 28);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA256) {
+ TestDigest(talk_base::DIGEST_SHA_256, 32);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA384) {
+ TestDigest(talk_base::DIGEST_SHA_384, 48);
+}
+
+TEST_F(SSLIdentityTest, DigestSHA512) {
+ TestDigest(talk_base::DIGEST_SHA_512, 64);
+}
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
index 17121fd..aa0f468 100644
--- a/talk/base/sslstreamadapter.cc
+++ b/talk/base/sslstreamadapter.cc
@@ -65,6 +65,23 @@
#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
}
+// Note: this matches the logic above with SCHANNEL dominating
+#if SSL_USE_SCHANNEL || !SSL_USE_OPENSSL
+bool SSLStreamAdapter::HaveDtls() { return false; }
+bool SSLStreamAdapter::HaveDtlsSrtp() { return false; }
+bool SSLStreamAdapter::HaveExporter() { return false; }
+#else
+bool SSLStreamAdapter::HaveDtls() {
+ return OpenSSLStreamAdapter::HaveDtls();
+}
+bool SSLStreamAdapter::HaveDtlsSrtp() {
+ return OpenSSLStreamAdapter::HaveDtlsSrtp();
+}
+bool SSLStreamAdapter::HaveExporter() {
+ return OpenSSLStreamAdapter::HaveExporter();
+}
+#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
///////////////////////////////////////////////////////////////////////////////
} // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
index 62cef08..2afe1da 100644
--- a/talk/base/sslstreamadapter.h
+++ b/talk/base/sslstreamadapter.h
@@ -28,6 +28,9 @@
#ifndef TALK_BASE_SSLSTREAMADAPTER_H__
#define TALK_BASE_SSLSTREAMADAPTER_H__
+#include <string>
+#include <vector>
+
#include "talk/base/stream.h"
#include "talk/base/sslidentity.h"
@@ -47,6 +50,13 @@
// for doing this are in SSLAdapter. They should possibly be moved out
// to a neutral class.
+
+enum SSLRole { SSL_CLIENT, SSL_SERVER };
+enum SSLMode { SSL_MODE_TLS, SSL_MODE_DTLS };
+
+// Errors for Read -- in the high range so no conflict with OpenSSL.
+enum { SSE_MSG_TRUNC = 0xff0001 };
+
class SSLStreamAdapter : public StreamAdapterInterface {
public:
// Instantiate an SSLStreamAdapter wrapping the given stream,
@@ -70,7 +80,12 @@
// Call this to indicate that we are to play the server's role in
// the peer-to-peer mode.
- virtual void SetServerRole() = 0;
+ // The default argument is for backward compatibility
+ // TODO(ekr@rtfm.com): rename this SetRole to reflect its new function
+ virtual void SetServerRole(SSLRole role = SSL_SERVER) = 0;
+
+ // Do DTLS or TLS
+ virtual void SetMode(SSLMode mode) = 0;
// The mode of operation is selected by calling either
// StartSSLWithServer or StartSSLWithPeer.
@@ -95,7 +110,7 @@
// StartSSLWithPeer starts negotiation in the special peer-to-peer
// mode.
// Generally, SetIdentity() and possibly SetServerRole() should have
- // been called before this.
+ // been called before this.
// SetPeerCertificate() must also be called. It may be called after
// StartSSLWithPeer() but must be called before the underlying
// stream opens.
@@ -112,6 +127,53 @@
// given SSLStream instance.
virtual void SetPeerCertificate(SSLCertificate* cert) = 0;
+ // Specify the digest of the certificate that our peer is expected to use in
+ // peer-to-peer mode. Only this certificate will be accepted during
+ // SSL verification. The certificate is assumed to have been
+ // obtained through some other secure channel (such as the XMPP
+ // channel). Unlike SetPeerCertificate(), this must specify the
+ // terminal certificate, not just a CA.
+ // SSLStream makes a copy of the digest value.
+ virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
+ const unsigned char* digest_val,
+ size_t digest_len) = 0;
+
+ // Key Exporter interface from RFC 5705
+ // Arguments are:
+ // label -- the exporter label.
+ // part of the RFC defining each exporter
+ // usage (IN)
+ // context/context_len -- a context to bind to for this connection;
+ // optional, can be NULL, 0 (IN)
+ // use_context -- whether to use the context value
+ // (needed to distinguish no context from
+ // zero-length ones).
+ // result -- where to put the computed value
+ // result_len -- the length of the computed value
+ virtual bool ExportKeyingMaterial(const std::string& label,
+ const uint8* context,
+ size_t context_len,
+ bool use_context,
+ uint8* result,
+ size_t result_len) {
+ return false; // Default is unsupported
+ }
+
+
+ // DTLS-SRTP interface
+ virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers) {
+ return false;
+ }
+
+ virtual bool GetDtlsSrtpCipher(std::string* cipher) {
+ return false;
+ }
+
+ // Capabilities testing
+ static bool HaveDtls();
+ static bool HaveDtlsSrtp();
+ static bool HaveExporter();
+
// If true, the server certificate need not match the configured
// server_name, and in fact missing certificate authority and other
// verification errors are ignored.
diff --git a/talk/base/sslstreamadapter_unittest.cc b/talk/base/sslstreamadapter_unittest.cc
new file mode 100644
index 0000000..befcdbc
--- /dev/null
+++ b/talk/base/sslstreamadapter_unittest.cc
@@ -0,0 +1,756 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ * Portions Copyright 2011, RTFM, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <set>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/stream.h"
+
+static const int kBlockSize = 4096;
+static const char kAES_CM_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
+static const char kAES_CM_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char kExporterLabel[] = "label";
+static const unsigned char kExporterContext[] = "context";
+static int kExporterContextLen = sizeof(kExporterContext);
+
+#define MAYBE_SKIP_TEST(feature) \
+ if (!(talk_base::SSLStreamAdapter::feature())) { \
+ LOG(LS_INFO) << "Feature disabled... skipping"; \
+ return; \
+ }
+
+class SSLStreamAdapterTestBase;
+
+class SSLDummyStream : public talk_base::StreamInterface,
+ public sigslot::has_slots<> {
+ public:
+ explicit SSLDummyStream(SSLStreamAdapterTestBase *test,
+ const std::string &side,
+ talk_base::FifoBuffer *in,
+ talk_base::FifoBuffer *out) :
+ test_(test),
+ side_(side),
+ in_(in),
+ out_(out),
+ first_packet_(true) {
+ in_->SignalEvent.connect(this, &SSLDummyStream::OnEventIn);
+ out_->SignalEvent.connect(this, &SSLDummyStream::OnEventOut);
+ }
+
+ virtual talk_base::StreamState GetState() const { return talk_base::SS_OPEN; }
+
+ virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ talk_base::StreamResult r;
+
+ r = in_->Read(buffer, buffer_len, read, error);
+ if (r == talk_base::SR_BLOCK)
+ return talk_base::SR_BLOCK;
+ if (r == talk_base::SR_EOS)
+ return talk_base::SR_EOS;
+
+ if (r != talk_base::SR_SUCCESS) {
+ ADD_FAILURE();
+ return talk_base::SR_ERROR;
+ }
+
+ return talk_base::SR_SUCCESS;
+ }
+
+ // Catch readability events on in and pass them up.
+ virtual void OnEventIn(talk_base::StreamInterface *stream, int sig,
+ int err) {
+ int mask = (talk_base::SE_READ | talk_base::SE_CLOSE);
+
+ if (sig & mask) {
+ LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig="
+ << sig << " forwarding upward";
+ PostEvent(sig & mask, 0);
+ }
+ }
+
+ // Catch writeability events on out and pass them up.
+ virtual void OnEventOut(talk_base::StreamInterface *stream, int sig,
+ int err) {
+ if (sig & talk_base::SE_WRITE) {
+ LOG(LS_INFO) << "SSLDummyStream::OnEvent side=" << side_ << " sig="
+ << sig << " forwarding upward";
+
+ PostEvent(sig & talk_base::SE_WRITE, 0);
+ }
+ }
+
+ // Write to the outgoing FifoBuffer
+ talk_base::StreamResult WriteData(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ return out_->Write(data, data_len, written, error);
+ }
+
+ // Defined later
+ virtual talk_base::StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+
+ virtual void Close() {
+ LOG(LS_INFO) << "Closing outbound stream";
+ out_->Close();
+ }
+
+ private:
+ SSLStreamAdapterTestBase *test_;
+ const std::string side_;
+ talk_base::FifoBuffer *in_;
+ talk_base::FifoBuffer *out_;
+ bool first_packet_;
+};
+
+class SSLStreamAdapterTestBase : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ explicit SSLStreamAdapterTestBase(bool dtls) :
+ client_buffer_(4096), server_buffer_(4096),
+ client_stream_(
+ new SSLDummyStream(this, "c2s", &client_buffer_, &server_buffer_)),
+ server_stream_(
+ new SSLDummyStream(this, "s2c", &server_buffer_, &client_buffer_)),
+ client_ssl_(talk_base::SSLStreamAdapter::Create(client_stream_)),
+ server_ssl_(talk_base::SSLStreamAdapter::Create(server_stream_)),
+ client_identity_(NULL), server_identity_(NULL),
+ delay_(0), mtu_(1460), loss_(0), lose_first_packet_(false), dtls_(dtls),
+ handshake_wait_(5000), identities_set_(false) {
+ // Set use of the test RNG to get predictable loss patterns.
+ talk_base::SetRandomTestMode(true);
+
+ LOG(LS_INFO) << "Setup";
+ talk_base::InitializeSSL();
+
+ // Set up the slots
+ client_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+ server_ssl_->SignalEvent.connect(this, &SSLStreamAdapterTestBase::OnEvent);
+
+ client_identity_ = talk_base::SSLIdentity::Generate("client");
+ server_identity_ = talk_base::SSLIdentity::Generate("server");
+
+ client_ssl_->SetIdentity(client_identity_);
+ server_ssl_->SetIdentity(server_identity_);
+ }
+
+ ~SSLStreamAdapterTestBase() {
+ // Put it back for the next test.
+ talk_base::SetRandomTestMode(false);
+ }
+
+ virtual void OnEvent(talk_base::StreamInterface *stream, int sig, int err) {
+ LOG(LS_INFO) << "SSLStreamAdapterTestBase::OnEvent sig=" << sig;
+
+ if (sig & talk_base::SE_READ) {
+ ReadData(stream);
+ }
+
+ if ((stream == client_ssl_.get()) && (sig & talk_base::SE_WRITE)) {
+ WriteData();
+ }
+ }
+
+ void SetPeerIdentitiesByCertificate(bool correct) {
+ LOG(LS_INFO) << "Setting peer identities by certificate";
+
+ if (correct) {
+ client_ssl_->SetPeerCertificate(server_identity_->certificate().
+ GetReference());
+ server_ssl_->SetPeerCertificate(client_identity_->certificate().
+ GetReference());
+ } else {
+ // If incorrect, set up to expect our own certificate at the peer
+ client_ssl_->SetPeerCertificate(client_identity_->certificate().
+ GetReference());
+ server_ssl_->SetPeerCertificate(server_identity_->certificate().
+ GetReference());
+ }
+ identities_set_ = true;
+ }
+
+ void SetPeerIdentitiesByDigest(bool correct) {
+ unsigned char digest[20];
+ size_t digest_len;
+ bool rv;
+
+ LOG(LS_INFO) << "Setting peer identities by digest";
+
+ rv = server_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1,
+ digest, 20,
+ &digest_len);
+ ASSERT_TRUE(rv);
+ if (!correct) {
+ LOG(LS_INFO) << "Setting bogus digest for server cert";
+ digest[0]++;
+ }
+ rv = client_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest,
+ digest_len);
+ ASSERT_TRUE(rv);
+
+
+ rv = client_identity_->certificate().ComputeDigest(talk_base::DIGEST_SHA_1,
+ digest, 20, &digest_len);
+ ASSERT_TRUE(rv);
+ if (!correct) {
+ LOG(LS_INFO) << "Setting bogus digest for client cert";
+ digest[0]++;
+ }
+ rv = server_ssl_->SetPeerCertificateDigest(talk_base::DIGEST_SHA_1, digest,
+ digest_len);
+ ASSERT_TRUE(rv);
+
+ identities_set_ = true;
+ }
+
+ void TestHandshake(bool expect_success = true) {
+ server_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS :
+ talk_base::SSL_MODE_TLS);
+ client_ssl_->SetMode(dtls_ ? talk_base::SSL_MODE_DTLS :
+ talk_base::SSL_MODE_TLS);
+
+ if (!dtls_) {
+ // Make sure we simulate a reliable network for TLS.
+ // This is just a check to make sure that people don't write wrong
+ // tests.
+ ASSERT((mtu_ == 1460) && (loss_ == 0) && (lose_first_packet_ == 0));
+ }
+
+ if (!identities_set_)
+ SetPeerIdentitiesByDigest(true);
+
+ // Start the handshake
+ int rv;
+
+ server_ssl_->SetServerRole();
+ rv = server_ssl_->StartSSLWithPeer();
+ ASSERT_EQ(0, rv);
+
+ rv = client_ssl_->StartSSLWithPeer();
+ ASSERT_EQ(0, rv);
+
+ // Now run the handshake
+ if (expect_success) {
+ EXPECT_TRUE_WAIT((client_ssl_->GetState() == talk_base::SS_OPEN)
+ && (server_ssl_->GetState() == talk_base::SS_OPEN),
+ handshake_wait_);
+ } else {
+ EXPECT_TRUE_WAIT(client_ssl_->GetState() == talk_base::SS_CLOSED,
+ handshake_wait_);
+ }
+ }
+
+ talk_base::StreamResult DataWritten(SSLDummyStream *from, const void *data,
+ size_t data_len, size_t *written,
+ int *error) {
+ // Randomly drop loss_ percent of packets
+ if (talk_base::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
+ LOG(LS_INFO) << "Randomly dropping packet, size=" << data_len;
+ *written = data_len;
+ return talk_base::SR_SUCCESS;
+ }
+ if (dtls_ && (data_len > mtu_)) {
+ LOG(LS_INFO) << "Dropping packet > mtu, size=" << data_len;
+ *written = data_len;
+ return talk_base::SR_SUCCESS;
+ }
+
+ return from->WriteData(data, data_len, written, error);
+ }
+
+ void SetDelay(int delay) {
+ delay_ = delay;
+ }
+ int GetDelay() { return delay_; }
+
+ void SetLoseFirstPacket(bool lose) {
+ lose_first_packet_ = lose;
+ }
+ bool GetLoseFirstPacket() { return lose_first_packet_; }
+
+ void SetLoss(int percent) {
+ loss_ = percent;
+ }
+
+ void SetMtu(size_t mtu) {
+ mtu_ = mtu;
+ }
+
+ void SetHandshakeWait(int wait) {
+ handshake_wait_ = wait;
+ }
+
+ void SetDtlsSrtpCiphers(const std::vector<std::string> &ciphers,
+ bool client) {
+ if (client)
+ client_ssl_->SetDtlsSrtpCiphers(ciphers);
+ else
+ server_ssl_->SetDtlsSrtpCiphers(ciphers);
+ }
+
+ bool GetDtlsSrtpCipher(bool client, std::string *retval) {
+ if (client)
+ return client_ssl_->GetDtlsSrtpCipher(retval);
+ else
+ return server_ssl_->GetDtlsSrtpCipher(retval);
+ }
+
+ bool ExportKeyingMaterial(const char *label,
+ const unsigned char *context,
+ size_t context_len,
+ bool use_context,
+ bool client,
+ unsigned char *result,
+ size_t result_len) {
+ if (client)
+ return client_ssl_->ExportKeyingMaterial(label,
+ context, context_len,
+ use_context,
+ result, result_len);
+ else
+ return server_ssl_->ExportKeyingMaterial(label,
+ context, context_len,
+ use_context,
+ result, result_len);
+ }
+
+ // To be implemented by subclasses.
+ virtual void WriteData() = 0;
+ virtual void ReadData(talk_base::StreamInterface *stream) = 0;
+ virtual void TestTransfer(int size) = 0;
+
+ protected:
+ talk_base::FifoBuffer client_buffer_;
+ talk_base::FifoBuffer server_buffer_;
+ SSLDummyStream *client_stream_; // freed by client_ssl_ destructor
+ SSLDummyStream *server_stream_; // freed by server_ssl_ destructor
+ talk_base::scoped_ptr<talk_base::SSLStreamAdapter> client_ssl_;
+ talk_base::scoped_ptr<talk_base::SSLStreamAdapter> server_ssl_;
+ talk_base::SSLIdentity *client_identity_; // freed by client_ssl_ destructor
+ talk_base::SSLIdentity *server_identity_; // freed by server_ssl_ destructor
+ int delay_;
+ size_t mtu_;
+ int loss_;
+ bool lose_first_packet_;
+ bool dtls_;
+ int handshake_wait_;
+ bool identities_set_;
+};
+
+
+class SSLStreamAdapterTestTLS : public SSLStreamAdapterTestBase {
+ public:
+ SSLStreamAdapterTestTLS() :
+ SSLStreamAdapterTestBase(false) {
+ };
+
+ // Test data transfer for TLS
+ virtual void TestTransfer(int size) {
+ LOG(LS_INFO) << "Starting transfer test with " << size << " bytes";
+ // Create some dummy data to send.
+ size_t received;
+
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+
+ // Start sending
+ WriteData();
+
+ // Wait for the client to close
+ EXPECT_TRUE_WAIT(server_ssl_->GetState() == talk_base::SS_CLOSED, 10000);
+
+ // Now check the data
+ recv_stream_.GetSize(&received);
+ EXPECT_EQ(static_cast<size_t>(size), received);
+ EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+ recv_stream_.GetBuffer(), size));
+ }
+
+ void WriteData() {
+ size_t position, tosend, size;
+ talk_base::StreamResult rv;
+ size_t sent;
+ char block[kBlockSize];
+
+ send_stream_.GetSize(&size);
+ if (!size)
+ return;
+
+ for (;;) {
+ send_stream_.GetPosition(&position);
+ if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+ talk_base::SR_EOS) {
+ rv = client_ssl_->Write(block, tosend, &sent, 0);
+
+ if (rv == talk_base::SR_SUCCESS) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else if (rv == talk_base::SR_BLOCK) {
+ LOG(LS_VERBOSE) << "Blocked...";
+ send_stream_.SetPosition(position);
+ break;
+ } else {
+ ADD_FAILURE();
+ break;
+ }
+ } else {
+ // Now close
+ LOG(LS_INFO) << "Wrote " << position << " bytes. Closing";
+ client_ssl_->Close();
+ break;
+ }
+ }
+ };
+
+ virtual void ReadData(talk_base::StreamInterface *stream) {
+ char buffer[1600];
+ size_t bread;
+ int err2;
+ talk_base::StreamResult r;
+
+ for (;;) {
+ r = stream->Read(buffer, sizeof(buffer), &bread, &err2);
+
+ if (r == talk_base::SR_ERROR) {
+ // Unfortunately, errors are the way that the stream adapter
+ // signals close right now
+ stream->Close();
+ return;
+ }
+
+ if (r == talk_base::SR_BLOCK)
+ break;
+
+ ASSERT_EQ(talk_base::SR_SUCCESS, r);
+ LOG(LS_INFO) << "Read " << bread;
+
+ recv_stream_.Write(buffer, bread, NULL, NULL);
+ }
+ }
+
+ private:
+ talk_base::MemoryStream send_stream_;
+ talk_base::MemoryStream recv_stream_;
+};
+
+
+class SSLStreamAdapterTestDTLS : public SSLStreamAdapterTestBase {
+ public:
+ SSLStreamAdapterTestDTLS() :
+ SSLStreamAdapterTestBase(true),
+ packet_size_(1000), count_(0), sent_(0) {
+ }
+
+ virtual void WriteData() {
+ unsigned char *packet = new unsigned char[1600];
+
+ do {
+ memset(packet, sent_ & 0xff, packet_size_);
+ *(reinterpret_cast<uint32_t *>(packet)) = sent_;
+
+ size_t sent;
+ int rv = client_ssl_->Write(packet, packet_size_, &sent, 0);
+ if (rv == talk_base::SR_SUCCESS) {
+ LOG(LS_VERBOSE) << "Sent: " << sent_;
+ sent_++;
+ } else if (rv == talk_base::SR_BLOCK) {
+ LOG(LS_VERBOSE) << "Blocked...";
+ break;
+ } else {
+ ADD_FAILURE();
+ break;
+ }
+ } while (sent_ < count_);
+
+ delete [] packet;
+ }
+
+ virtual void ReadData(talk_base::StreamInterface *stream) {
+ unsigned char *buffer = new unsigned char[2000];
+ size_t bread;
+ int err2;
+ talk_base::StreamResult r;
+
+ for (;;) {
+ r = stream->Read(buffer, 2000,
+ &bread, &err2);
+
+ if (r == talk_base::SR_ERROR) {
+ // Unfortunately, errors are the way that the stream adapter
+ // signals close right now
+ stream->Close();
+ return;
+ }
+
+ if (r == talk_base::SR_BLOCK)
+ break;
+
+ ASSERT_EQ(talk_base::SR_SUCCESS, r);
+ LOG(LS_INFO) << "Read " << bread;
+
+ // Now parse the datagram
+ ASSERT_EQ(packet_size_, bread);
+ uint32_t packet_num = *(reinterpret_cast<uint32_t *>(buffer));
+
+ for (size_t i = 4; i < packet_size_; i++) {
+ ASSERT_EQ((packet_num & 0xff), buffer[i]);
+ }
+ received_.insert(packet_num);
+ }
+ }
+
+ virtual void TestTransfer(int count) {
+ count_ = count;
+
+ WriteData();
+
+ EXPECT_TRUE_WAIT(sent_ == count_, 10000);
+ LOG(LS_INFO) << "sent_ == " << sent_;
+
+ if (loss_ == 0) {
+ EXPECT_TRUE_WAIT(static_cast<size_t>(sent_) == received_.size(), 1000);
+ } else {
+ LOG(LS_INFO) << "Sent " << sent_ << " packets; received " <<
+ received_.size();
+ }
+ };
+
+ private:
+ size_t packet_size_;
+ int count_;
+ int sent_;
+ std::set<int> received_;
+};
+
+
+talk_base::StreamResult SSLDummyStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ *written = data_len;
+
+ LOG(LS_INFO) << "Writing to loopback " << data_len;
+
+ if (first_packet_) {
+ first_packet_ = false;
+ if (test_->GetLoseFirstPacket()) {
+ LOG(LS_INFO) << "Losing initial packet of length " << data_len;
+ return talk_base::SR_SUCCESS;
+ }
+ }
+
+ return test_->DataWritten(this, data, data_len, written, error);
+
+ return talk_base::SR_SUCCESS;
+};
+
+
+// Basic tests: TLS
+// Test that we can make a handshake work
+TEST_F(SSLStreamAdapterTestTLS, TestTLSConnect) {
+ TestHandshake();
+};
+
+// Test transfer -- trivial
+TEST_F(SSLStreamAdapterTestTLS, TestTLSTransfer) {
+ TestHandshake();
+ TestTransfer(100000);
+};
+
+// Test a handshake with a bogus peer digest
+TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusDigest) {
+ SetPeerIdentitiesByDigest(false);
+ TestHandshake(false);
+};
+
+// Test a handshake with a peer certificate
+TEST_F(SSLStreamAdapterTestTLS, TestTLSPeerCertificate) {
+ SetPeerIdentitiesByCertificate(true);
+ TestHandshake();
+};
+
+// Test a handshake with a bogus peer certificate
+TEST_F(SSLStreamAdapterTestTLS, TestTLSBogusPeerCertificate) {
+ SetPeerIdentitiesByCertificate(false);
+ TestHandshake(false);
+};
+// Test moving a bunch of data
+
+// Basic tests: DTLS
+// Test that we can make a handshake work
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnect) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ TestHandshake();
+};
+
+// Test that we can make a handshake work if the first packet in
+// each direction is lost. This gives us predictable loss
+// rather than having to tune random
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithLostFirstPacket) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ SetLoseFirstPacket(true);
+ TestHandshake();
+};
+
+// Test a handshake with loss and delay
+TEST_F(SSLStreamAdapterTestDTLS,
+ TestDTLSConnectWithLostFirstPacketDelay2s) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ SetLoseFirstPacket(true);
+ SetDelay(2000);
+ SetHandshakeWait(20000);
+ TestHandshake();
+};
+
+// Test a handshake with small MTU
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ SetMtu(700);
+ TestHandshake();
+};
+
+// Test transfer -- trivial
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransfer) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ TestHandshake();
+ TestTransfer(100);
+};
+
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithLoss) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ TestHandshake();
+ SetLoss(10);
+ TestTransfer(100);
+};
+
+// Test DTLS-SRTP with all high ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHigh) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ std::vector<std::string> high;
+ high.push_back(kAES_CM_HMAC_SHA1_80);
+ SetDtlsSrtpCiphers(high, true);
+ SetDtlsSrtpCiphers(high, false);
+ TestHandshake();
+
+ std::string client_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+ std::string server_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+ ASSERT_EQ(client_cipher, server_cipher);
+ ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test DTLS-SRTP with all low ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ std::vector<std::string> low;
+ low.push_back(kAES_CM_HMAC_SHA1_32);
+ SetDtlsSrtpCiphers(low, true);
+ SetDtlsSrtpCiphers(low, false);
+ TestHandshake();
+
+ std::string client_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+ std::string server_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+ ASSERT_EQ(client_cipher, server_cipher);
+ ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_32);
+};
+
+
+// Test DTLS-SRTP with a mismatch -- should not converge
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ std::vector<std::string> high;
+ high.push_back(kAES_CM_HMAC_SHA1_80);
+ std::vector<std::string> low;
+ low.push_back(kAES_CM_HMAC_SHA1_32);
+ SetDtlsSrtpCiphers(high, true);
+ SetDtlsSrtpCiphers(low, false);
+ TestHandshake();
+
+ std::string client_cipher;
+ ASSERT_FALSE(GetDtlsSrtpCipher(true, &client_cipher));
+ std::string server_cipher;
+ ASSERT_FALSE(GetDtlsSrtpCipher(false, &server_cipher));
+};
+
+// Test DTLS-SRTP with each side being mixed -- should select high
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) {
+ MAYBE_SKIP_TEST(HaveDtlsSrtp);
+ std::vector<std::string> mixed;
+ mixed.push_back(kAES_CM_HMAC_SHA1_80);
+ mixed.push_back(kAES_CM_HMAC_SHA1_32);
+ SetDtlsSrtpCiphers(mixed, true);
+ SetDtlsSrtpCiphers(mixed, false);
+ TestHandshake();
+
+ std::string client_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+ std::string server_cipher;
+ ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+ ASSERT_EQ(client_cipher, server_cipher);
+ ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test an exporter
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSExporter) {
+ MAYBE_SKIP_TEST(HaveExporter);
+ TestHandshake();
+ unsigned char client_out[20];
+ unsigned char server_out[20];
+
+ bool result;
+ result = ExportKeyingMaterial(kExporterLabel,
+ kExporterContext, kExporterContextLen,
+ true, true,
+ client_out, sizeof(client_out));
+ ASSERT_TRUE(result);
+
+ result = ExportKeyingMaterial(kExporterLabel,
+ kExporterContext, kExporterContextLen,
+ true, false,
+ server_out, sizeof(server_out));
+ ASSERT_TRUE(result);
+
+ ASSERT_TRUE(!memcmp(client_out, server_out, sizeof(client_out)));
+}
diff --git a/talk/base/stream.cc b/talk/base/stream.cc
index 9fcb1a8..24a97dc 100644
--- a/talk/base/stream.cc
+++ b/talk/base/stream.cc
@@ -50,11 +50,6 @@
///////////////////////////////////////////////////////////////////////////////
// StreamInterface
///////////////////////////////////////////////////////////////////////////////
-
-enum {
- MSG_POST_EVENT = 0xF1F1
-};
-
StreamInterface::~StreamInterface() {
}
@@ -174,9 +169,8 @@
///////////////////////////////////////////////////////////////////////////////
StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
-: StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
- tap_error_(0)
-{
+ : StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
+ tap_error_(0) {
AttachTap(tap);
}
@@ -228,24 +222,21 @@
///////////////////////////////////////////////////////////////////////////////
StreamSegment::StreamSegment(StreamInterface* stream)
-: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
- length_(SIZE_UNKNOWN)
-{
+ : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+ length_(SIZE_UNKNOWN) {
// It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
stream->GetPosition(&start_);
}
StreamSegment::StreamSegment(StreamInterface* stream, size_t length)
-: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
- length_(length)
-{
+ : StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+ length_(length) {
// It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
stream->GetPosition(&start_);
}
StreamResult StreamSegment::Read(void* buffer, size_t buffer_len,
- size_t* read, int* error)
-{
+ size_t* read, int* error) {
if (SIZE_UNKNOWN != length_) {
if (pos_ >= length_)
return SR_EOS;
@@ -744,6 +735,12 @@
// all events are done on the owner_ thread
}
+FifoBuffer::FifoBuffer(size_t size, Thread *owner)
+ : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size),
+ data_length_(0), read_position_(0), owner_(owner) {
+ // all events are done on the owner_ thread
+}
+
FifoBuffer::~FifoBuffer() {
}
@@ -872,7 +869,7 @@
const size_t write_position = (read_position_ + data_length_)
% buffer_length_;
- *size = (write_position >= read_position_) ?
+ *size = (write_position > read_position_ || data_length_ == 0) ?
buffer_length_ - write_position : read_position_ - write_position;
return &buffer_[write_position];
}
@@ -950,8 +947,7 @@
LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
const std::string& label, bool hex_mode)
-: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode)
-{
+ : StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) {
set_label(label);
}
@@ -974,7 +970,8 @@
StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
size_t* written, int* error) {
- size_t local_written; if (!written) written = &local_written;
+ size_t local_written;
+ if (!written) written = &local_written;
StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
error);
if (result == SR_SUCCESS) {
@@ -1007,13 +1004,11 @@
///////////////////////////////////////////////////////////////////////////////
StringStream::StringStream(std::string& str)
-: str_(str), read_pos_(0), read_only_(false)
-{
+ : 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)
-{
+ : str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true) {
}
StreamState StringStream::GetState() const {
@@ -1169,4 +1164,4 @@
///////////////////////////////////////////////////////////////////////////////
-} // namespace talk_base
+} // namespace talk_base
diff --git a/talk/base/stream.h b/talk/base/stream.h
index 0798741..808156c 100644
--- a/talk/base/stream.h
+++ b/talk/base/stream.h
@@ -67,6 +67,10 @@
class StreamInterface : public MessageHandler {
public:
+ enum {
+ MSG_POST_EVENT = 0xF1F1, MSG_MAX = MSG_POST_EVENT
+ };
+
virtual ~StreamInterface();
virtual StreamState GetState() const = 0;
@@ -125,7 +129,7 @@
// latter operation but not the former.
//
- // The following four methods are used to avoid coping data multiple times.
+ // The following four methods are used to avoid copying data multiple times.
// GetReadData returns a pointer to a buffer which is owned by the stream.
// The buffer contains data_len bytes. NULL is returned if no data is
@@ -556,6 +560,8 @@
public:
// Creates a FIFO buffer with the specified capacity.
explicit FifoBuffer(size_t length);
+ // Creates a FIFO buffer with the specified capacity and owner
+ FifoBuffer(size_t length, Thread* owner);
virtual ~FifoBuffer();
// Gets the amount of data currently readable from the buffer.
bool GetBuffered(size_t* data_len) const;
diff --git a/talk/base/stream_unittest.cc b/talk/base/stream_unittest.cc
index d0c5718..b96279b 100644
--- a/talk/base/stream_unittest.cc
+++ b/talk/base/stream_unittest.cc
@@ -35,14 +35,14 @@
///////////////////////////////////////////////////////////////////////////////
class TestStream : public StreamInterface {
-public:
+ public:
TestStream() : pos_(0) { }
virtual StreamState GetState() const { return SS_OPEN; }
virtual StreamResult Read(void* buffer, size_t buffer_len,
size_t* read, int* error) {
unsigned char* uc_buffer = static_cast<unsigned char*>(buffer);
- for (size_t i=0; i<buffer_len; ++i) {
+ for (size_t i = 0; i < buffer_len; ++i) {
uc_buffer[i] = pos_++;
}
if (read)
@@ -71,14 +71,14 @@
return false;
}
-private:
+ private:
unsigned char pos_;
};
bool VerifyTestBuffer(unsigned char* buffer, size_t len,
unsigned char value) {
bool passed = true;
- for (size_t i=0; i<len; ++i) {
+ for (size_t i = 0; i < len; ++i) {
if (buffer[i] != value++) {
passed = false;
break;
@@ -393,6 +393,15 @@
EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL));
}
+TEST(FifoBufferTest, FullBufferCheck) {
+ FifoBuffer buff(10);
+ buff.ConsumeWriteBuffer(10);
+
+ size_t free;
+ EXPECT_TRUE(buff.GetWriteBuffer(&free) != NULL);
+ EXPECT_EQ(0U, free);
+}
+
TEST(FifoBufferTest, WriteOffsetAndReadOffset) {
const size_t kSize = 16;
const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
@@ -442,4 +451,4 @@
EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
}
-} // namespace talk_base
+} // namespace talk_base
diff --git a/talk/base/systeminfo.cc b/talk/base/systeminfo.cc
new file mode 100644
index 0000000..7cbc418
--- /dev/null
+++ b/talk/base/systeminfo.cc
@@ -0,0 +1,461 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef WIN32
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/win32.h" // first because it brings in win32 stuff
+#ifndef EXCLUDE_D3D9
+#include <d3d9.h>
+#endif
+
+#elif defined(OSX)
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreServices/CoreServices.h>
+#include <sys/sysctl.h>
+#include "talk/base/macconversion.h"
+#elif defined(IOS)
+#include <sys/sysctl.h>
+#elif defined(LINUX) || defined(ANDROID)
+#include <unistd.h>
+#include "talk/base/linux.h"
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/cpuid.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/systeminfo.h"
+
+namespace talk_base {
+
+// See Also: http://msdn.microsoft.com/en-us/library/ms683194(v=vs.85).aspx
+#ifdef WIN32
+typedef BOOL (WINAPI *LPFN_GLPI)(
+ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,
+ PDWORD);
+
+static int NumCores() {
+ // GetLogicalProcessorInformation() is available on Windows XP SP3 and beyond.
+ LPFN_GLPI glpi = reinterpret_cast<LPFN_GLPI>(GetProcAddress(
+ GetModuleHandle(L"kernel32"),
+ "GetLogicalProcessorInformation"));
+ if (NULL == glpi) {
+ return -1;
+ }
+ // Determine buffer size, allocate and get processor information.
+ // Size can change between calls (unlikely), so a loop is done.
+ DWORD return_length = 0;
+ scoped_array<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> infos;
+ while (!glpi(infos.get(), &return_length)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ infos.reset(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[
+ return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]);
+ } else {
+ return -1;
+ }
+ }
+ int processor_core_count = 0;
+ for (size_t i = 0;
+ i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) {
+ if (infos[i].Relationship == RelationProcessorCore) {
+ ++processor_core_count;
+ }
+ }
+ return processor_core_count;
+}
+#endif
+
+// Note(fbarchard):
+// Family and model are extended family and extended model. 8 bits each.
+SystemInfo::SystemInfo()
+ : physical_cpus_(1), logical_cpus_(1),
+ cpu_family_(0), cpu_model_(0), cpu_stepping_(0),
+ cpu_speed_(0), memory_(0) {
+ // Initialize the basic information.
+
+#if defined(__arm__)
+ cpu_arch_ = ARCH_ARM;
+#elif defined(CPU_X86)
+ cpu_arch_ = ARCH_X86;
+#else
+#error "Unknown architecture."
+#endif
+
+#ifdef WIN32
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ logical_cpus_ = si.dwNumberOfProcessors;
+ physical_cpus_ = NumCores();
+ if (physical_cpus_ <= 0) {
+ physical_cpus_ = logical_cpus_;
+ }
+ cpu_family_ = si.wProcessorLevel;
+ cpu_model_ = si.wProcessorRevision >> 8;
+ cpu_stepping_ = si.wProcessorRevision & 0xFF;
+#elif defined(OSX) || defined(IOS)
+ uint32_t sysctl_value;
+ size_t length = sizeof(sysctl_value);
+ if (!sysctlbyname("hw.physicalcpu_max", &sysctl_value, &length, NULL, 0)) {
+ physical_cpus_ = static_cast<int>(sysctl_value);
+ }
+ length = sizeof(sysctl_value);
+ if (!sysctlbyname("hw.logicalcpu_max", &sysctl_value, &length, NULL, 0)) {
+ logical_cpus_ = static_cast<int>(sysctl_value);
+ }
+ length = sizeof(sysctl_value);
+ if (!sysctlbyname("machdep.cpu.family", &sysctl_value, &length, NULL, 0)) {
+ cpu_family_ = static_cast<int>(sysctl_value);
+ }
+ length = sizeof(sysctl_value);
+ if (!sysctlbyname("machdep.cpu.model", &sysctl_value, &length, NULL, 0)) {
+ cpu_model_ = static_cast<int>(sysctl_value);
+ }
+ length = sizeof(sysctl_value);
+ if (!sysctlbyname("machdep.cpu.stepping", &sysctl_value, &length, NULL, 0)) {
+ cpu_stepping_ = static_cast<int>(sysctl_value);
+ }
+#else // LINUX || ANDROID
+ ProcCpuInfo proc_info;
+ if (proc_info.LoadFromSystem()) {
+ proc_info.GetNumCpus(&logical_cpus_);
+ proc_info.GetNumPhysicalCpus(&physical_cpus_);
+ proc_info.GetCpuFamily(&cpu_family_);
+#if !defined(__arm__)
+ // These values aren't found on ARM systems.
+ proc_info.GetSectionIntValue(0, "model", &cpu_model_);
+ proc_info.GetSectionIntValue(0, "stepping", &cpu_stepping_);
+ proc_info.GetSectionIntValue(0, "cpu MHz", &cpu_speed_);
+#endif
+ }
+
+ // ProcCpuInfo reads cpu speed from "cpu MHz" under /proc/cpuinfo.
+ // But that number is a moving target which can change on-the-fly according to
+ // many factors including system workload.
+ // See /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors.
+ // The one in /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq is more
+ // accurate. We use it as our cpu speed when it is available.
+ int max_freq = talk_base::ReadCpuMaxFreq();
+ if (max_freq > 0) {
+ cpu_speed_ = max_freq;
+ }
+#endif
+}
+
+// Return the number of cpu threads available to the system.
+int SystemInfo::GetMaxCpus() {
+ return logical_cpus_;
+}
+
+// Return the number of cpu cores available to the system.
+int SystemInfo::GetMaxPhysicalCpus() {
+ return physical_cpus_;
+}
+
+// Return the number of cpus available to the process. Since affinity can be
+// changed on the fly, do not cache this value.
+// Can be affected by heat.
+int SystemInfo::GetCurCpus() {
+ int cur_cpus;
+#ifdef WIN32
+ DWORD process_mask, system_mask;
+ ::GetProcessAffinityMask(::GetCurrentProcess(), &process_mask, &system_mask);
+ for (cur_cpus = 0; process_mask; ++cur_cpus) {
+ // Sparse-ones algorithm. There are slightly faster methods out there but
+ // they are unintuitive and won't make a difference on a single dword.
+ process_mask &= (process_mask - 1);
+ }
+#elif defined(OSX)
+ // Find number of _available_ cores
+ cur_cpus = MPProcessorsScheduled();
+#elif defined(IOS)
+ uint32_t sysctl_value;
+ size_t length = sizeof(sysctl_value);
+ int error = sysctlbyname("hw.ncpu", &sysctl_value, &length, NULL, 0);
+ cur_cpus = !error ? static_cast<int>(sysctl_value) : 1;
+#else
+ // Linux, Solaris, ANDROID
+ cur_cpus = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
+#endif
+ return cur_cpus;
+}
+
+// Return the type of this CPU.
+SystemInfo::Architecture SystemInfo::GetCpuArchitecture() {
+ return cpu_arch_;
+}
+
+// Returns the vendor string from the cpu, e.g. "GenuineIntel", "AuthenticAMD".
+// See "Intel Processor Identification and the CPUID Instruction"
+// (Intel document number: 241618)
+std::string SystemInfo::GetCpuVendor() {
+ if (cpu_vendor_.empty()) {
+ cpu_vendor_ = talk_base::CpuInfo::GetCpuVendor();
+ }
+ return cpu_vendor_;
+}
+
+// Return the "family" of this CPU.
+int SystemInfo::GetCpuFamily() {
+ return cpu_family_;
+}
+
+// Return the "model" of this CPU.
+int SystemInfo::GetCpuModel() {
+ return cpu_model_;
+}
+
+// Return the "stepping" of this CPU.
+int SystemInfo::GetCpuStepping() {
+ return cpu_stepping_;
+}
+
+// Return the clockrate of the primary processor in Mhz. This value can be
+// cached. Returns -1 on error.
+int SystemInfo::GetMaxCpuSpeed() {
+ if (cpu_speed_) {
+ return cpu_speed_;
+ }
+
+#ifdef WIN32
+ HKEY key;
+ static const WCHAR keyName[] =
+ L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
+
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key)
+ == ERROR_SUCCESS) {
+ DWORD data, len;
+ len = sizeof(data);
+
+ if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
+ &len) == ERROR_SUCCESS) {
+ cpu_speed_ = data;
+ } else {
+ LOG(LS_WARNING) << "Failed to query registry value HKLM\\" << keyName
+ << "\\~Mhz";
+ cpu_speed_ = -1;
+ }
+
+ RegCloseKey(key);
+ } else {
+ LOG(LS_WARNING) << "Failed to open registry key HKLM\\" << keyName;
+ cpu_speed_ = -1;
+ }
+#elif defined(IOS) || defined(OSX)
+ uint64_t sysctl_value;
+ size_t length = sizeof(sysctl_value);
+ int error = sysctlbyname("hw.cpufrequency_max", &sysctl_value, &length, NULL, 0);
+ cpu_speed_ = !error ? static_cast<int>(sysctl_value/1000000) : -1;
+#else
+ // TODO: Implement using proc/cpuinfo
+ cpu_speed_ = 0;
+#endif
+ return cpu_speed_;
+}
+
+// Dynamically check the current clockrate, which could be reduced because of
+// powersaving profiles. Eventually for windows we want to query WMI for
+// root\WMI::ProcessorPerformance.InstanceName="Processor_Number_0".frequency
+int SystemInfo::GetCurCpuSpeed() {
+#ifdef WIN32
+ // TODO: Add WMI check, requires COM initialization
+ // NOTE(fbarchard): Testable on Sandy Bridge.
+ return GetMaxCpuSpeed();
+#elif defined(IOS) || defined(OSX)
+ uint64_t sysctl_value;
+ size_t length = sizeof(sysctl_value);
+ int error = sysctlbyname("hw.cpufrequency", &sysctl_value, &length, NULL, 0);
+ return !error ? static_cast<int>(sysctl_value/1000000) : GetMaxCpuSpeed();
+#else // LINUX || ANDROID
+ // TODO: Use proc/cpuinfo for Cur speed on Linux.
+ return GetMaxCpuSpeed();
+#endif
+}
+
+// Returns the amount of installed physical memory in Bytes. Cacheable.
+// Returns -1 on error.
+int64 SystemInfo::GetMemorySize() {
+ if (memory_) {
+ return memory_;
+ }
+
+#ifdef WIN32
+ MEMORYSTATUSEX status = {0};
+ status.dwLength = sizeof(status);
+
+ if (GlobalMemoryStatusEx(&status)) {
+ memory_ = status.ullTotalPhys;
+ } else {
+ LOG_GLE(LS_WARNING) << "GlobalMemoryStatusEx failed.";
+ memory_ = -1;
+ }
+
+#elif defined(OSX) || defined(IOS)
+ size_t len = sizeof(memory_);
+ int error = sysctlbyname("hw.memsize", &memory_, &len, NULL, 0);
+ if (error || memory_ == 0) {
+ memory_ = -1;
+ }
+#else
+ memory_ = static_cast<int64>(sysconf(_SC_PHYS_PAGES)) *
+ static_cast<int64>(sysconf(_SC_PAGESIZE));
+ if (memory_ < 0) {
+ LOG(LS_WARNING) << "sysconf(_SC_PHYS_PAGES) failed."
+ << "sysconf(_SC_PHYS_PAGES) " << sysconf(_SC_PHYS_PAGES)
+ << "sysconf(_SC_PAGESIZE) " << sysconf(_SC_PAGESIZE);
+ memory_ = -1;
+ }
+#endif
+
+ return memory_;
+}
+
+
+// Return the name of the machine model we are currently running on.
+// This is a human readable string that consists of the name and version
+// number of the hardware, i.e 'MacBookAir1,1'. Returns an empty string if
+// model can not be determined. The string is cached for subsequent calls.
+std::string SystemInfo::GetMachineModel() {
+ if (!machine_model_.empty()) {
+ return machine_model_;
+ }
+
+#if defined(OSX) || defined(IOS)
+ char buffer[128];
+ size_t length = sizeof(buffer);
+ int error = sysctlbyname("hw.model", buffer, &length, NULL, 0);
+ if (!error) {
+ machine_model_.assign(buffer, length - 1);
+ } else {
+ machine_model_.clear();
+ }
+#else
+ machine_model_ = "Not available";
+#endif
+
+ return machine_model_;
+}
+
+#ifdef OSX
+// Helper functions to query IOKit for video hardware properties.
+static CFTypeRef SearchForProperty(io_service_t port, CFStringRef name) {
+ return IORegistryEntrySearchCFProperty(port, kIOServicePlane,
+ name, kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents);
+}
+
+static void GetProperty(io_service_t port, CFStringRef name, int* value) {
+ if (!value) return;
+ CFTypeRef ref = SearchForProperty(port, name);
+ if (ref) {
+ CFTypeID refType = CFGetTypeID(ref);
+ if (CFNumberGetTypeID() == refType) {
+ CFNumberRef number = reinterpret_cast<CFNumberRef>(ref);
+ p_convertCFNumberToInt(number, value);
+ } else if (CFDataGetTypeID() == refType) {
+ CFDataRef data = reinterpret_cast<CFDataRef>(ref);
+ if (CFDataGetLength(data) == sizeof(UInt32)) {
+ *value = *reinterpret_cast<const UInt32*>(CFDataGetBytePtr(data));
+ }
+ }
+ CFRelease(ref);
+ }
+}
+
+static void GetProperty(io_service_t port, CFStringRef name,
+ std::string* value) {
+ if (!value) return;
+ CFTypeRef ref = SearchForProperty(port, name);
+ if (ref) {
+ CFTypeID refType = CFGetTypeID(ref);
+ if (CFStringGetTypeID() == refType) {
+ CFStringRef stringRef = reinterpret_cast<CFStringRef>(ref);
+ p_convertHostCFStringRefToCPPString(stringRef, *value);
+ } else if (CFDataGetTypeID() == refType) {
+ CFDataRef dataRef = reinterpret_cast<CFDataRef>(ref);
+ *value = std::string(reinterpret_cast<const char*>(
+ CFDataGetBytePtr(dataRef)), CFDataGetLength(dataRef));
+ }
+ CFRelease(ref);
+ }
+}
+#endif
+
+// Fills a struct with information on the graphics adapater and returns true
+// iff successful.
+bool SystemInfo::GetGpuInfo(GpuInfo *info) {
+ if (!info) return false;
+#if defined(WIN32) && !defined(EXCLUDE_D3D9)
+ D3DADAPTER_IDENTIFIER9 identifier;
+ HRESULT hr = E_FAIL;
+ HINSTANCE d3d_lib = LoadLibrary(L"d3d9.dll");
+
+ if (d3d_lib) {
+ typedef IDirect3D9* (WINAPI *D3DCreate9Proc)(UINT);
+ D3DCreate9Proc d3d_create_proc = reinterpret_cast<D3DCreate9Proc>(
+ GetProcAddress(d3d_lib, "Direct3DCreate9"));
+ if (d3d_create_proc) {
+ IDirect3D9* d3d = d3d_create_proc(D3D_SDK_VERSION);
+ if (d3d) {
+ hr = d3d->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &identifier);
+ d3d->Release();
+ }
+ }
+ FreeLibrary(d3d_lib);
+ }
+
+ if (hr != D3D_OK) {
+ LOG(LS_ERROR) << "Failed to access Direct3D9 information.";
+ return false;
+ }
+
+ info->device_name = identifier.DeviceName;
+ info->description = identifier.Description;
+ info->vendor_id = identifier.VendorId;
+ info->device_id = identifier.DeviceId;
+ info->driver = identifier.Driver;
+ // driver_version format: product.version.subversion.build
+ std::stringstream ss;
+ ss << HIWORD(identifier.DriverVersion.HighPart) << "."
+ << LOWORD(identifier.DriverVersion.HighPart) << "."
+ << HIWORD(identifier.DriverVersion.LowPart) << "."
+ << LOWORD(identifier.DriverVersion.LowPart);
+ info->driver_version = ss.str();
+ return true;
+#elif defined(OSX)
+ // We'll query the IOKit for the gpu of the main display.
+ io_service_t display_service_port = CGDisplayIOServicePort(
+ kCGDirectMainDisplay);
+ GetProperty(display_service_port, CFSTR("vendor-id"), &info->vendor_id);
+ GetProperty(display_service_port, CFSTR("device-id"), &info->device_id);
+ GetProperty(display_service_port, CFSTR("model"), &info->description);
+ return true;
+#else // LINUX || ANDROID
+ // TODO: Implement this on Linux
+ return false;
+#endif
+}
+} // namespace talk_base
diff --git a/talk/base/systeminfo.h b/talk/base/systeminfo.h
new file mode 100644
index 0000000..cf8e537
--- /dev/null
+++ b/talk/base/systeminfo.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SYSTEMINFO_H__
+#define TALK_BASE_SYSTEMINFO_H__
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class SystemInfo {
+ public:
+ enum Architecture {
+ ARCH_X86 = 0,
+ ARCH_X64 = 1,
+ ARCH_ARM = 2
+ };
+
+ SystemInfo();
+
+ // The number of CPU Cores in the system.
+ int GetMaxPhysicalCpus();
+ // The number of CPU Threads in the system.
+ int GetMaxCpus();
+ // The number of CPU Threads currently available to this process.
+ int GetCurCpus();
+ // Identity of the CPUs.
+ Architecture GetCpuArchitecture();
+ std::string GetCpuVendor();
+ int GetCpuFamily();
+ int GetCpuModel();
+ int GetCpuStepping();
+ // Estimated speed of the CPUs, in MHz.
+ int GetMaxCpuSpeed();
+ int GetCurCpuSpeed();
+ // Total amount of physical memory, in bytes.
+ int64 GetMemorySize();
+ // The model name of the machine, e.g. "MacBookAir1,1"
+ std::string GetMachineModel();
+
+ // The gpu identifier
+ struct GpuInfo {
+ GpuInfo() : vendor_id(0), device_id(0) {}
+ std::string device_name;
+ std::string description;
+ int vendor_id;
+ int device_id;
+ std::string driver;
+ std::string driver_version;
+ };
+ bool GetGpuInfo(GpuInfo *info);
+
+ private:
+ int physical_cpus_;
+ int logical_cpus_;
+ Architecture cpu_arch_;
+ std::string cpu_vendor_;
+ int cpu_family_;
+ int cpu_model_;
+ int cpu_stepping_;
+ int cpu_speed_;
+ int64 memory_;
+ std::string machine_model_;
+};
+
+}
+
+#endif // TALK_BASE_SYSTEMINFO_H__
diff --git a/talk/base/systeminfo_unittest.cc b/talk/base/systeminfo_unittest.cc
new file mode 100644
index 0000000..6df0db5
--- /dev/null
+++ b/talk/base/systeminfo_unittest.cc
@@ -0,0 +1,143 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/systeminfo.h"
+
+#ifdef CPU_X86
+
+// CPUID-based tests only work on X86
+
+// Tests CPUID instruction for Vendor identification.
+TEST(SystemInfoTest, CpuVendorNonEmpty) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "CpuVendor: " << info.GetCpuVendor();
+ EXPECT_FALSE(info.GetCpuVendor().empty());
+}
+
+// Tests Vendor identification is Intel or AMD.
+// See Also http://en.wikipedia.org/wiki/CPUID
+TEST(SystemInfoTest, CpuVendorIntelAMD) {
+ talk_base::SystemInfo info;
+ EXPECT_TRUE(talk_base::string_match(info.GetCpuVendor().c_str(),
+ "GenuineIntel") ||
+ talk_base::string_match(info.GetCpuVendor().c_str(),
+ "AuthenticAMD"));
+}
+
+#endif // CPU_X86
+
+// Tests MachineModel is set.
+TEST(SystemInfoTest, MachineModelNonEmpty) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MachineModel: " << info.GetMachineModel();
+ EXPECT_FALSE(info.GetMachineModel().empty());
+}
+
+// Tests maximum cpu clockrate.
+TEST(SystemInfoTest, CpuMaxCpuSpeed) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MaxCpuSpeed: " << info.GetMaxCpuSpeed();
+ EXPECT_GT(info.GetMaxCpuSpeed(), 0);
+}
+
+// Tests current cpu clockrate.
+TEST(SystemInfoTest, CpuCurCpuSpeed) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MaxCurSpeed: " << info.GetCurCpuSpeed();
+ EXPECT_GT(info.GetCurCpuSpeed(), 0);
+}
+
+// Tests physical memory size.
+TEST(SystemInfoTest, MemorySize) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MemorySize: " << info.GetMemorySize();
+ EXPECT_GT(info.GetMemorySize(), -1);
+}
+
+// Tests number of logical cpus available to the system.
+TEST(SystemInfoTest, MaxCpus) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MaxCpus: " << info.GetMaxCpus();
+ EXPECT_GT(info.GetMaxCpus(), 0);
+}
+
+// Tests number of physical cpus available to the system.
+TEST(SystemInfoTest, MaxPhysicalCpus) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "MaxPhysicalCpus: " << info.GetMaxPhysicalCpus();
+ EXPECT_GT(info.GetMaxPhysicalCpus(), 0);
+ EXPECT_LE(info.GetMaxPhysicalCpus(), info.GetMaxCpus());
+}
+
+// Tests number of logical cpus available to the process.
+TEST(SystemInfoTest, CurCpus) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "CurCpus: " << info.GetCurCpus();
+ EXPECT_GT(info.GetCurCpus(), 0);
+ EXPECT_LE(info.GetCurCpus(), info.GetMaxCpus());
+}
+
+#ifdef CPU_X86
+
+// CPU family/model/steeping is only available on X86
+
+// Tests Intel CPU Family identification.
+TEST(SystemInfoTest, CpuFamilyNonZero) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "CpuFamily: " << info.GetCpuFamily();
+ EXPECT_GT(info.GetCpuFamily(), 0);
+}
+
+// Tests Intel CPU Model identification.
+TEST(SystemInfoTest, CpuModelNonZero) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "CpuModel: " << info.GetCpuModel();
+ EXPECT_GT(info.GetCpuModel(), 0);
+}
+
+// Tests Intel CPU Stepping identification.
+TEST(SystemInfoTest, CpuSteppingNonZero) {
+ talk_base::SystemInfo info;
+ LOG(LS_INFO) << "CpuStepping: " << info.GetCpuStepping();
+ EXPECT_GT(info.GetCpuStepping(), 0);
+}
+
+#endif // CPU_X86
+
+#if WIN32 && !defined(EXCLUDE_D3D9)
+TEST(SystemInfoTest, GpuInfo) {
+ talk_base::SystemInfo info;
+ talk_base::SystemInfo::GpuInfo gi;
+ EXPECT_TRUE(info.GetGpuInfo(&gi));
+ LOG(LS_INFO) << "GpuDriver: " << gi.driver;
+ EXPECT_FALSE(gi.driver.empty());
+ LOG(LS_INFO) << "GpuDriverVersion: " << gi.driver_version;
+ EXPECT_FALSE(gi.driver_version.empty());
+}
+#endif
diff --git a/talk/base/task.cc b/talk/base/task.cc
index ad17438..c37797c 100644
--- a/talk/base/task.cc
+++ b/talk/base/task.cc
@@ -197,22 +197,15 @@
}
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;
+ case STATE_BLOCKED: return "BLOCKED";
+ case STATE_INIT: return "INIT";
+ case STATE_START: return "START";
+ case STATE_DONE: return "DONE";
+ case STATE_ERROR: return "ERROR";
+ case STATE_RESPONSE: return "RESPONSE";
}
- return STR_HUH;
+ return "??";
}
int Task::Process(int state) {
diff --git a/talk/base/task_unittest.cc b/talk/base/task_unittest.cc
index 076ff6f..0c4a7a2 100644
--- a/talk/base/task_unittest.cc
+++ b/talk/base/task_unittest.cc
@@ -43,7 +43,7 @@
#include "talk/base/task.h"
#include "talk/base/taskrunner.h"
#include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
namespace talk_base {
diff --git a/talk/base/testbase64.h b/talk/base/testbase64.h
new file mode 100644
index 0000000..39dd00c
--- /dev/null
+++ b/talk/base/testbase64.h
@@ -0,0 +1,5 @@
+/* This file was generated by googleclient/talk/binary2header.sh */
+
+static unsigned char testbase64[] = {
+0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xe1, 0x0d, 0x07, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x9e, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0xbe, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0xc3, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xcc, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd4, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x04, 0x02, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x02, 0xc4, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x53, 0x4f, 0x4e, 0x59, 0x00, 0x44, 0x53, 0x43, 0x2d, 0x50, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x37, 0x2e, 0x30, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x33, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x31, 0x30, 0x3a, 0x30, 0x34, 0x00, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x58, 0x20, 0x31, 0x30, 0x2e, 0x34, 0x2e, 0x38, 0x00, 0x00, 0x1c, 0x82, 0x9a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x6a, 0x82, 0x9d, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x72, 0x88, 0x22, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x88, 0x27, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x00, 0x00, 0x90, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x7a, 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x02, 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xa2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xaa, 0x92, 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xb2, 0x92, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x92, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x92, 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xba, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa0, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0a, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x32, 0x30, 0x30, 0x37, 0x3a, 0x30, 0x31, 0x3a, 0x32, 0x30, 0x20, 0x32, 0x33, 0x3a, 0x30, 0x35, 0x3a, 0x35, 0x32, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x12, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x1a, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x22, 0x02, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0xff, 0xed, 0x2e, 0x1c, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20, 0x33, 0x2e, 0x30, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x1c, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x1c, 0x02, 0x78, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfb, 0x09, 0xa6, 0xbd, 0x07, 0x4c, 0x2a, 0x36, 0x9d, 0x8f, 0xe2, 0xcc, 0x57, 0xa9, 0xac, 0x85, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xea, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xb0, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x22, 0x2d, 0x2f, 0x2f, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x2f, 0x44, 0x54, 0x44, 0x20, 0x50, 0x4c, 0x49, 0x53, 0x54, 0x20, 0x31, 0x2e, 0x30, 0x2f, 0x2f, 0x45, 0x4e, 0x22, 0x20, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x54, 0x44, 0x73, 0x2f, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x2d, 0x31, 0x2e, 0x30, 0x2e, 0x64, 0x74, 0x64, 0x22, 0x3e, 0x0a, 0x3c, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x48, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x32, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x31, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x73, 0x75, 0x62, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x70, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x50, 0x4d, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x6e, 0x61, 0x2d, 0x6c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x30, 0x2e, 0x30, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x33, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x37, 0x36, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x61, 0x70, 0x65, 0x72, 0x52, 0x65, 0x63, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x2d, 0x31, 0x38, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x37, 0x37, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x35, 0x39, 0x34, 0x3c, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x2d, 0x33, 0x30, 0x54, 0x32, 0x32, 0x3a, 0x30, 0x38, 0x3a, 0x34, 0x31, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x30, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x70, 0x64, 0x2e, 0x50, 0x4d, 0x50, 0x61, 0x70, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x55, 0x53, 0x20, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x70, 0x6d, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x30, 0x37, 0x2d, 0x30, 0x31, 0x54, 0x31, 0x37, 0x3a, 0x34, 0x39, 0x3a, 0x33, 0x36, 0x5a, 0x3c, 0x2f, 0x64, 0x61, 0x74, 0x65, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x31, 0x3c, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x3c, 0x2f, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x70, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x41, 0x50, 0x49, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x30, 0x30, 0x2e, 0x32, 0x30, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x63, 0x6b, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2f, 0x3e, 0x0a, 0x09, 0x3c, 0x6b, 0x65, 0x79, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x3c, 0x2f, 0x6b, 0x65, 0x79, 0x3e, 0x0a, 0x09, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x3c, 0x2f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x63, 0x74, 0x3e, 0x0a, 0x3c, 0x2f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x3e, 0x0a, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xde, 0x02, 0x40, 0xff, 0xee, 0xff, 0xee, 0x03, 0x06, 0x02, 0x52, 0x03, 0x67, 0x05, 0x28, 0x03, 0xfc, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x02, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x7f, 0xff, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x08, 0x00, 0x19, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x27, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0x6c, 0x66, 0x66, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2f, 0x66, 0x66, 0x00, 0x01, 0x00, 0xa1, 0x99, 0x9a, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x03, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x53, 0x00, 0x43, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x32, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x06, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x56, 0x6c, 0x4c, 0x73, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x07, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x44, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0c, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x6f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0a, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6d, 0x67, 0x20, 0x00, 0x00, 0x00, 0x06, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x4f, 0x62, 0x6a, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x63, 0x74, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x54, 0x6f, 0x70, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x66, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x74, 0x6f, 0x6d, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x52, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6c, 0x6c, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x73, 0x67, 0x65, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x61, 0x6c, 0x74, 0x54, 0x61, 0x67, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x49, 0x73, 0x48, 0x54, 0x4d, 0x4c, 0x62, 0x6f, 0x6f, 0x6c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x63, 0x65, 0x6c, 0x6c, 0x54, 0x65, 0x78, 0x74, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x68, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x48, 0x6f, 0x72, 0x7a, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x09, 0x76, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x0f, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x56, 0x65, 0x72, 0x74, 0x41, 0x6c, 0x69, 0x67, 0x6e, 0x00, 0x00, 0x00, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x00, 0x00, 0x0b, 0x62, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x65, 0x6e, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x11, 0x45, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x42, 0x47, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x09, 0x74, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x6c, 0x65, 0x66, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x72, 0x69, 0x67, 0x68, 0x74, 0x4f, 0x75, 0x74, 0x73, 0x65, 0x74, 0x6c, 0x6f, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x09, 0xf9, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x09, 0xdd, 0x00, 0x18, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xed, 0x00, 0x0c, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x5f, 0x43, 0x4d, 0x00, 0x02, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0c, 0x09, 0x09, 0x0c, 0x11, 0x0b, 0x0a, 0x0b, 0x11, 0x15, 0x0f, 0x0c, 0x0c, 0x0f, 0x15, 0x18, 0x13, 0x13, 0x15, 0x13, 0x13, 0x18, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x0d, 0x0b, 0x0b, 0x0d, 0x0e, 0x0d, 0x10, 0x0e, 0x0e, 0x10, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x07, 0xff, 0xc4, 0x01, 0x3f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x01, 0x04, 0x01, 0x03, 0x02, 0x04, 0x02, 0x05, 0x07, 0x06, 0x08, 0x05, 0x03, 0x0c, 0x33, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x05, 0x41, 0x51, 0x61, 0x13, 0x22, 0x71, 0x81, 0x32, 0x06, 0x14, 0x91, 0xa1, 0xb1, 0x42, 0x23, 0x24, 0x15, 0x52, 0xc1, 0x62, 0x33, 0x34, 0x72, 0x82, 0xd1, 0x43, 0x07, 0x25, 0x92, 0x53, 0xf0, 0xe1, 0xf1, 0x63, 0x73, 0x35, 0x16, 0xa2, 0xb2, 0x83, 0x26, 0x44, 0x93, 0x54, 0x64, 0x45, 0xc2, 0xa3, 0x74, 0x36, 0x17, 0xd2, 0x55, 0xe2, 0x65, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x27, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x35, 0x01, 0x00, 0x02, 0x11, 0x03, 0x21, 0x31, 0x12, 0x04, 0x41, 0x51, 0x61, 0x71, 0x22, 0x13, 0x05, 0x32, 0x81, 0x91, 0x14, 0xa1, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xf0, 0x33, 0x24, 0x62, 0xe1, 0x72, 0x82, 0x92, 0x43, 0x53, 0x15, 0x63, 0x73, 0x34, 0xf1, 0x25, 0x06, 0x16, 0xa2, 0xb2, 0x83, 0x07, 0x26, 0x35, 0xc2, 0xd2, 0x44, 0x93, 0x54, 0xa3, 0x17, 0x64, 0x45, 0x55, 0x36, 0x74, 0x65, 0xe2, 0xf2, 0xb3, 0x84, 0xc3, 0xd3, 0x75, 0xe3, 0xf3, 0x46, 0x94, 0xa4, 0x85, 0xb4, 0x95, 0xc4, 0xd4, 0xe4, 0xf4, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x27, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf2, 0xed, 0xb2, 0x8d, 0x4d, 0x45, 0xcd, 0x2f, 0x3f, 0x44, 0x68, 0x93, 0xc3, 0x58, 0xc8, 0xf1, 0x1f, 0x8a, 0x33, 0x86, 0xda, 0x58, 0xc1, 0xa0, 0x02, 0x4f, 0xc4, 0xa1, 0x69, 0xa5, 0x9b, 0x5b, 0x4b, 0x84, 0x73, 0xdf, 0xc9, 0x15, 0xf8, 0xe3, 0xd1, 0x0e, 0x07, 0x93, 0xf3, 0xd1, 0x0f, 0x1c, 0x17, 0xef, 0x2e, 0x3b, 0x5b, 0xdc, 0xff, 0x00, 0xdf, 0x42, 0xbf, 0x8f, 0x8e, 0xdc, 0x82, 0xca, 0xd8, 0x37, 0x11, 0xa9, 0x3d, 0x82, 0x69, 0x2b, 0xc4, 0x6d, 0xc9, 0x75, 0x25, 0xbc, 0xf7, 0xec, 0xa1, 0xb5, 0x74, 0x19, 0x5d, 0x2e, 0x8a, 0x9a, 0x4b, 0x89, 0x7d, 0xc4, 0x68, 0xc6, 0xf6, 0xfe, 0xb2, 0xa0, 0x30, 0x1d, 0x60, 0x86, 0x88, 0x8d, 0x49, 0x3e, 0x01, 0x11, 0x20, 0xa3, 0x8c, 0xb9, 0xb1, 0xaa, 0x62, 0xad, 0xbf, 0x18, 0x97, 0x43, 0x47, 0x1d, 0xd2, 0xaf, 0x04, 0xd9, 0xb8, 0xc8, 0x0d, 0x68, 0xe4, 0xf7, 0x3e, 0x48, 0xf1, 0x05, 0xbc, 0x25, 0xaa, 0x07, 0x71, 0xd9, 0x14, 0x78, 0xf6, 0x49, 0xb5, 0x90, 0xfd, 0xa7, 0xc6, 0x14, 0xfd, 0x1b, 0x1c, 0xff, 0x00, 0x4d, 0x8d, 0x2e, 0x73, 0x8c, 0x35, 0xa3, 0x52, 0x4f, 0x92, 0x48, 0xa6, 0x1a, 0x24, 0xb6, 0x2a, 0xfa, 0xa5, 0x9e, 0x60, 0x64, 0x39, 0x94, 0x13, 0xcb, 0x27, 0x73, 0x80, 0xf3, 0x0c, 0xf6, 0xff, 0x00, 0xd2, 0x5a, 0x78, 0xbf, 0x53, 0x71, 0xf6, 0x01, 0x75, 0xb6, 0x97, 0x6a, 0x25, 0xa1, 0xad, 0x1f, 0xf4, 0xb7, 0x23, 0x48, 0xb7, 0x94, 0x84, 0x97, 0x5b, 0xff, 0x00, 0x32, 0xa9, 0xdd, 0xfc, 0xed, 0x9b, 0x7e, 0x0d, 0x9e, 0x52, 0x4a, 0x95, 0x61, 0xff, 0xd0, 0xf3, 0x3b, 0xa7, 0x70, 0xee, 0x01, 0x8f, 0xb9, 0x59, 0xfa, 0x7e, 0xdf, 0xe4, 0xc8, 0xf9, 0x2a, 0xc2, 0x5c, 0x63, 0xc3, 0x54, 0x67, 0x87, 0x6e, 0x10, 0x35, 0x68, 0xd4, 0x79, 0x1e, 0x53, 0x4a, 0xe0, 0xdc, 0xe9, 0xb8, 0x1f, 0x6a, 0xda, 0x6c, 0x25, 0x94, 0x37, 0xb0, 0xd0, 0xb8, 0xad, 0x67, 0xe4, 0x55, 0x8a, 0x5b, 0x8b, 0x82, 0xc0, 0x6f, 0x76, 0x80, 0x34, 0x49, 0x05, 0x2e, 0x9e, 0xc6, 0x1c, 0x66, 0x31, 0xba, 0x10, 0x23, 0xe0, 0xaf, 0xe1, 0x61, 0x53, 0x43, 0x8d, 0x81, 0xb3, 0x67, 0xef, 0x9e, 0x49, 0x2a, 0x12, 0x6c, 0xb6, 0x63, 0x1a, 0x0c, 0x31, 0xba, 0x55, 0xcd, 0xac, 0xfa, 0x8e, 0xdf, 0x91, 0x6e, 0x91, 0xd9, 0xb3, 0xc9, 0x73, 0x90, 0x7a, 0xab, 0x6a, 0xc2, 0xa4, 0x60, 0xe2, 0x8f, 0xd2, 0x38, 0x03, 0x7d, 0x9e, 0x0d, 0xff, 0x00, 0xcc, 0xd6, 0xd3, 0x6b, 0x71, 0x67, 0xd2, 0x3e, 0x64, 0x72, 0xab, 0xdb, 0x8d, 0x54, 0x39, 0xc5, 0x83, 0x6b, 0x3d, 0xee, 0x2e, 0xd4, 0x92, 0x3c, 0x4a, 0x56, 0xba, 0xb4, 0x79, 0x5c, 0xf7, 0xb2, 0x96, 0x6c, 0x8d, 0xaf, 0x80, 0x48, 0x3c, 0xf0, 0xb2, 0x1f, 0x63, 0x9c, 0xe9, 0x3f, 0x24, 0x5c, 0xdb, 0xdd, 0x76, 0x43, 0xde, 0xfd, 0x5c, 0xe3, 0x24, 0xfc, 0x50, 0x00, 0x93, 0x0a, 0x78, 0x8a, 0x0d, 0x49, 0xca, 0xcf, 0x93, 0x63, 0x1b, 0x7d, 0xd7, 0x57, 0x50, 0xd5, 0xef, 0x70, 0x6b, 0x4f, 0xc7, 0x45, 0xdb, 0x74, 0x9e, 0x8d, 0x5e, 0x33, 0x83, 0xd8, 0x37, 0xdd, 0xc3, 0xac, 0x3d, 0xbf, 0x92, 0xc5, 0x5b, 0xea, 0xbf, 0xd5, 0x62, 0xc0, 0xdc, 0xbc, 0xbd, 0x2d, 0x22, 0x5a, 0xcf, 0xdd, 0x69, 0xff, 0x00, 0xd1, 0x8e, 0x5d, 0xa5, 0x38, 0xb5, 0xb0, 0x00, 0xc6, 0xc4, 0x24, 0x4a, 0xd6, 0x8d, 0x18, 0x04, 0x49, 0x88, 0x9e, 0x55, 0xd6, 0x61, 0xb0, 0xc1, 0x70, 0x32, 0xdd, 0x3c, 0x95, 0xda, 0xf1, 0xfe, 0xf5, 0x62, 0xbc, 0x76, 0x8e, 0x75, 0x28, 0x02, 0xa2, 0xe7, 0x7d, 0x92, 0xb9, 0x84, 0x96, 0x96, 0xda, 0xf7, 0x70, 0x12, 0x4e, 0x5a, 0xff, 0x00, 0xff, 0xd1, 0xf3, 0x7a, 0x21, 0xaf, 0xde, 0xef, 0xa2, 0x22, 0x55, 0xfc, 0x5a, 0xbd, 0x42, 0xfb, 0x08, 0xfa, 0x67, 0x4f, 0x82, 0xcd, 0x6d, 0x85, 0xc0, 0x56, 0x3b, 0x90, 0xb7, 0xf0, 0x2a, 0x0e, 0x63, 0x58, 0x3b, 0xf2, 0xa3, 0x9e, 0x8c, 0xb8, 0x86, 0xbe, 0x49, 0xf1, 0x2c, 0x0c, 0x86, 0xb4, 0x4c, 0x69, 0xe4, 0xaf, 0x6e, 0xcc, 0x6b, 0x7d, 0x46, 0xb3, 0x70, 0xec, 0x38, 0x51, 0x7d, 0x02, 0x8a, 0xc7, 0xa6, 0xd9, 0x20, 0x68, 0x0f, 0x8f, 0x8a, 0xcf, 0xc9, 0xc2, 0xea, 0x59, 0x5b, 0x48, 0xb0, 0x91, 0xae, 0xe6, 0xc9, 0x03, 0xc9, 0x30, 0x51, 0x66, 0xd4, 0x0d, 0xad, 0xbd, 0x5f, 0x53, 0xcc, 0x6b, 0xb6, 0x90, 0x5a, 0x3b, 0x83, 0x0b, 0x43, 0x17, 0x31, 0xd6, 0xc3, 0x6e, 0x12, 0x3b, 0x79, 0xac, 0xc1, 0x89, 0x47, 0xd9, 0xe8, 0x63, 0x98, 0x45, 0xed, 0x6c, 0x5a, 0xf1, 0xa0, 0x27, 0xc5, 0x5b, 0xc3, 0x6f, 0xa6, 0xe0, 0x1c, 0x7d, 0xb3, 0xa2, 0x69, 0x34, 0x7b, 0xae, 0x1a, 0x8d, 0x45, 0x17, 0x9d, 0xeb, 0xfd, 0x21, 0xd8, 0xb9, 0xae, 0xb5, 0x80, 0xbb, 0x1e, 0xd2, 0x5c, 0xd7, 0x78, 0x13, 0xf9, 0xae, 0x4b, 0xea, 0xc7, 0x4a, 0x39, 0xbd, 0x55, 0xb3, 0xed, 0x66, 0x38, 0xf5, 0x09, 0x22, 0x41, 0x23, 0xe8, 0x37, 0xfb, 0x4b, 0xa1, 0xeb, 0xd6, 0xfe, 0x88, 0x31, 0xbf, 0x41, 0xc0, 0xee, 0xd2, 0x74, 0x02, 0x78, 0x53, 0xfa, 0x97, 0x43, 0x19, 0x85, 0x65, 0xff, 0x00, 0x9d, 0x71, 0x33, 0xe4, 0x1a, 0x7d, 0x8d, 0x53, 0x42, 0x56, 0x35, 0x6b, 0xe5, 0x80, 0x06, 0xc7, 0x57, 0xa7, 0xc4, 0xa9, 0xdb, 0xb6, 0x81, 0x1f, 0xeb, 0xd9, 0x69, 0x56, 0xc2, 0xd0, 0x00, 0xe5, 0x55, 0xc0, 0x12, 0xc2, 0xd7, 0x4e, 0xa2, 0x5a, 0x7c, 0x0a, 0xd0, 0x63, 0x9a, 0xd1, 0xaf, 0xd2, 0xe2, 0x3c, 0x12, 0x62, 0x66, 0xc6, 0x42, 0x23, 0x5a, 0x49, 0x8f, 0x10, 0xa2, 0xd2, 0x3e, 0x28, 0x9d, 0xc4, 0x88, 0x09, 0x29, 0x16, 0xc3, 0x3c, 0x24, 0x8d, 0xe6, 0x92, 0x72, 0x1f, 0xff, 0xd2, 0xf3, 0xbb, 0xb0, 0xfe, 0xcb, 0x99, 0xe9, 0xce, 0xf6, 0x88, 0x2d, 0x77, 0x91, 0x5b, 0x3d, 0x3d, 0xd0, 0xe6, 0x90, 0xa9, 0x65, 0x57, 0x38, 0x95, 0xdd, 0xcb, 0x9a, 0x7d, 0xce, 0xf2, 0x3f, 0x44, 0x23, 0x60, 0x58, 0x76, 0xe9, 0xca, 0x8c, 0xea, 0x1b, 0x31, 0x02, 0x32, 0x23, 0xea, 0xee, 0xb1, 0xcd, 0xb0, 0xc7, 0x87, 0x74, 0x7a, 0xeb, 0x70, 0x1a, 0x71, 0xe1, 0xfe, 0xe4, 0x1c, 0x1d, 0xae, 0xe5, 0x69, 0xd8, 0xfa, 0x99, 0x50, 0x0d, 0x1a, 0xf7, 0x2a, 0x3a, 0x0c, 0xf4, 0x1a, 0x8e, 0xc7, 0x27, 0x5d, 0xbf, 0x18, 0x41, 0xdc, 0xc2, 0xf0, 0x7f, 0x74, 0xf6, 0x3a, 0x22, 0x66, 0xdb, 0x68, 0xc6, 0x80, 0x48, 0x6b, 0x88, 0x06, 0x39, 0x0d, 0xee, 0xaa, 0x1f, 0xb3, 0xd5, 0x1b, 0x83, 0xd8, 0x3b, 0x38, 0x8f, 0x69, 0xfe, 0xdf, 0xd1, 0x4d, 0x29, 0xa1, 0x4c, 0x7a, 0xf4, 0xbf, 0xa7, 0x92, 0xcf, 0xa5, 0x20, 0x08, 0xf3, 0xf6, 0xff, 0x00, 0x15, 0xbb, 0xd1, 0x31, 0xd9, 0x5e, 0x3d, 0x75, 0x56, 0x36, 0x88, 0x00, 0x81, 0xe0, 0x16, 0x5e, 0x55, 0x74, 0x3f, 0x00, 0x9d, 0xe0, 0xcc, 0x69, 0xe7, 0x3a, 0x2d, 0xbe, 0x90, 0x00, 0xa9, 0xae, 0xef, 0x1f, 0x95, 0x4b, 0x0d, 0x9a, 0xdc, 0xc7, 0x45, 0xfe, 0xb1, 0x7d, 0x60, 0xa7, 0xa1, 0xe0, 0x1f, 0x4e, 0x1d, 0x99, 0x69, 0x02, 0x9a, 0xcf, 0x1f, 0xca, 0x7b, 0xbf, 0x90, 0xc5, 0xc2, 0xb3, 0xeb, 0x57, 0xd6, 0x03, 0x6b, 0xae, 0x39, 0xb6, 0x82, 0xe3, 0x31, 0xa1, 0x68, 0xf2, 0x6b, 0x5c, 0x12, 0xfa, 0xe1, 0x91, 0x66, 0x47, 0x5d, 0xb8, 0x3b, 0x4f, 0x44, 0x36, 0xb6, 0x8f, 0x28, 0xdd, 0xff, 0x00, 0x7e, 0x46, 0xab, 0x12, 0x2b, 0x65, 0x55, 0x32, 0xa7, 0x62, 0xb6, 0xbd, 0xf7, 0x64, 0x10, 0xdb, 0x03, 0x9f, 0x1b, 0x9e, 0xc7, 0xd9, 0xb8, 0x3b, 0x1f, 0x67, 0xf3, 0x6c, 0x52, 0x80, 0xd7, 0x7d, 0x0f, 0xea, 0x7f, 0x5d, 0x1d, 0x67, 0xa6, 0x0b, 0x1e, 0x47, 0xda, 0x69, 0x3b, 0x2e, 0x03, 0xc7, 0xf3, 0x5f, 0x1f, 0xf0, 0x8b, 0xa1, 0x02, 0x46, 0xba, 0x79, 0xaf, 0x32, 0xff, 0x00, 0x16, 0xad, 0xca, 0x1d, 0x57, 0x2a, 0xdc, 0x79, 0x18, 0x41, 0xb0, 0xf6, 0x9e, 0xe4, 0x9f, 0xd0, 0x8f, 0xeb, 0x31, 0xab, 0xd2, 0x83, 0xa4, 0xcb, 0x8c, 0xb8, 0xa0, 0x42, 0x12, 0x7b, 0x67, 0x9f, 0x2f, 0xf5, 0x09, 0x26, 0x96, 0xc4, 0xce, 0xa9, 0x20, 0xa7, 0xff, 0xd3, 0xf3, 0x2f, 0xb4, 0x5d, 0xe9, 0x0a, 0xb7, 0x9f, 0x4c, 0x19, 0xdb, 0x3a, 0x2d, 0x5e, 0x94, 0xfd, 0xc4, 0xb7, 0xc5, 0x62, 0xf9, 0x2b, 0xfd, 0x2e, 0xe3, 0x5d, 0xe0, 0x7c, 0x13, 0x48, 0xd1, 0x92, 0x12, 0xa9, 0x0b, 0x7a, 0xbc, 0x2d, 0xc2, 0x7f, 0x92, 0x60, 0xab, 0x4e, 0x79, 0x2e, 0x00, 0xf0, 0xaa, 0xe1, 0xda, 0x3d, 0x43, 0xfc, 0xad, 0x55, 0xbb, 0x80, 0x79, 0x81, 0xa0, 0xe6, 0x54, 0x32, 0x6d, 0x02, 0xbe, 0xf3, 0x61, 0x81, 0xa8, 0x44, 0x14, 0x03, 0x59, 0x0e, 0x1c, 0xf6, 0x1f, 0xdc, 0xb2, 0xec, 0xa3, 0x23, 0x77, 0xe8, 0x6e, 0x70, 0xf2, 0x25, 0x1f, 0x1f, 0x17, 0xa9, 0x6d, 0x71, 0x36, 0x97, 0x47, 0x00, 0xa4, 0x02, 0xe0, 0x2c, 0x7c, 0xc1, 0xab, 0xd5, 0x31, 0x85, 0x35, 0xd4, 0xe6, 0x13, 0x02, 0xd6, 0x4b, 0x67, 0x48, 0x2b, 0xa9, 0xe9, 0x2e, 0x02, 0xb6, 0x4f, 0x82, 0xe5, 0x7a, 0x95, 0x19, 0xc6, 0x87, 0x3d, 0xfb, 0xa2, 0xb8, 0x79, 0x1e, 0x4d, 0x3b, 0x96, 0xcf, 0x4f, 0xbd, 0xcd, 0xa2, 0xa2, 0x1f, 0xa0, 0x82, 0xd3, 0xfc, 0x97, 0x05, 0x24, 0x36, 0x6b, 0xf3, 0x31, 0xa2, 0x35, 0x79, 0xef, 0xad, 0xf8, 0xae, 0xaf, 0xaf, 0xd8, 0xf2, 0xd8, 0x6d, 0xed, 0x6b, 0xda, 0x7b, 0x18, 0x1b, 0x5d, 0xff, 0x00, 0x52, 0xb1, 0x6d, 0xf0, 0x81, 0x31, 0xca, 0xf4, 0x6e, 0xb1, 0x80, 0xce, 0xb1, 0x84, 0xc0, 0x21, 0xb7, 0xd6, 0x77, 0x31, 0xd1, 0x27, 0xc1, 0xcd, 0xfe, 0xd2, 0xe3, 0xec, 0xe8, 0x1d, 0x45, 0x96, 0xb0, 0x9a, 0xb7, 0x87, 0x3f, 0x68, 0x2d, 0xf7, 0x01, 0x1f, 0xbe, 0xd1, 0xf4, 0x7f, 0xb4, 0xa4, 0x0d, 0x77, 0xbb, 0xfa, 0x8f, 0x80, 0x3a, 0x7f, 0x43, 0xaa, 0xe2, 0xdf, 0xd2, 0x65, 0x7e, 0x95, 0xe4, 0x0f, 0x1f, 0xa1, 0xfe, 0x6b, 0x16, 0x9f, 0x52, 0xfa, 0xc1, 0xd3, 0xba, 0x6d, 0x26, 0xdc, 0xac, 0x86, 0xd4, 0xd9, 0x0d, 0x31, 0x2e, 0x74, 0x9e, 0xdb, 0x59, 0x2e, 0x55, 0xe8, 0xc9, 0xb2, 0x96, 0xd5, 0x4b, 0x9f, 0xb8, 0x6d, 0xda, 0x1c, 0x04, 0x09, 0x03, 0xfe, 0x8a, 0xc6, 0xfa, 0xd3, 0xf5, 0x6a, 0xbe, 0xbb, 0x5b, 0x2e, 0xc6, 0xb5, 0x94, 0xe6, 0xd5, 0x20, 0x97, 0x7d, 0x1b, 0x1b, 0xf9, 0xad, 0x7c, 0x7d, 0x17, 0xb7, 0xf3, 0x1e, 0x92, 0x1b, 0x7f, 0xf8, 0xe0, 0x7d, 0x59, 0xdd, 0xfd, 0x32, 0xd8, 0x8f, 0xa5, 0xe8, 0x3a, 0x12, 0x5c, 0x3f, 0xfc, 0xc4, 0xfa, 0xc3, 0xb3, 0x77, 0xa7, 0x56, 0xed, 0xdb, 0x76, 0x7a, 0x8d, 0xdd, 0x1f, 0xbf, 0xfd, 0x44, 0x92, 0x56, 0x8f, 0xff, 0xd4, 0xf2, 0xe8, 0x86, 0x17, 0x1e, 0xfa, 0x04, 0x56, 0x4b, 0x43, 0x6c, 0x6f, 0x2d, 0xe5, 0x46, 0x01, 0x64, 0x2b, 0x14, 0x32, 0x5b, 0xb4, 0xa0, 0x52, 0x1d, 0xde, 0x9b, 0x94, 0xdb, 0xab, 0x6b, 0x81, 0xf7, 0x05, 0xb0, 0xd7, 0x07, 0xb2, 0x27, 0x55, 0xc6, 0x57, 0x65, 0xd8, 0x76, 0x6e, 0x64, 0xed, 0xee, 0x16, 0xce, 0x27, 0x57, 0x63, 0xda, 0x0c, 0xc2, 0x8e, 0x51, 0x67, 0x84, 0xfa, 0x1d, 0xdd, 0x62, 0xc7, 0x07, 0xe9, 0xf7, 0xa3, 0xd6, 0x6c, 0x02, 0x41, 0x55, 0x31, 0xf3, 0x2b, 0xb3, 0xba, 0x2b, 0x2e, 0x68, 0x24, 0x1d, 0x47, 0x64, 0xca, 0xa6, 0x50, 0x41, 0x65, 0x90, 0x6c, 0xb1, 0xa5, 0xae, 0x33, 0x23, 0x51, 0xe4, 0xab, 0x7d, 0x5d, 0xcb, 0xb6, 0xcc, 0x37, 0xd0, 0x40, 0x73, 0x71, 0xde, 0x58, 0x09, 0xe7, 0x6f, 0x2c, 0x44, 0xc9, 0xc9, 0xae, 0xba, 0x9d, 0x63, 0x88, 0x01, 0xa0, 0x95, 0x9d, 0xf5, 0x3f, 0x2a, 0xe6, 0x67, 0xdb, 0x50, 0x83, 0x55, 0xad, 0x36, 0x3e, 0x78, 0x10, 0x74, 0x77, 0xfd, 0x2d, 0xaa, 0x4c, 0x7d, 0x58, 0x73, 0x91, 0xa0, 0x0f, 0x51, 0x45, 0xb7, 0x33, 0xdd, 0x58, 0x69, 0x1d, 0xd8, 0x0c, 0x9f, 0x96, 0x88, 0x19, 0x99, 0x19, 0xac, 0xcf, 0xa3, 0xd2, 0xad, 0xb5, 0xdb, 0x76, 0x8f, 0xad, 0xc4, 0xea, 0xcf, 0xdf, 0x7e, 0xdf, 0xdd, 0xfc, 0xd5, 0xa3, 0x5e, 0x43, 0x2b, 0x6b, 0xb2, 0xad, 0x3b, 0x6a, 0xa4, 0x13, 0xa7, 0x04, 0xac, 0x7a, 0x6f, 0xb3, 0x23, 0x26, 0xcc, 0xfb, 0xb4, 0x75, 0x8e, 0x01, 0x83, 0xf7, 0x58, 0x3e, 0x8b, 0x53, 0xa7, 0x2a, 0x1a, 0x31, 0x42, 0x36, 0x5d, 0x4c, 0x9a, 0xf2, 0xdc, 0xc6, 0xfe, 0x98, 0xb4, 0x34, 0xcb, 0x48, 0x0a, 0x8f, 0xdb, 0xb2, 0xeb, 0x76, 0xd6, 0x07, 0x5c, 0x59, 0xc9, 0x64, 0x8f, 0x93, 0xa7, 0x73, 0x16, 0x83, 0xaf, 0x0e, 0xa4, 0x33, 0xef, 0x50, 0xc5, 0x0c, 0xda, 0x59, 0x10, 0x06, 0x8a, 0x2e, 0x29, 0x0e, 0xac, 0xc2, 0x31, 0x3d, 0x36, 0x69, 0x7e, 0xd6, 0xcc, 0xf5, 0x3d, 0x6f, 0xb3, 0xeb, 0x1b, 0x76, 0xef, 0x3b, 0xa3, 0xfa, 0xc9, 0x2b, 0x5f, 0x66, 0x6f, 0xa9, 0x1e, 0x73, 0xf2, 0x49, 0x2e, 0x39, 0xf7, 0x4f, 0xb7, 0x8d, 0xff, 0xd5, 0xf3, 0x26, 0xfe, 0x0a, 0xc5, 0x1b, 0xa7, 0xcb, 0xb2, 0xcf, 0x49, 0x03, 0xb2, 0x46, 0xee, 0xd9, 0xd9, 0xb3, 0xf4, 0x9f, 0x25, 0x4a, 0xdf, 0x4b, 0x77, 0xe8, 0x27, 0xd4, 0xef, 0x1c, 0x2a, 0x29, 0x26, 0xc5, 0x7c, 0x9d, 0x6c, 0x7f, 0xb7, 0x6e, 0x1b, 0x26, 0x7f, 0x05, 0xa3, 0xfe, 0x53, 0x8d, 0x62, 0x57, 0x30, 0x92, 0x12, 0xfa, 0x2f, 0x86, 0xdf, 0xa4, 0xec, 0x67, 0xfe, 0xd0, 0xf4, 0xff, 0x00, 0x4d, 0xfc, 0xdf, 0x78, 0xe1, 0x68, 0x7d, 0x54, 0x99, 0xbf, 0x6f, 0xf3, 0xbe, 0xdf, 0x8e, 0xdd, 0x7f, 0xef, 0xeb, 0x97, 0x49, 0x3e, 0x3b, 0x7f, 0x06, 0x2c, 0x9f, 0x37, 0x5f, 0xf0, 0x9f, 0x4c, 0xeb, 0x7b, 0xbf, 0x67, 0x55, 0xe8, 0xff, 0x00, 0x31, 0xbc, 0x7a, 0x9e, 0x31, 0xdb, 0xfe, 0x92, 0xae, 0x37, 0x7a, 0x4d, 0xdb, 0xe2, 0x17, 0x9d, 0xa4, 0xa3, 0xc9, 0xba, 0xfc, 0x7b, 0x7d, 0x5f, 0x52, 0xa7, 0x7e, 0xd1, 0x28, 0xf8, 0xf3, 0xb0, 0xc7, 0x32, 0xbc, 0x99, 0x24, 0xc5, 0xe3, 0xab, 0xeb, 0x1f, 0xa4, 0xf5, 0xfc, 0xe1, 0x25, 0xe4, 0xe9, 0x24, 0x97, 0xff, 0xd9, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x00, 0x00, 0x13, 0x00, 0x41, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x20, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x38, 0x42, 0x49, 0x4d, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0xff, 0xe1, 0x15, 0x67, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x00, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x3d, 0x27, 0xef, 0xbb, 0xbf, 0x27, 0x20, 0x69, 0x64, 0x3d, 0x27, 0x57, 0x35, 0x4d, 0x30, 0x4d, 0x70, 0x43, 0x65, 0x68, 0x69, 0x48, 0x7a, 0x72, 0x65, 0x53, 0x7a, 0x4e, 0x54, 0x63, 0x7a, 0x6b, 0x63, 0x39, 0x64, 0x27, 0x3f, 0x3e, 0x0a, 0x3c, 0x3f, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2d, 0x78, 0x61, 0x70, 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x20, 0x65, 0x73, 0x63, 0x3d, 0x22, 0x43, 0x52, 0x22, 0x3f, 0x3e, 0x0a, 0x3c, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x27, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x27, 0x20, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x74, 0x6b, 0x3d, 0x27, 0x58, 0x4d, 0x50, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69, 0x74, 0x20, 0x32, 0x2e, 0x38, 0x2e, 0x32, 0x2d, 0x33, 0x33, 0x2c, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x31, 0x2e, 0x35, 0x27, 0x3e, 0x0a, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x27, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x69, 0x58, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x58, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x64, 0x66, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x64, 0x66, 0x2f, 0x31, 0x2e, 0x33, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x64, 0x66, 0x3a, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x43, 0x61, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x78, 0x61, 0x70, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x61, 0x70, 0x2f, 0x31, 0x2e, 0x30, 0x2f, 0x6d, 0x6d, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x64, 0x6f, 0x63, 0x69, 0x64, 0x3a, 0x70, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x36, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x3c, 0x2f, 0x78, 0x61, 0x70, 0x4d, 0x4d, 0x3a, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x27, 0x75, 0x75, 0x69, 0x64, 0x3a, 0x32, 0x32, 0x64, 0x30, 0x32, 0x62, 0x30, 0x61, 0x2d, 0x62, 0x32, 0x34, 0x39, 0x2d, 0x31, 0x31, 0x64, 0x62, 0x2d, 0x38, 0x61, 0x66, 0x38, 0x2d, 0x39, 0x31, 0x64, 0x35, 0x34, 0x30, 0x33, 0x66, 0x39, 0x32, 0x66, 0x39, 0x27, 0x0a, 0x20, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x64, 0x63, 0x3d, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x70, 0x75, 0x72, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x64, 0x63, 0x2f, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x31, 0x2e, 0x31, 0x2f, 0x27, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x20, 0x78, 0x6d, 0x6c, 0x3a, 0x6c, 0x61, 0x6e, 0x67, 0x3d, 0x27, 0x78, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x27, 0x3e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x6c, 0x69, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x41, 0x6c, 0x74, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x63, 0x3a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x0a, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x61, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x3d, 0x27, 0x77, 0x27, 0x3f, 0x3e, 0xff, 0xee, 0x00, 0x0e, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x00, 0x64, 0x40, 0x00, 0x00, 0x00, 0x01, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x06, 0x04, 0x03, 0x04, 0x06, 0x07, 0x05, 0x04, 0x04, 0x05, 0x07, 0x08, 0x06, 0x06, 0x07, 0x06, 0x06, 0x08, 0x0a, 0x08, 0x09, 0x09, 0x09, 0x09, 0x08, 0x0a, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0a, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x04, 0x05, 0x05, 0x08, 0x07, 0x08, 0x0f, 0x0a, 0x0a, 0x0f, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x0d, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x00, 0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x05, 0x03, 0x02, 0x06, 0x01, 0x00, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x02, 0x02, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x02, 0x06, 0x07, 0x03, 0x04, 0x02, 0x06, 0x02, 0x73, 0x01, 0x02, 0x03, 0x11, 0x04, 0x00, 0x05, 0x21, 0x12, 0x31, 0x41, 0x51, 0x06, 0x13, 0x61, 0x22, 0x71, 0x81, 0x14, 0x32, 0x91, 0xa1, 0x07, 0x15, 0xb1, 0x42, 0x23, 0xc1, 0x52, 0xd1, 0xe1, 0x33, 0x16, 0x62, 0xf0, 0x24, 0x72, 0x82, 0xf1, 0x25, 0x43, 0x34, 0x53, 0x92, 0xa2, 0xb2, 0x63, 0x73, 0xc2, 0x35, 0x44, 0x27, 0x93, 0xa3, 0xb3, 0x36, 0x17, 0x54, 0x64, 0x74, 0xc3, 0xd2, 0xe2, 0x08, 0x26, 0x83, 0x09, 0x0a, 0x18, 0x19, 0x84, 0x94, 0x45, 0x46, 0xa4, 0xb4, 0x56, 0xd3, 0x55, 0x28, 0x1a, 0xf2, 0xe3, 0xf3, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x37, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0x11, 0x00, 0x02, 0x02, 0x01, 0x02, 0x03, 0x05, 0x05, 0x04, 0x05, 0x06, 0x04, 0x08, 0x03, 0x03, 0x6d, 0x01, 0x00, 0x02, 0x11, 0x03, 0x04, 0x21, 0x12, 0x31, 0x41, 0x05, 0x51, 0x13, 0x61, 0x22, 0x06, 0x71, 0x81, 0x91, 0x32, 0xa1, 0xb1, 0xf0, 0x14, 0xc1, 0xd1, 0xe1, 0x23, 0x42, 0x15, 0x52, 0x62, 0x72, 0xf1, 0x33, 0x24, 0x34, 0x43, 0x82, 0x16, 0x92, 0x53, 0x25, 0xa2, 0x63, 0xb2, 0xc2, 0x07, 0x73, 0xd2, 0x35, 0xe2, 0x44, 0x83, 0x17, 0x54, 0x93, 0x08, 0x09, 0x0a, 0x18, 0x19, 0x26, 0x36, 0x45, 0x1a, 0x27, 0x64, 0x74, 0x55, 0x37, 0xf2, 0xa3, 0xb3, 0xc3, 0x28, 0x29, 0xd3, 0xe3, 0xf3, 0x84, 0x94, 0xa4, 0xb4, 0xc4, 0xd4, 0xe4, 0xf4, 0x65, 0x75, 0x85, 0x95, 0xa5, 0xb5, 0xc5, 0xd5, 0xe5, 0xf5, 0x46, 0x56, 0x66, 0x76, 0x86, 0x96, 0xa6, 0xb6, 0xc6, 0xd6, 0xe6, 0xf6, 0x47, 0x57, 0x67, 0x77, 0x87, 0x97, 0xa7, 0xb7, 0xc7, 0xd7, 0xe7, 0xf7, 0x38, 0x48, 0x58, 0x68, 0x78, 0x88, 0x98, 0xa8, 0xb8, 0xc8, 0xd8, 0xe8, 0xf8, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89, 0x99, 0xa9, 0xb9, 0xc9, 0xd9, 0xe9, 0xf9, 0x2a, 0x3a, 0x4a, 0x5a, 0x6a, 0x7a, 0x8a, 0x9a, 0xaa, 0xba, 0xca, 0xda, 0xea, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf0, 0x67, 0xa6, 0x5c, 0x0f, 0x01, 0xd4, 0x7e, 0x18, 0x12, 0x98, 0xe9, 0xd6, 0x2d, 0x34, 0x6d, 0x70, 0xdf, 0xdc, 0xa1, 0xe3, 0xec, 0x5b, 0xfb, 0x32, 0x24, 0xb2, 0x01, 0x1f, 0x15, 0xa4, 0x52, 0x4a, 0x82, 0x31, 0xf1, 0xfe, 0xd1, 0x3d, 0x14, 0x64, 0x49, 0x64, 0x22, 0x98, 0xcf, 0xa5, 0x46, 0x6c, 0x16, 0x55, 0x71, 0x56, 0x62, 0x28, 0x07, 0xc5, 0x45, 0x15, 0xa0, 0xc8, 0x89, 0x33, 0xe1, 0x63, 0xd2, 0xd8, 0x34, 0x44, 0x17, 0xa0, 0x2c, 0x4d, 0x16, 0xbb, 0xed, 0xdc, 0xf8, 0x64, 0xc1, 0x6b, 0x31, 0x42, 0x18, 0x8e, 0xc7, 0xb5, 0x2a, 0x7d, 0xb2, 0x56, 0xc5, 0x61, 0x8c, 0xf2, 0xa0, 0x1b, 0x1e, 0x83, 0x0d, 0xa1, 0x63, 0x50, 0x1f, 0x97, 0x7c, 0x2a, 0xa9, 0x1a, 0x9a, 0x86, 0x4f, 0xb4, 0xb4, 0x38, 0x0a, 0xa6, 0x0b, 0xb8, 0x0c, 0x05, 0x14, 0xf8, 0x76, 0x3e, 0x19, 0x14, 0xb6, 0x78, 0xf8, 0x8c, 0x2a, 0xd5, 0x01, 0xdc, 0x6f, 0x8a, 0x1a, 0xe3, 0x8d, 0xab, 0xff, 0xd0, 0xf0, 0xec, 0xe9, 0x15, 0xb5, 0xb9, 0x5a, 0x7c, 0x4c, 0xa2, 0x9e, 0x24, 0xf5, 0xca, 0xc6, 0xe5, 0x99, 0xd9, 0x34, 0x99, 0x04, 0x3a, 0x7d, 0xb5, 0xba, 0xd5, 0x51, 0x63, 0x0e, 0xc7, 0xc5, 0x9b, 0x73, 0xf8, 0xe4, 0x6f, 0x76, 0xca, 0xd9, 0xda, 0x54, 0x6d, 0x72, 0x2e, 0x1a, 0x57, 0x11, 0x44, 0x40, 0x0d, 0x27, 0x7a, 0x0f, 0xd9, 0x5f, 0x12, 0x69, 0x4c, 0x84, 0xcd, 0x36, 0xe3, 0x85, 0xb2, 0xcd, 0x2f, 0x4a, 0x8b, 0x58, 0x36, 0xf6, 0x76, 0xa8, 0x64, 0x64, 0x3c, 0xa4, 0x93, 0xaa, 0x25, 0x3c, 0x49, 0xda, 0xa4, 0xe5, 0x26, 0x54, 0xe4, 0x8c, 0x7c, 0x5c, 0x93, 0x4d, 0x67, 0xc9, 0x3a, 0x6e, 0x9f, 0x13, 0xb4, 0xce, 0xf7, 0x3a, 0x9b, 0xad, 0x52, 0xd6, 0x2a, 0xd1, 0x49, 0xee, 0xc7, 0xf8, 0x64, 0x46, 0x42, 0x4e, 0xcd, 0x92, 0xc2, 0x00, 0xdd, 0x8a, 0x47, 0xe5, 0x69, 0x6e, 0xd4, 0xa4, 0x08, 0x16, 0x83, 0x9c, 0x8c, 0xdd, 0x95, 0x6b, 0xb9, 0xf6, 0xef, 0x97, 0x78, 0x94, 0xe3, 0x78, 0x04, 0xa4, 0xf3, 0xe8, 0xee, 0x64, 0xe1, 0x12, 0x10, 0x05, 0x6a, 0xc7, 0xc0, 0x6f, 0x53, 0xf3, 0xc9, 0x89, 0xb4, 0x9c, 0x4e, 0xb4, 0xf2, 0xd3, 0xde, 0x7a, 0xd2, 0x19, 0x16, 0x38, 0x61, 0x5d, 0xd9, 0x88, 0x05, 0x9c, 0xf4, 0x0a, 0x0f, 0x5f, 0x73, 0x84, 0xe4, 0xa4, 0xc7, 0x0d, 0xa5, 0xf1, 0x59, 0xba, 0x5c, 0x08, 0x98, 0x6f, 0xc8, 0x20, 0xfa, 0x4e, 0x4e, 0xf6, 0x69, 0xe1, 0xa2, 0x89, 0xfd, 0x1f, 0x77, 0x2c, 0xe6, 0xce, 0xd6, 0x17, 0x9a, 0x69, 0xdb, 0xd3, 0x86, 0x18, 0xc1, 0x67, 0x77, 0x26, 0x80, 0x28, 0x1b, 0x93, 0x88, 0x41, 0x0f, 0x40, 0xb0, 0xfc, 0x87, 0xf3, 0x43, 0x98, 0xd7, 0x58, 0x96, 0xdb, 0x4d, 0x91, 0x88, 0xe5, 0x6c, 0x58, 0xdc, 0x5c, 0x2a, 0xf7, 0x2c, 0xb1, 0xfc, 0x20, 0x8f, 0x02, 0xd9, 0x65, 0x06, 0xbe, 0x26, 0x6f, 0xa2, 0x7f, 0xce, 0x3d, 0x69, 0x26, 0xdd, 0x13, 0x52, 0xbf, 0xbd, 0x92, 0x62, 0x59, 0x4c, 0x90, 0xac, 0x50, 0x45, 0x5e, 0xbb, 0x09, 0x03, 0x12, 0x29, 0x84, 0x00, 0xc4, 0xc9, 0x11, 0xff, 0x00, 0x42, 0xe7, 0xa7, 0x7a, 0xd4, 0xfd, 0x21, 0x79, 0xe9, 0x78, 0x71, 0x8b, 0x95, 0x39, 0x75, 0xaf, 0x4e, 0x98, 0x78, 0x42, 0x38, 0xdf, 0xff, 0xd1, 0xf0, 0xe6, 0xa0, 0x58, 0xc8, 0x84, 0x9a, 0xaa, 0x30, 0x55, 0xf9, 0x0a, 0x6f, 0x90, 0x0c, 0xca, 0x72, 0x48, 0xb8, 0x1e, 0x89, 0xa7, 0x23, 0x17, 0x24, 0xff, 0x00, 0x61, 0xb6, 0x54, 0x76, 0x6e, 0x1b, 0xa7, 0xbe, 0x50, 0xf2, 0xc1, 0xd7, 0x4c, 0x52, 0x5e, 0x33, 0x5b, 0xe9, 0x10, 0xf4, 0x54, 0x3c, 0x5e, 0x77, 0xee, 0x49, 0xec, 0x2b, 0xb6, 0x63, 0xe4, 0xc9, 0xc3, 0xef, 0x73, 0xf0, 0xe1, 0x32, 0x1b, 0xf2, 0x7a, 0x05, 0xce, 0xad, 0x65, 0xa1, 0x98, 0xb4, 0x0f, 0x2a, 0x5b, 0x23, 0xeb, 0x12, 0x00, 0x88, 0xb0, 0xa8, 0x66, 0x46, 0x3d, 0xea, 0x7b, 0xfb, 0x9e, 0x99, 0x89, 0xbc, 0x8d, 0x97, 0x3a, 0x34, 0x05, 0x32, 0x5d, 0x1f, 0xc9, 0x1a, 0x8c, 0x36, 0x8c, 0x6f, 0x66, 0xfa, 0xc6, 0xb7, 0x7d, 0xf0, 0x94, 0x04, 0xf0, 0x88, 0xc9, 0xd5, 0x9d, 0x8d, 0x4b, 0x11, 0xd4, 0x9f, 0xbb, 0x25, 0xc5, 0xdc, 0xa2, 0x03, 0x99, 0x4b, 0xbc, 0xf3, 0x0d, 0x97, 0x96, 0x74, 0xe5, 0xf2, 0xb6, 0x80, 0x95, 0xbd, 0x99, 0x15, 0xf5, 0x4b, 0xd2, 0x37, 0x58, 0x46, 0xd4, 0x27, 0xc5, 0xce, 0xc1, 0x7c, 0x30, 0x8e, 0x68, 0x94, 0x7b, 0x9e, 0x6d, 0xe6, 0x7b, 0x9b, 0x5d, 0x3a, 0xd8, 0xdb, 0x32, 0xfa, 0x77, 0x65, 0x15, 0xe4, 0x57, 0xa7, 0x21, 0x55, 0x04, 0x57, 0xef, 0xd8, 0x66, 0x56, 0x38, 0x19, 0x1b, 0xe8, 0xe0, 0x67, 0x98, 0xc7, 0x1a, 0x1c, 0xde, 0x71, 0x71, 0x79, 0x2c, 0xf2, 0xfa, 0x8c, 0x48, 0xec, 0xb5, 0x24, 0x9a, 0x0c, 0xce, 0x75, 0x29, 0xae, 0x8c, 0x67, 0xd4, 0xb5, 0x0b, 0x4b, 0x04, 0x05, 0xef, 0x2e, 0x66, 0x8e, 0x18, 0x08, 0x15, 0xdd, 0x8f, 0x11, 0xb0, 0xeb, 0x4c, 0x04, 0x5b, 0x21, 0x2a, 0x7d, 0x41, 0xe4, 0x4f, 0xcb, 0xcb, 0x5d, 0x12, 0x45, 0xb8, 0xb7, 0x53, 0x71, 0xaa, 0x9f, 0x86, 0x5b, 0xd6, 0x50, 0x4a, 0xed, 0xba, 0x46, 0x77, 0x00, 0x13, 0xd4, 0x8c, 0x85, 0xd3, 0x12, 0x6d, 0xeb, 0x1a, 0x67, 0x95, 0xd9, 0x39, 0x39, 0x50, 0xac, 0xff, 0x00, 0x6f, 0xc4, 0xff, 0x00, 0x1c, 0x81, 0x92, 0xb2, 0x6b, 0x6d, 0x02, 0xdd, 0xbd, 0x36, 0x92, 0x36, 0x2d, 0x1f, 0xc0, 0x2a, 0x0b, 0x28, 0x1b, 0x91, 0x41, 0xf4, 0x9c, 0xb6, 0x25, 0x81, 0x46, 0xfe, 0x81, 0xb5, 0xad, 0x3d, 0xba, 0x57, 0xb7, 0xf9, 0xf6, 0xc9, 0xb0, 0x7f, 0xff, 0xd2, 0xf0, 0xe2, 0x86, 0x95, 0xc4, 0x67, 0x7e, 0x3f, 0x11, 0xf7, 0xa8, 0x19, 0x06, 0x69, 0x8d, 0xca, 0xca, 0x24, 0x8f, 0xd3, 0x52, 0x24, 0x89, 0x47, 0x25, 0x1f, 0xcb, 0x20, 0xf8, 0xb2, 0xb2, 0x76, 0x6e, 0x88, 0x36, 0xf6, 0x6f, 0x2a, 0xc1, 0x6e, 0xfa, 0x45, 0xad, 0xbc, 0x3f, 0x0b, 0x46, 0x81, 0x4d, 0x46, 0xea, 0x7a, 0x9a, 0x83, 0x9a, 0xa9, 0xdd, 0xbb, 0xec, 0x7b, 0x06, 0x5b, 0xe5, 0xcf, 0x2e, 0x69, 0xfa, 0x5c, 0xcd, 0x7b, 0x14, 0x5e, 0xa5, 0xee, 0xf5, 0xb8, 0x7d, 0xdd, 0x99, 0xba, 0xef, 0x91, 0x16, 0x5b, 0x36, 0xb6, 0x65, 0x0d, 0xac, 0xb2, 0x5b, 0xed, 0x34, 0x81, 0x7a, 0xbb, 0x46, 0x40, 0x6a, 0x9e, 0xb4, 0x39, 0x31, 0x13, 0x49, 0xda, 0xd2, 0x9b, 0xed, 0x1e, 0xc4, 0x24, 0xb3, 0x35, 0xb2, 0x88, 0x60, 0x06, 0xe6, 0x56, 0x98, 0x96, 0x79, 0x1e, 0x31, 0x51, 0xc9, 0x8f, 0xcb, 0x00, 0xe6, 0xb3, 0xe4, 0xf9, 0x2b, 0xcc, 0x7a, 0x94, 0xda, 0x96, 0xa9, 0x71, 0x77, 0x70, 0x79, 0xcd, 0x33, 0x97, 0x76, 0x3f, 0xcc, 0xc6, 0xa6, 0x9f, 0x2e, 0x99, 0xb9, 0xc6, 0x2a, 0x21, 0xe6, 0x73, 0xca, 0xe6, 0x4a, 0x51, 0x1a, 0x99, 0x1c, 0x28, 0x04, 0x93, 0xd0, 0x0e, 0xa4, 0xe4, 0xda, 0x5f, 0x50, 0xfe, 0x4a, 0xfe, 0x48, 0xb5, 0xb2, 0xc1, 0xe6, 0x1f, 0x31, 0x7e, 0xef, 0x52, 0x91, 0x43, 0xc3, 0x6e, 0x77, 0xf4, 0x22, 0x6d, 0xbf, 0xe4, 0x63, 0x0e, 0xbf, 0xca, 0x36, 0xeb, 0x5c, 0x84, 0xa5, 0x48, 0x7d, 0x3b, 0x61, 0xa1, 0xdb, 0x5b, 0x2c, 0x71, 0xda, 0x45, 0xc4, 0x28, 0x00, 0x81, 0xdb, 0x31, 0xc9, 0xb4, 0xb2, 0x3b, 0x5d, 0x27, 0xa5, 0x05, 0x1b, 0xc7, 0xdb, 0x10, 0xa9, 0xbd, 0xa6, 0x93, 0x0c, 0x75, 0xe4, 0x39, 0x35, 0x41, 0x3d, 0xc5, 0x06, 0xdb, 0x8e, 0xfd, 0x46, 0x5b, 0x1d, 0x98, 0x95, 0x4f, 0x46, 0xdb, 0xd5, 0xfb, 0x29, 0x5e, 0x9d, 0x0d, 0x32, 0xeb, 0x61, 0x4f, 0xff, 0xd3, 0xf1, 0x46, 0x9a, 0x16, 0x1b, 0x91, 0x71, 0x28, 0xac, 0x4a, 0x14, 0x30, 0x3e, 0x19, 0x54, 0xb9, 0x36, 0xc7, 0x9b, 0x2d, 0xd1, 0x6c, 0x45, 0xe3, 0xdc, 0xde, 0xc8, 0x95, 0x5b, 0x87, 0xf8, 0x41, 0x1d, 0x10, 0x54, 0x01, 0x98, 0x79, 0x25, 0xd1, 0xda, 0xe9, 0xe1, 0xb5, 0x9e, 0xac, 0xeb, 0x42, 0xba, 0x8e, 0xdf, 0x8c, 0x31, 0x21, 0x70, 0xb4, 0x5d, 0xbe, 0xc5, 0x7c, 0x2b, 0xed, 0xe1, 0x94, 0x18, 0xb9, 0x51, 0x3d, 0x03, 0x2c, 0x13, 0x6b, 0xf1, 0x42, 0x6e, 0xe2, 0xb7, 0x12, 0xa0, 0xdd, 0x50, 0x9f, 0x4f, 0x6f, 0xa7, 0x6f, 0xc7, 0x03, 0x61, 0xa0, 0x83, 0xb5, 0xf3, 0x97, 0x98, 0x20, 0x9c, 0x44, 0xea, 0xd0, 0xad, 0x48, 0x64, 0x90, 0x21, 0xd8, 0x9f, 0xa7, 0xa6, 0x44, 0xca, 0x99, 0xc6, 0x36, 0xcb, 0x74, 0x5d, 0x7e, 0x5b, 0xfe, 0x31, 0x6a, 0x31, 0xf3, 0x8c, 0xd0, 0xad, 0x40, 0xa3, 0x1f, 0x7c, 0x44, 0xd6, 0x51, 0xd9, 0xe0, 0x5f, 0x9a, 0x7e, 0x41, 0x9f, 0x40, 0xf3, 0x14, 0xba, 0x85, 0xba, 0x34, 0xba, 0x2d, 0xfb, 0x34, 0xd0, 0xcf, 0x4f, 0xb0, 0xce, 0x6a, 0x51, 0xe9, 0xb0, 0x20, 0xf4, 0xf1, 0x19, 0xb2, 0xc3, 0x90, 0x11, 0x4e, 0x97, 0x55, 0x80, 0x83, 0xc4, 0x17, 0x7e, 0x4c, 0x79, 0x19, 0xfc, 0xd1, 0xe7, 0x78, 0x4b, 0x91, 0x1d, 0xae, 0x92, 0xa6, 0xf6, 0x46, 0x75, 0xe4, 0xad, 0x22, 0x1f, 0xdd, 0xa1, 0x07, 0xb3, 0x1e, 0xfe, 0xd9, 0x92, 0xeb, 0x4b, 0xed, 0xfd, 0x0a, 0xc2, 0x63, 0x27, 0xa4, 0x88, 0x17, 0x60, 0x49, 0x35, 0xdc, 0x8e, 0xa5, 0x7d, 0xab, 0xd3, 0x28, 0x90, 0x50, 0xcd, 0xed, 0x2d, 0xda, 0x15, 0x55, 0x51, 0xf1, 0x1a, 0x0a, 0xf7, 0x39, 0x5d, 0xaa, 0x77, 0x6f, 0x01, 0x8e, 0xa7, 0x7d, 0xfa, 0xff, 0x00, 0x66, 0x10, 0xa8, 0xb8, 0x63, 0x76, 0x90, 0xa8, 0x20, 0x06, 0x56, 0xdb, 0x61, 0xda, 0xbd, 0x4f, 0xcb, 0x24, 0x15, 0x0f, 0xf5, 0x66, 0xe5, 0x5f, 0x4c, 0x53, 0xc3, 0xb7, 0xce, 0x99, 0x6b, 0x17, 0xff, 0xd4, 0xf0, 0xec, 0x57, 0x6f, 0x32, 0xa5, 0xa4, 0x43, 0x76, 0x75, 0xa9, 0xf1, 0x03, 0xfa, 0x64, 0x08, 0x6c, 0x8e, 0xfb, 0x3d, 0x7f, 0xcb, 0x16, 0x2b, 0x3d, 0xbc, 0x16, 0xa3, 0x66, 0x6d, 0x98, 0xfb, 0x1e, 0xb9, 0xac, 0xc8, 0x77, 0xb7, 0x7d, 0x01, 0xb3, 0x37, 0xb8, 0xd3, 0x46, 0x95, 0x68, 0x86, 0xd2, 0x2e, 0x4e, 0xab, 0xf0, 0x23, 0x11, 0x4e, 0x5f, 0xcd, 0x98, 0xe7, 0x25, 0x96, 0x71, 0x83, 0x0f, 0xd6, 0x3c, 0xb9, 0xe7, 0x0d, 0x7c, 0x41, 0x22, 0x5e, 0xb3, 0x20, 0x0c, 0x65, 0x80, 0xc8, 0x63, 0x8e, 0xbb, 0x95, 0xa5, 0x07, 0xeb, 0xcc, 0xac, 0x73, 0x83, 0x4e, 0x5c, 0x59, 0x09, 0xd8, 0xec, 0xc8, 0x57, 0x41, 0xd3, 0x4e, 0x95, 0xa5, 0x5b, 0x4b, 0x6a, 0xcb, 0xab, 0x43, 0x10, 0x4b, 0xeb, 0x85, 0xa2, 0x2c, 0x8e, 0x3f, 0x68, 0x54, 0xf5, 0x00, 0xd3, 0x97, 0x7a, 0x65, 0x79, 0xa6, 0x24, 0x76, 0x6f, 0xd3, 0x62, 0x96, 0x30, 0x78, 0xcb, 0x21, 0xf2, 0xf4, 0x22, 0xce, 0x54, 0x8e, 0x46, 0x26, 0x10, 0x7e, 0x0a, 0xf5, 0xd8, 0xf5, 0x1f, 0x31, 0x98, 0x83, 0x73, 0xb3, 0x91, 0xcd, 0x67, 0xe6, 0x7d, 0xe8, 0x16, 0x69, 0x6f, 0x10, 0x1f, 0x54, 0x9a, 0x37, 0xf5, 0x41, 0x5e, 0x7f, 0x0a, 0x29, 0x62, 0x02, 0xf8, 0x9c, 0xc8, 0x8c, 0x77, 0x6a, 0x99, 0xa0, 0x89, 0xff, 0x00, 0x9c, 0x74, 0xd2, 0xed, 0xed, 0xfc, 0xbb, 0x7b, 0xaa, 0x9a, 0x7d, 0x62, 0xfe, 0x46, 0x2d, 0xfe, 0x4c, 0x51, 0x31, 0x11, 0xa9, 0xf6, 0xef, 0x9b, 0x30, 0x5e, 0x7b, 0x38, 0xdd, 0xf4, 0x7f, 0x95, 0x94, 0xbc, 0x12, 0x43, 0x30, 0x6a, 0xb2, 0xf3, 0x86, 0x40, 0x3e, 0xcb, 0xd7, 0x6a, 0xd7, 0xb1, 0xe9, 0x8f, 0x37, 0x19, 0x97, 0x41, 0x2c, 0x71, 0x20, 0xf5, 0x36, 0x9c, 0x55, 0x78, 0x1d, 0x8a, 0x91, 0xd7, 0x11, 0x14, 0x5a, 0x3e, 0x19, 0x03, 0x10, 0x6b, 0xca, 0xbd, 0x86, 0xf8, 0x9d, 0x95, 0x18, 0x36, 0x65, 0x2e, 0xbc, 0x54, 0x1f, 0xa2, 0x99, 0x00, 0x59, 0x2a, 0x6f, 0x5e, 0x55, 0x15, 0xe9, 0x5f, 0xc3, 0x2f, 0xb6, 0x14, 0xff, 0x00, 0xff, 0xd5, 0xf1, 0x95, 0xfe, 0x80, 0x74, 0x0d, 0x7c, 0xd9, 0x89, 0x3d, 0x78, 0x57, 0x8b, 0xc5, 0x28, 0xe8, 0x55, 0xf7, 0x1f, 0x48, 0xca, 0x38, 0xb8, 0x83, 0x9f, 0x93, 0x07, 0x85, 0x3a, 0x7a, 0x6f, 0x95, 0x66, 0x2b, 0x2c, 0x4c, 0x0d, 0x14, 0x00, 0x3e, 0x9c, 0xc3, 0x98, 0x76, 0xb8, 0x45, 0xbd, 0x02, 0xde, 0x48, 0xee, 0xdc, 0xa0, 0x15, 0xe2, 0x2b, 0xc8, 0x8a, 0x8a, 0xfd, 0x3b, 0x66, 0x3f, 0x00, 0x73, 0x84, 0x2d, 0x36, 0xb5, 0xb5, 0x9e, 0x35, 0x1c, 0x29, 0xc4, 0xfe, 0xc8, 0x04, 0x7f, 0xc4, 0x69, 0x91, 0xe1, 0x67, 0x2c, 0x4a, 0xd2, 0xe9, 0x4e, 0xe3, 0xd4, 0xf4, 0x81, 0x5a, 0x12, 0xc5, 0x41, 0x3f, 0x79, 0x38, 0x9b, 0x60, 0x20, 0x07, 0x34, 0xb0, 0xc9, 0x03, 0x5c, 0x23, 0x03, 0x53, 0x13, 0x56, 0x88, 0xdf, 0x09, 0xda, 0x9b, 0xd3, 0xb6, 0x52, 0x0e, 0xec, 0xe4, 0x29, 0x24, 0xfc, 0xd0, 0xe7, 0x75, 0xe5, 0x57, 0x6b, 0x61, 0xfb, 0xf0, 0xca, 0xaa, 0x57, 0xa8, 0xe6, 0x78, 0x1a, 0x7d, 0xf9, 0x95, 0x8a, 0x5e, 0xa0, 0xe3, 0x67, 0x8f, 0xa0, 0xbd, 0x5b, 0xf2, 0xdf, 0x4a, 0x82, 0xcb, 0x4a, 0xb3, 0xb0, 0xb4, 0x41, 0x0a, 0x70, 0x48, 0xd9, 0x57, 0x60, 0x51, 0x3a, 0x8f, 0xbc, 0xe6, 0x7b, 0xcb, 0xe4, 0x3b, 0xa7, 0x3f, 0x9b, 0x9f, 0x9a, 0xba, 0x77, 0xe5, 0x5f, 0x95, 0x9c, 0x59, 0x94, 0x9f, 0xcd, 0x37, 0x8c, 0xa9, 0xa6, 0xd9, 0x39, 0xaa, 0xd0, 0x7d, 0xa9, 0x1c, 0x03, 0x5e, 0x09, 0xff, 0x00, 0x0c, 0x76, 0xcb, 0x62, 0x2d, 0xa5, 0xf2, 0x85, 0xbf, 0xe7, 0x87, 0xe6, 0xa3, 0x5e, 0x4d, 0xa8, 0xc9, 0xe6, 0x8b, 0xd5, 0x69, 0x5c, 0xb0, 0x4a, 0xab, 0xc4, 0xb5, 0x35, 0x0a, 0xaa, 0xea, 0x40, 0x03, 0xa0, 0xf6, 0xcb, 0x40, 0x4d, 0x3e, 0xdb, 0xff, 0x00, 0x9c, 0x7f, 0xfc, 0xce, 0x4f, 0xcc, 0xbf, 0x26, 0x25, 0xe5, 0xd3, 0x2f, 0xe9, 0xdd, 0x3d, 0xfe, 0xab, 0xa9, 0xaa, 0xd2, 0xa6, 0x40, 0x2a, 0xb2, 0x71, 0x00, 0x01, 0xea, 0x0d, 0xe8, 0x3a, 0x64, 0x25, 0x16, 0x1c, 0x8b, 0xd9, 0x51, 0x39, 0x28, 0x12, 0x51, 0x41, 0xfd, 0xa3, 0xd2, 0xb9, 0x4f, 0x0d, 0x33, 0xb5, 0xf4, 0x87, 0x9d, 0x79, 0x0e, 0xb4, 0xaf, 0x6a, 0xf8, 0xf1, 0xf0, 0xc9, 0xda, 0xbf, 0xff, 0xd6, 0xf2, 0xc6, 0xb5, 0x68, 0x64, 0xd0, 0x6d, 0x35, 0x20, 0x39, 0xcd, 0x13, 0x0f, 0x5e, 0x61, 0xfc, 0x8f, 0x40, 0x8b, 0x5e, 0xe0, 0x66, 0x1c, 0x4f, 0xaa, 0x9d, 0xe6, 0xa6, 0x1e, 0x91, 0x2e, 0xa9, 0x87, 0x95, 0xee, 0x9c, 0xc5, 0x55, 0x34, 0x60, 0x40, 0xae, 0x57, 0x30, 0xd9, 0xa7, 0x95, 0xbd, 0x6f, 0xcb, 0x26, 0x39, 0x40, 0x0d, 0x4e, 0xc0, 0x9f, 0x9e, 0x50, 0x5d, 0xac, 0x79, 0x33, 0x8b, 0xbb, 0x9b, 0x3b, 0x6b, 0x35, 0x48, 0x54, 0x09, 0x29, 0x56, 0x7f, 0xe1, 0x86, 0x72, 0x00, 0x2c, 0x6e, 0xf7, 0x63, 0x3e, 0x63, 0xbd, 0xbd, 0x5d, 0x20, 0x2a, 0xb3, 0xa4, 0x33, 0x48, 0xab, 0x21, 0x43, 0xf1, 0x2c, 0x47, 0xed, 0x1d, 0xbc, 0x73, 0x18, 0x9b, 0x64, 0x28, 0x96, 0x3a, 0xc7, 0x49, 0xb0, 0xf4, 0xcc, 0xe9, 0x73, 0x6c, 0xb4, 0xf8, 0x67, 0x92, 0x32, 0x21, 0x70, 0x7b, 0x89, 0x05, 0x57, 0xef, 0x38, 0x28, 0x94, 0x4a, 0x7d, 0x13, 0x7d, 0x6a, 0xd3, 0x4c, 0xb8, 0xf2, 0xc3, 0xc8, 0x2e, 0x03, 0xf3, 0xe2, 0x7d, 0x33, 0xb7, 0xc5, 0xcc, 0x71, 0x03, 0xc6, 0xb9, 0x64, 0x06, 0xe2, 0x9a, 0xf2, 0x4f, 0xd2, 0x6d, 0xe9, 0xfe, 0x41, 0x45, 0x5b, 0x18, 0x66, 0xa5, 0x64, 0x09, 0xf4, 0xd5, 0xb7, 0xcd, 0x93, 0xc7, 0xcf, 0x9b, 0xe5, 0x6f, 0xf9, 0xc8, 0x0d, 0x56, 0xeb, 0x59, 0xfc, 0xce, 0xd5, 0x12, 0x61, 0xc4, 0x69, 0xe9, 0x0d, 0xa4, 0x4b, 0xfe, 0x48, 0x40, 0xd5, 0x3e, 0xe4, 0xb6, 0x64, 0x8e, 0x4c, 0x02, 0x61, 0x65, 0xa0, 0x14, 0xb4, 0xb6, 0xb0, 0xb1, 0xb6, 0xb2, 0x97, 0xcb, 0xf1, 0x5a, 0x2d, 0xc6, 0xa5, 0xac, 0xb4, 0x70, 0x5d, 0xc7, 0x3d, 0xc1, 0x51, 0x24, 0x91, 0xc9, 0x31, 0x75, 0x6b, 0x70, 0x9f, 0x14, 0x68, 0x01, 0x46, 0xe4, 0xb5, 0xa3, 0x17, 0xcb, 0x40, 0x61, 0x6f, 0x47, 0xff, 0x00, 0x9c, 0x3a, 0x8f, 0x5b, 0x4f, 0x3c, 0x6b, 0xb7, 0xfa, 0x30, 0x91, 0x3c, 0xa4, 0xb1, 0x95, 0xb9, 0x82, 0x42, 0x0a, 0xbc, 0x8e, 0xe4, 0xdb, 0xa9, 0xef, 0xc9, 0x17, 0x91, 0x24, 0x7c, 0xb2, 0x05, 0x64, 0xfb, 0x75, 0x64, 0x32, 0x39, 0x69, 0x5b, 0x9c, 0xad, 0xb9, 0xdb, 0xa7, 0xb5, 0x3b, 0x53, 0x2a, 0x21, 0x41, 0x44, 0xf3, 0x8b, 0x8f, 0x2e, 0x43, 0x9d, 0x2b, 0xd4, 0x57, 0x23, 0x41, 0x36, 0xff, 0x00, 0xff, 0xd7, 0xf0, 0xc0, 0xd5, 0xb5, 0x11, 0x64, 0xb6, 0x3f, 0x59, 0x90, 0xd9, 0xab, 0x06, 0xf4, 0x79, 0x7c, 0x3b, 0x74, 0xc8, 0x08, 0x8b, 0xb6, 0xe3, 0x96, 0x55, 0x57, 0xb3, 0x3e, 0xf2, 0x35, 0xc7, 0xd6, 0x0b, 0x45, 0x5d, 0xdc, 0x8a, 0x7d, 0xd9, 0x8d, 0x94, 0x3b, 0x3d, 0x1c, 0x9e, 0xc3, 0xe5, 0xc3, 0x2c, 0x7c, 0xc5, 0x0f, 0xee, 0xdb, 0x8b, 0x0c, 0xc4, 0x26, 0x9d, 0xa0, 0x9a, 0x7d, 0x2c, 0xe5, 0xe4, 0x55, 0x7f, 0xee, 0xc1, 0x15, 0x04, 0xd0, 0x12, 0x3c, 0x72, 0x89, 0x1b, 0x2c, 0xcc, 0xa8, 0x2a, 0x8b, 0x87, 0xbb, 0x63, 0x1a, 0x28, 0x65, 0xf0, 0xed, 0xf2, 0xc3, 0xc2, 0x0a, 0x06, 0x4a, 0x46, 0xc7, 0xa5, 0xa3, 0x59, 0xc8, 0xb2, 0xc7, 0x45, 0x22, 0x9c, 0x14, 0x54, 0x10, 0x46, 0xf5, 0x1d, 0x32, 0x5c, 0x14, 0x14, 0xe4, 0x32, 0x2f, 0x3a, 0xf3, 0xb6, 0x90, 0x9a, 0x6d, 0xae, 0x9f, 0x3d, 0xab, 0xb8, 0x8a, 0x3b, 0xf8, 0x39, 0x44, 0x58, 0xf0, 0x08, 0xd5, 0x14, 0xa5, 0x7b, 0x65, 0x98, 0x8e, 0xfb, 0xb5, 0x67, 0x87, 0xa5, 0xef, 0x5e, 0x44, 0x96, 0x35, 0xb5, 0xb6, 0x59, 0x36, 0xfd, 0xd8, 0xa0, 0xf1, 0x20, 0x53, 0x33, 0xc0, 0x79, 0x59, 0x73, 0x7c, 0xd7, 0xf9, 0xfb, 0xa2, 0xcd, 0x67, 0xf9, 0xa7, 0x7b, 0x72, 0xf1, 0x71, 0x83, 0x53, 0x86, 0x0b, 0x98, 0x24, 0x22, 0x8a, 0xcc, 0x88, 0x23, 0x7f, 0xb8, 0xae, 0xf9, 0x7c, 0x50, 0x1e, 0x5f, 0x7c, 0x48, 0x21, 0x44, 0x6b, 0xce, 0x9b, 0xb0, 0x1b, 0x9e, 0xf5, 0xaf, 0x8e, 0x4d, 0x5f, 0x7a, 0x7f, 0xce, 0x34, 0xf9, 0x5d, 0x3c, 0xa3, 0xf9, 0x69, 0x63, 0xa9, 0x3c, 0x27, 0xeb, 0xda, 0xe1, 0x37, 0xd7, 0x2e, 0xaa, 0xdb, 0x06, 0xda, 0x30, 0x49, 0xfe, 0x54, 0x03, 0x03, 0x49, 0xdc, 0xb3, 0xaf, 0x38, 0xfe, 0x6a, 0xf9, 0x47, 0xc9, 0x3a, 0x74, 0x97, 0xfa, 0xf6, 0xaf, 0x15, 0x85, 0xb8, 0x75, 0x89, 0xb8, 0x87, 0x9a, 0x72, 0xee, 0x2a, 0x14, 0x24, 0x60, 0xb1, 0xa8, 0xdf, 0x07, 0x0b, 0x2d, 0xcb, 0xcf, 0x7f, 0xe8, 0x6a, 0xff, 0x00, 0x26, 0xbd, 0x6a, 0x7f, 0x89, 0x2f, 0xf8, 0x52, 0x9e, 0xb7, 0xe8, 0xb9, 0xb8, 0x57, 0xc2, 0x95, 0xe9, 0x8f, 0x08, 0x5a, 0x2f, 0xff, 0xd0, 0xf0, 0x4d, 0x40, 0xaa, 0xd7, 0x00, 0x64, 0xcb, 0x3c, 0x97, 0xa8, 0xb5, 0x9e, 0xa3, 0x1a, 0xd6, 0x84, 0x95, 0x3f, 0x45, 0x72, 0x9c, 0xa2, 0xc3, 0x99, 0xa5, 0x9d, 0x49, 0xf4, 0x17, 0x97, 0xaf, 0x63, 0x17, 0x52, 0x6f, 0xf0, 0xc8, 0x43, 0x6f, 0x9a, 0xe9, 0x07, 0x70, 0x0e, 0xec, 0x83, 0x51, 0x44, 0xb8, 0x61, 0x1a, 0x9e, 0x11, 0xd3, 0x91, 0x60, 0x68, 0x6b, 0xd3, 0x31, 0x4f, 0x36, 0xd3, 0x4c, 0x52, 0xef, 0x4c, 0xd5, 0x0c, 0xc4, 0x69, 0xda, 0x94, 0xc8, 0x3a, 0xf0, 0x66, 0x07, 0x73, 0xe0, 0x40, 0xfd, 0x79, 0x93, 0x12, 0x1c, 0x9c, 0x32, 0xc7, 0xfc, 0x41, 0x33, 0xd2, 0xb4, 0x6f, 0x38, 0x98, 0x65, 0x76, 0xbf, 0x69, 0x42, 0xd0, 0xaa, 0xc9, 0xde, 0x95, 0xad, 0x28, 0x46, 0x4e, 0xac, 0x39, 0x77, 0x80, 0x11, 0xbf, 0xd8, 0xc7, 0x7c, 0xe1, 0xa5, 0xf9, 0x92, 0x4d, 0x32, 0x5b, 0x8b, 0x93, 0x27, 0xa7, 0x68, 0x56, 0xe2, 0x45, 0xda, 0x85, 0x61, 0x6e, 0x67, 0xad, 0x6b, 0xb0, 0x38, 0xc2, 0x81, 0xe4, 0xc7, 0x52, 0x31, 0x1c, 0x67, 0x86, 0x5b, 0xbd, 0x37, 0xca, 0x7a, 0x94, 0xb1, 0x69, 0xb6, 0x2e, 0xb7, 0x15, 0x48, 0xc2, 0xb4, 0x52, 0x53, 0xac, 0x32, 0xaf, 0xb1, 0xed, 0x9b, 0x10, 0x36, 0x78, 0x5c, 0x9f, 0x51, 0x64, 0x1f, 0x98, 0x3e, 0x58, 0xb6, 0xfc, 0xc8, 0xf2, 0xe5, 0xbc, 0x68, 0x52, 0x2d, 0x5a, 0xd1, 0x84, 0xb6, 0xf3, 0x95, 0x0e, 0xc0, 0x85, 0xe2, 0xcb, 0xd8, 0xd1, 0xbb, 0xe4, 0xc1, 0xa6, 0x97, 0xce, 0x17, 0x5f, 0x95, 0xde, 0x6d, 0xb6, 0xbe, 0xb7, 0x69, 0x34, 0xf3, 0x3c, 0x72, 0xcf, 0xe8, 0xa3, 0x45, 0x49, 0x95, 0x4a, 0x90, 0x3e, 0x35, 0x5a, 0x95, 0x1d, 0xfe, 0x21, 0x93, 0x4d, 0xbe, 0xd2, 0xd2, 0xf5, 0x8b, 0xbd, 0x32, 0x2d, 0x3f, 0x4c, 0x9a, 0xe4, 0xca, 0x9e, 0x90, 0x85, 0x65, 0x55, 0x08, 0x85, 0x91, 0x01, 0x3b, 0x0a, 0x05, 0xe9, 0xb0, 0xc0, 0x5a, 0xc3, 0xcd, 0x3f, 0x3b, 0x7f, 0x26, 0xec, 0xff, 0x00, 0x35, 0x6d, 0x6d, 0xb5, 0x3d, 0x16, 0xfe, 0x0d, 0x3b, 0xcd, 0x96, 0x01, 0x92, 0x46, 0x9e, 0xa2, 0x0b, 0xc8, 0xb7, 0x28, 0x92, 0x71, 0xfb, 0x2e, 0xa7, 0xec, 0x3d, 0x0f, 0xc2, 0x68, 0x71, 0x05, 0x95, 0xd3, 0xe7, 0x9f, 0xfa, 0x16, 0x2f, 0xcd, 0x7f, 0x43, 0xd6, 0xfa, 0xa5, 0x97, 0xab, 0xeb, 0x7a, 0x5f, 0x55, 0xfa, 0xec, 0x5e, 0xaf, 0x0f, 0xf7, 0xed, 0x2b, 0x4e, 0x15, 0xff, 0x00, 0x65, 0xdf, 0x8e, 0x14, 0xf1, 0xbf, 0xff, 0xd1, 0xf0, 0x5a, 0xa7, 0x18, 0x5e, 0x56, 0x1f, 0x68, 0x71, 0x5f, 0xa7, 0xbe, 0x2a, 0x98, 0xdb, 0xfa, 0x90, 0x24, 0x37, 0xb0, 0xfd, 0xb8, 0xa8, 0x58, 0x78, 0xae, 0x43, 0xc9, 0xb4, 0x6d, 0xbb, 0xda, 0x3c, 0xa1, 0xad, 0x43, 0xa8, 0xda, 0xc5, 0x2a, 0x3d, 0x26, 0x5a, 0x02, 0x2b, 0xbe, 0x60, 0x64, 0x8d, 0x17, 0x6f, 0x8b, 0x20, 0x90, 0x7a, 0x3c, 0x32, 0x8b, 0xa8, 0x02, 0xf3, 0xfd, 0xe0, 0x1b, 0x11, 0x98, 0x66, 0x3b, 0xb9, 0x62, 0x54, 0x83, 0x36, 0xf2, 0xa4, 0xe4, 0x29, 0x34, 0xeb, 0xc8, 0x74, 0xae, 0x0d, 0xc3, 0x65, 0x82, 0x13, 0x6b, 0x57, 0xba, 0x54, 0xe4, 0x8c, 0x41, 0x1b, 0x75, 0xa7, 0xe0, 0x72, 0x5c, 0x4c, 0x84, 0x50, 0x5a, 0xb3, 0xdd, 0xdd, 0xc3, 0x24, 0x33, 0xb1, 0x60, 0xe0, 0x86, 0x52, 0x45, 0x38, 0xd2, 0x87, 0x24, 0x26, 0x6d, 0x8c, 0xe1, 0x41, 0x25, 0xfc, 0xa3, 0xd7, 0x2f, 0x6f, 0x3c, 0xbf, 0x73, 0xa5, 0xb2, 0x2c, 0xd1, 0x69, 0x17, 0x2f, 0x6b, 0x14, 0x8c, 0x0f, 0x21, 0x0d, 0x79, 0x46, 0x09, 0x15, 0xed, 0xb7, 0x4e, 0xd9, 0xb9, 0x8b, 0xcb, 0xe4, 0xa2, 0x5e, 0xa3, 0xa6, 0xdf, 0x6a, 0x36, 0xe4, 0xcd, 0x69, 0x1c, 0x4e, 0x84, 0x7c, 0x76, 0xab, 0x21, 0x67, 0xa8, 0xa7, 0xd9, 0xf8, 0x4d, 0x2b, 0xf3, 0xc3, 0x4d, 0x49, 0x57, 0x98, 0x75, 0x6f, 0x31, 0xda, 0xf9, 0xa3, 0x4b, 0xfd, 0x1f, 0x69, 0x1d, 0xae, 0xa1, 0xa9, 0x7e, 0xee, 0xe6, 0xd2, 0x79, 0x18, 0xf3, 0xb5, 0x1f, 0xee, 0xd9, 0x0a, 0x01, 0x4e, 0x3f, 0xb3, 0x4d, 0xf2, 0x9c, 0xb9, 0x04, 0x05, 0xb7, 0xe2, 0x87, 0x1e, 0xdd, 0x19, 0x3e, 0xaf, 0x6b, 0xae, 0xcb, 0x6d, 0x13, 0x0d, 0x45, 0xa2, 0x8e, 0x06, 0xe5, 0x13, 0x2a, 0x02, 0x01, 0x5e, 0x82, 0xb5, 0x04, 0xe6, 0x11, 0xd4, 0xcd, 0xda, 0x43, 0x49, 0x8e, 0xb7, 0xdc, 0xb1, 0x51, 0xe6, 0x4d, 0x76, 0xd2, 0x61, 0x15, 0xaa, 0x4b, 0xa8, 0xc9, 0x6e, 0x49, 0x79, 0x20, 0xe6, 0x8c, 0x49, 0xad, 0x43, 0x16, 0xe4, 0xa7, 0xaf, 0x43, 0xd3, 0x26, 0x35, 0x75, 0xcd, 0xa8, 0xe8, 0x87, 0x46, 0xbf, 0xc7, 0x9a, 0xff, 0x00, 0xd6, 0xbf, 0x48, 0xfe, 0x88, 0xfd, 0xe7, 0x0f, 0xab, 0xfa, 0x3f, 0x58, 0x7f, 0x5f, 0x8d, 0x3f, 0x9f, 0xa7, 0x5e, 0xd4, 0xc3, 0xf9, 0xd1, 0x7c, 0xb6, 0x47, 0xe4, 0x3a, 0x5b, 0xff, 0xd2, 0xf0, 0xb7, 0xa6, 0x1e, 0xdf, 0xd3, 0xf6, 0xa5, 0x71, 0x54, 0xdb, 0x4b, 0x80, 0x3c, 0x42, 0x26, 0xee, 0x29, 0xbe, 0x51, 0x23, 0x4e, 0x44, 0x05, 0x84, 0x45, 0xa5, 0xd5, 0xf7, 0x97, 0x2e, 0xfd, 0x6b, 0x6a, 0x98, 0x09, 0xab, 0xc7, 0xfc, 0x46, 0x3b, 0x4c, 0x26, 0x32, 0x30, 0x3e, 0x4f, 0x49, 0xd0, 0xfc, 0xfb, 0x05, 0xd4, 0x4a, 0x7d, 0x40, 0xac, 0x3a, 0x8e, 0x84, 0x1c, 0xc5, 0x96, 0x2a, 0x73, 0xe1, 0x9c, 0x16, 0x6d, 0xa5, 0x79, 0x86, 0xd6, 0xec, 0x80, 0x5a, 0xa0, 0xf5, 0xca, 0xcc, 0x5c, 0xa1, 0x2b, 0x1b, 0x26, 0x30, 0x6a, 0x31, 0x46, 0xcf, 0x1c, 0x87, 0x94, 0x64, 0x9e, 0x3d, 0xb6, 0xf0, 0xca, 0xa8, 0x39, 0x51, 0x99, 0x42, 0x6b, 0x1a, 0xc5, 0xa5, 0xa5, 0x94, 0xf7, 0x92, 0xc8, 0xaa, 0xb1, 0x23, 0x30, 0x04, 0xf8, 0x0e, 0x9f, 0x4e, 0x4a, 0x11, 0xb2, 0xd5, 0x9b, 0x25, 0x06, 0x1b, 0xff, 0x00, 0x38, 0xfd, 0xad, 0xdf, 0xda, 0xf9, 0xa2, 0xfe, 0xc5, 0x42, 0xbe, 0x9b, 0x7f, 0x0b, 0xdd, 0xdd, 0x07, 0xaf, 0x14, 0x68, 0xd8, 0x71, 0x6d, 0xbb, 0x90, 0xfc, 0x73, 0x6e, 0xf2, 0xf2, 0xdd, 0xf4, 0xad, 0xa6, 0xab, 0x6d, 0x69, 0x14, 0xfa, 0xee, 0xa0, 0xe2, 0x0b, 0x0d, 0x39, 0x19, 0xfe, 0x11, 0xc5, 0x1a, 0x4a, 0x1d, 0x8f, 0x73, 0x4f, 0xf8, 0x96, 0x0b, 0x40, 0x8d, 0xec, 0xf3, 0x6d, 0x3f, 0x52, 0xba, 0xd6, 0x35, 0x8b, 0xbf, 0x36, 0x6a, 0x5f, 0x0d, 0xc5, 0xdc, 0xa8, 0xb6, 0xa8, 0x7a, 0xc5, 0x6c, 0x9b, 0x22, 0x0f, 0xa3, 0x73, 0x9a, 0xbc, 0xb3, 0xe2, 0x36, 0xed, 0xb1, 0x43, 0x80, 0x53, 0xd0, 0xa7, 0xd4, 0x44, 0xfa, 0x7a, 0xda, 0x83, 0xbd, 0x3e, 0x2f, 0xa7, 0x2b, 0xad, 0x9b, 0xb8, 0x8d, 0xa8, 0xe8, 0x91, 0xdb, 0xfa, 0x2d, 0x6f, 0xc3, 0x8a, 0x2d, 0x56, 0xa3, 0xad, 0x4f, 0x5c, 0xa4, 0x0d, 0xdc, 0xa3, 0xca, 0xd0, 0xbf, 0xa1, 0xe3, 0xfa, 0xe7, 0x0f, 0xf2, 0xb9, 0x57, 0xbf, 0x1a, 0xe4, 0xb8, 0x57, 0xc5, 0xdd, 0xff, 0xd3, 0xf0, 0xcc, 0x5d, 0x7b, 0x70, 0xc5, 0x53, 0x6d, 0x2f, 0xd5, 0xe4, 0x69, 0xfd, 0xdf, 0xec, 0xd7, 0xad, 0x7d, 0xb2, 0x8c, 0x8d, 0xd8, 0xed, 0x91, 0x9f, 0x43, 0xea, 0xe7, 0xeb, 0x94, 0xad, 0x3e, 0x1e, 0x95, 0xfc, 0x72, 0x81, 0x7d, 0x1c, 0x9d, 0xba, 0xb1, 0x7b, 0xdf, 0xa9, 0x7a, 0xdf, 0xee, 0x2f, 0xd4, 0xfa, 0xe7, 0xed, 0x7a, 0x7f, 0xdd, 0xff, 0x00, 0xb2, 0xae, 0x64, 0x0b, 0xea, 0xe3, 0x9a, 0xbf, 0x4a, 0x6f, 0xa4, 0xff, 0x00, 0x89, 0xbd, 0x45, 0xfa, 0xb5, 0x79, 0xf7, 0xeb, 0xc7, 0xe9, 0xae, 0x57, 0x2e, 0x17, 0x23, 0x1f, 0x89, 0xd1, 0x99, 0x8f, 0xf1, 0xa7, 0x11, 0xcf, 0xd3, 0xf5, 0x29, 0xb5, 0x6b, 0xd3, 0xe8, 0xcc, 0x7f, 0x45, 0xb9, 0xa3, 0xc5, 0x62, 0xbe, 0x68, 0xff, 0x00, 0x15, 0xfd, 0x4c, 0xfe, 0x90, 0xaf, 0xd4, 0xab, 0xf1, 0x7a, 0x7f, 0x62, 0x9d, 0xab, 0xdf, 0x32, 0xb1, 0x70, 0x5e, 0xdc, 0xdc, 0x2d, 0x47, 0x8b, 0x5e, 0xae, 0x4c, 0xbf, 0xf2, 0x37, 0x9f, 0x3d, 0x5b, 0xd2, 0xff, 0x00, 0x8e, 0x87, 0xee, 0x29, 0x5a, 0xf2, 0xf4, 0xaa, 0xd4, 0xa5, 0x36, 0xa7, 0x3a, 0x57, 0xfd, 0x8e, 0x64, 0x3a, 0xf2, 0xf6, 0xbf, 0xcc, 0x7f, 0x5b, 0xfc, 0x23, 0xa7, 0xfe, 0x8e, 0xff, 0x00, 0x8e, 0x37, 0xd6, 0x63, 0xfa, 0xe5, 0x2b, 0xcb, 0x87, 0xec, 0xd6, 0xbd, 0xb9, 0x7d, 0xac, 0xc7, 0xcd, 0x7c, 0x2d, 0xf8, 0x2b, 0x89, 0x26, 0x8f, 0xd4, 0xfa, 0x94, 0x3e, 0x85, 0x29, 0xc9, 0x69, 0xfc, 0x33, 0x58, 0x5d, 0x9c, 0x79, 0xb2, 0xbb, 0x0f, 0xac, 0x7a, 0x2b, 0xea, 0x75, 0xef, 0x92, 0x0c, 0x53, 0x3d, 0x2f, 0xd4, 0xfa, 0xbb, 0xfa, 0x74, 0xf5, 0x39, 0x9a, 0xd7, 0xe7, 0x80, 0x53, 0x79, 0xba, 0x5b, 0xfe, 0x97, 0xfa, 0x4b, 0xfc, 0xba, 0x7f, 0xb1, 0xc7, 0xab, 0x1e, 0x8f, 0xff, 0xd9
+};
diff --git a/talk/base/testclient.cc b/talk/base/testclient.cc
new file mode 100644
index 0000000..8a605ce
--- /dev/null
+++ b/talk/base/testclient.cc
@@ -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.
+ */
+
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+// DESIGN: Each packet received is put it into a list of packets.
+// Callers can retrieve received packets from any thread by calling
+// NextPacket.
+
+TestClient::TestClient(AsyncPacketSocket* socket) : socket_(socket) {
+ 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];
+ delete packets_;
+}
+
+bool TestClient::CheckConnState(AsyncPacketSocket::State state) {
+ // Wait for our timeout value until the socket reaches the desired state.
+ uint32 end = TimeAfter(kTimeout);
+ while (socket_->GetState() != state && TimeUntil(end) > 0)
+ Thread::Current()->ProcessMessages(1);
+ return (socket_->GetState() == state);
+}
+
+int TestClient::Send(const char* buf, size_t size) {
+ return socket_->Send(buf, size);
+}
+
+int TestClient::SendTo(const char* buf, size_t size,
+ const SocketAddress& dest) {
+ return socket_->SendTo(buf, size, dest);
+}
+
+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.
+
+ // Note also that we only try to pump our current thread's message queue.
+ // Pumping another thread's queue could lead to messages being dispatched from
+ // the wrong thread to non-thread-safe objects.
+
+ uint32 end = TimeAfter(kTimeout);
+ while (packets_->size() == 0 && TimeUntil(end) > 0)
+ Thread::Current()->ProcessMessages(1);
+
+ // Return the first packet placed in the queue.
+ Packet* packet = NULL;
+ if (packets_->size() > 0) {
+ CritScope cs(&crit_);
+ packet = packets_->front();
+ packets_->erase(packets_->begin());
+ }
+
+ return packet;
+}
+
+bool TestClient::CheckNextPacket(const char* buf, size_t size,
+ SocketAddress* addr) {
+ bool res = false;
+ Packet* packet = NextPacket();
+ if (packet) {
+ res = (packet->size == size && std::memcmp(packet->buf, buf, size) == 0);
+ if (addr)
+ *addr = packet->addr;
+ delete packet;
+ }
+ return res;
+}
+
+bool TestClient::CheckNoPacket() {
+ bool res;
+ Packet* packet = NextPacket();
+ res = (packet == NULL);
+ delete packet;
+ return res;
+}
+
+void TestClient::OnPacket(AsyncPacketSocket* socket, const char* buf,
+ size_t size, const SocketAddress& remote_addr) {
+ CritScope cs(&crit_);
+ packets_->push_back(new Packet(remote_addr, buf, size));
+}
+
+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/talk/base/testclient.h b/talk/base/testclient.h
new file mode 100644
index 0000000..400e58f
--- /dev/null
+++ b/talk/base/testclient.h
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TESTCLIENT_H_
+#define TALK_BASE_TESTCLIENT_H_
+
+#include <vector>
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+// A simple client that can send TCP or UDP data and check that it receives
+// what it expects to receive. Useful for testing server functionality.
+class TestClient : public sigslot::has_slots<> {
+ public:
+ // Records the contents of a packet that was received.
+ struct Packet {
+ Packet(const SocketAddress& a, const char* b, size_t s);
+ Packet(const Packet& p);
+ virtual ~Packet();
+
+ SocketAddress addr;
+ char* buf;
+ size_t size;
+ };
+
+ // Creates a client that will send and receive with the given socket and
+ // will post itself messages with the given thread.
+ explicit TestClient(AsyncPacketSocket* socket);
+ ~TestClient();
+
+ SocketAddress address() const { return socket_->GetLocalAddress(); }
+ SocketAddress remote_address() const { return socket_->GetRemoteAddress(); }
+
+ // Checks that the socket moves to the specified connect state.
+ bool CheckConnState(AsyncPacketSocket::State state);
+
+ // Checks that the socket is connected to the remote side.
+ bool CheckConnected() {
+ return CheckConnState(AsyncPacketSocket::STATE_CONNECTED);
+ }
+
+ // Sends using the clients socket.
+ int Send(const char* buf, size_t size);
+
+ // Sends using the clients socket to the given destination.
+ int SendTo(const char* buf, size_t size, const SocketAddress& dest);
+
+ // 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.
+ bool CheckNextPacket(const char* buf, size_t len, SocketAddress* addr);
+
+ // Checks that no packets have arrived or will arrive in the next second.
+ bool CheckNoPacket();
+
+ private:
+ static const int kTimeout = 1000;
+ // Workaround for the fact that AsyncPacketSocket::GetConnState doesn't exist.
+ Socket::ConnState GetState();
+ // Slot for packets read on the socket.
+ void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t len,
+ const SocketAddress& remote_addr);
+
+ CriticalSection crit_;
+ AsyncPacketSocket* socket_;
+ std::vector<Packet*>* packets_;
+ DISALLOW_EVIL_CONSTRUCTORS(TestClient);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TESTCLIENT_H_
diff --git a/talk/base/testclient_unittest.cc b/talk/base/testclient_unittest.cc
new file mode 100644
index 0000000..ee91067
--- /dev/null
+++ b/talk/base/testclient_unittest.cc
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testechoserver.h"
+#include "talk/base/thread.h"
+
+using namespace talk_base;
+
+static const SocketAddress kLocalHostAnyAddr("127.0.0.1", 0);
+
+// Tests whether the TestClient can send UDP to itself.
+TEST(TestClientTest, TestUdp) {
+ Thread *main = Thread::Current();
+ AsyncSocket* socket = main->socketserver()->CreateAsyncSocket(SOCK_DGRAM);
+ socket->Bind(kLocalHostAnyAddr);
+
+ TestClient client(new AsyncUDPSocket(socket));
+ SocketAddress addr = client.address(), from;
+ EXPECT_EQ(3, client.SendTo("foo", 3, addr));
+ EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from));
+ EXPECT_EQ(from, addr);
+ EXPECT_TRUE(client.CheckNoPacket());
+}
+
+// Tests whether the TestClient can connect to a server and exchange data.
+TEST(TestClientTest, TestTcp) {
+ Thread *main = Thread::Current();
+ TestEchoServer server(main, kLocalHostAnyAddr);
+
+ AsyncSocket* socket = main->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+ AsyncTCPSocket* tcp_socket = AsyncTCPSocket::Create(
+ socket, kLocalHostAnyAddr, server.address());
+ ASSERT_TRUE(tcp_socket != NULL);
+
+ TestClient client(tcp_socket);
+ SocketAddress addr = client.address(), from;
+ EXPECT_TRUE(client.CheckConnected());
+ EXPECT_EQ(3, client.Send("foo", 3));
+ EXPECT_TRUE(client.CheckNextPacket("foo", 3, &from));
+ EXPECT_EQ(from, server.address());
+ EXPECT_TRUE(client.CheckNoPacket());
+}
diff --git a/talk/base/thread.cc b/talk/base/thread.cc
index 5e0eec4..8b11485 100644
--- a/talk/base/thread.cc
+++ b/talk/base/thread.cc
@@ -36,7 +36,7 @@
#include "talk/base/common.h"
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#ifdef USE_COCOA_THREADING
#if !defined(OSX) && !defined(IOS)
@@ -48,14 +48,15 @@
namespace talk_base {
-ThreadManager g_thmgr;
+ThreadManager* ThreadManager::Instance() {
+ LIBJINGLE_DEFINE_STATIC_LOCAL(ThreadManager, thread_manager, ());
+ return &thread_manager;
+}
#ifdef POSIX
-pthread_key_t ThreadManager::key_;
-
ThreadManager::ThreadManager() {
pthread_key_create(&key_, NULL);
- main_thread_ = WrapCurrentThread();
+ WrapCurrentThread();
#ifdef USE_COCOA_THREADING
InitCocoaMultiThreading();
#endif
@@ -69,7 +70,6 @@
ScopedAutoreleasePool pool;
#endif
UnwrapCurrentThread();
- // Unwrap deletes main_thread_ automatically.
pthread_key_delete(key_);
}
@@ -77,17 +77,15 @@
return static_cast<Thread *>(pthread_getspecific(key_));
}
-void ThreadManager::SetCurrent(Thread *thread) {
+void ThreadManager::SetCurrentThread(Thread *thread) {
pthread_setspecific(key_, thread);
}
#endif
#ifdef WIN32
-DWORD ThreadManager::key_;
-
ThreadManager::ThreadManager() {
key_ = TlsAlloc();
- main_thread_ = WrapCurrentThread();
+ WrapCurrentThread();
}
ThreadManager::~ThreadManager() {
@@ -99,7 +97,7 @@
return static_cast<Thread *>(TlsGetValue(key_));
}
-void ThreadManager::SetCurrent(Thread *thread) {
+void ThreadManager::SetCurrentThread(Thread *thread) {
TlsSetValue(key_, thread);
}
#endif
@@ -109,7 +107,7 @@
Thread* result = CurrentThread();
if (NULL == result) {
result = new Thread();
- result->WrapCurrent();
+ result->WrapCurrentWithThreadManager(this);
}
return result;
}
@@ -123,25 +121,6 @@
}
}
-void ThreadManager::Add(Thread *thread) {
- CritScope cs(&crit_);
- threads_.push_back(thread);
-}
-
-void ThreadManager::Remove(Thread *thread) {
- CritScope cs(&crit_);
- threads_.erase(std::remove(threads_.begin(), threads_.end(), thread),
- threads_.end());
-}
-
-void ThreadManager::StopAllThreads_() {
- // TODO: In order to properly implement, Threads need to be ref-counted.
- CritScope cs(&g_thmgr.crit_);
- for (size_t i = 0; i < g_thmgr.threads_.size(); ++i) {
- g_thmgr.threads_[i]->Stop();
- }
-}
-
struct ThreadInit {
Thread* thread;
Runnable* runnable;
@@ -157,7 +136,6 @@
#endif
owned_(true),
delete_self_when_complete_(false) {
- g_thmgr.Add(this);
SetName("Thread", this); // default name
}
@@ -165,7 +143,6 @@
Stop();
if (active_)
Clear(NULL);
- g_thmgr.Remove(this);
}
bool Thread::SleepMs(int milliseconds) {
@@ -231,6 +208,10 @@
ASSERT(!started_);
if (started_) return false;
+ // Make sure that ThreadManager is created on the main thread before
+ // we start a new thread.
+ ThreadManager::Instance();
+
ThreadInit* init = new ThreadInit;
init->thread = this;
init->runnable = runnable;
@@ -334,7 +315,7 @@
void* Thread::PreRun(void* pv) {
ThreadInit* init = static_cast<ThreadInit*>(pv);
- ThreadManager::SetCurrent(init->thread);
+ ThreadManager::Instance()->SetCurrentThread(init->thread);
#if defined(WIN32)
SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str());
#elif defined(POSIX)
@@ -509,6 +490,10 @@
}
bool Thread::WrapCurrent() {
+ return WrapCurrentWithThreadManager(ThreadManager::Instance());
+}
+
+bool Thread::WrapCurrentWithThreadManager(ThreadManager* thread_manager) {
if (started_)
return false;
#if defined(WIN32)
@@ -524,13 +509,13 @@
#endif
owned_ = false;
started_ = true;
- ThreadManager::SetCurrent(this);
+ thread_manager->SetCurrentThread(this);
return true;
}
void Thread::UnwrapCurrent() {
// Clears the platform-specific thread-specific storage.
- ThreadManager::SetCurrent(NULL);
+ ThreadManager::Instance()->SetCurrentThread(NULL);
#ifdef WIN32
if (!CloseHandle(thread_)) {
LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle.";
@@ -541,14 +526,14 @@
AutoThread::AutoThread(SocketServer* ss) : Thread(ss) {
- if (!ThreadManager::CurrentThread()) {
- ThreadManager::SetCurrent(this);
+ if (!ThreadManager::Instance()->CurrentThread()) {
+ ThreadManager::Instance()->SetCurrentThread(this);
}
}
AutoThread::~AutoThread() {
- if (ThreadManager::CurrentThread() == this) {
- ThreadManager::SetCurrent(NULL);
+ if (ThreadManager::Instance()->CurrentThread() == this) {
+ ThreadManager::Instance()->SetCurrentThread(NULL);
}
}
diff --git a/talk/base/thread.h b/talk/base/thread.h
index 04ceeef..13fc68c 100644
--- a/talk/base/thread.h
+++ b/talk/base/thread.h
@@ -53,10 +53,10 @@
ThreadManager();
~ThreadManager();
- static Thread *CurrentThread();
- static void SetCurrent(Thread *thread);
- void Add(Thread *thread);
- void Remove(Thread *thread);
+ static ThreadManager* Instance();
+
+ Thread* CurrentThread();
+ void SetCurrentThread(Thread* thread);
// Returns a thread object with its thread_ ivar set
// to whatever the OS uses to represent the thread.
@@ -71,22 +71,16 @@
// shame to break it. It is also conceivable on Win32 that we won't even
// be able to get synchronization privileges, in which case the result
// will have a NULL handle.
- static Thread *WrapCurrentThread();
- static void UnwrapCurrentThread();
-
- static void StopAllThreads_(); // Experimental
+ Thread *WrapCurrentThread();
+ void UnwrapCurrentThread();
private:
- Thread *main_thread_;
- std::vector<Thread *> threads_;
- CriticalSection crit_;
-
#ifdef POSIX
- static pthread_key_t key_;
+ pthread_key_t key_;
#endif
#ifdef WIN32
- static DWORD key_;
+ DWORD key_;
#endif
DISALLOW_COPY_AND_ASSIGN(ThreadManager);
@@ -126,11 +120,11 @@
virtual ~Thread();
static inline Thread* Current() {
- return ThreadManager::CurrentThread();
+ return ThreadManager::Instance()->CurrentThread();
}
bool IsCurrent() const {
- return (ThreadManager::CurrentThread() == this);
+ return ThreadManager::Instance()->CurrentThread() == this;
}
// Sleeps the calling thread for the specified number of milliseconds, during
@@ -215,6 +209,11 @@
private:
static void *PreRun(void *pv);
+ // ThreadManager calls this instead WrapCurrent() because
+ // ThreadManager::Instance() cannot be used while ThreadManager is
+ // being created.
+ bool WrapCurrentWithThreadManager(ThreadManager* thread_manager);
+
std::list<_SendMessage> sendlist_;
std::string name_;
ThreadPriority priority_;
diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc
index a70b52d..1a3ce75 100644
--- a/talk/base/thread_unittest.cc
+++ b/talk/base/thread_unittest.cc
@@ -124,9 +124,6 @@
CustomThread() {}
virtual ~CustomThread() {}
bool Start() { return false; }
-
- bool WrapCurrent() { return talk_base::Thread::WrapCurrent(); }
- void UnwrapCurrent() { talk_base::Thread::UnwrapCurrent(); }
};
@@ -220,7 +217,7 @@
}
TEST(ThreadTest, Wrap) {
- Thread* current_thread = ThreadManager::CurrentThread();
+ Thread* current_thread = Thread::Current();
current_thread->UnwrapCurrent();
CustomThread* cthread = new CustomThread();
EXPECT_TRUE(cthread->WrapCurrent());
diff --git a/talk/base/time.h b/talk/base/time.h
index eca0e4e..8453ef5 100644
--- a/talk/base/time.h
+++ b/talk/base/time.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2005, Google Inc.
+ * Copyright 2005 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -36,6 +36,15 @@
namespace talk_base {
+static const int64 kNumMillisecsPerSec = 1000;
+static const int64 kNumMicrosecsPerSec = 1000000;
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec /
+ kNumMillisecsPerSec;
+static const int64 kNumNanosecsPerMillisec = kNumNanosecsPerSec /
+ kNumMillisecsPerSec;
+
typedef uint32 TimeStamp;
// Returns the current time in milliseconds.
diff --git a/talk/base/timeutils.cc b/talk/base/timeutils.cc
new file mode 100644
index 0000000..98ba0bc
--- /dev/null
+++ b/talk/base/timeutils.cc
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/timeutils.h"
+
+#define EFFICIENT_IMPLEMENTATION 1
+
+namespace talk_base {
+
+const uint32 LAST = 0xFFFFFFFF;
+const uint32 HALF = 0x80000000;
+
+#ifdef POSIX
+uint32 Time() {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+#endif
+
+#ifdef WIN32
+uint32 Time() {
+ return GetTickCount();
+}
+#endif
+
+uint32 StartTime() {
+ // Close to program execution time
+ static const uint32 g_start = Time();
+ return g_start;
+}
+
+// Make sure someone calls it so that it gets initialized
+static uint32 ignore = StartTime();
+
+uint32 TimeAfter(int32 elapsed) {
+ ASSERT(elapsed >= 0);
+ ASSERT(static_cast<uint32>(elapsed) < HALF);
+ return Time() + elapsed;
+}
+
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) {
+ if (earlier <= later) {
+ return ((earlier <= middle) && (middle <= later));
+ } else {
+ return !((later < middle) && (middle < earlier));
+ }
+}
+
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+ int32 diff = later - earlier;
+ return (diff >= 0 && static_cast<uint32>(diff) < HALF);
+#else
+ const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+ return later_or_equal;
+#endif
+}
+
+bool TimeIsLater(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+ int32 diff = later - earlier;
+ return (diff > 0 && static_cast<uint32>(diff) < HALF);
+#else
+ const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF);
+ return !earlier_or_equal;
+#endif
+}
+
+int32 TimeDiff(uint32 later, uint32 earlier) {
+#if EFFICIENT_IMPLEMENTATION
+ return later - earlier;
+#else
+ const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+ if (later_or_equal) {
+ if (earlier <= later) {
+ return static_cast<long>(later - earlier);
+ } else {
+ return static_cast<long>(later + (LAST - earlier) + 1);
+ }
+ } else {
+ if (later <= earlier) {
+ return -static_cast<long>(earlier - later);
+ } else {
+ return -static_cast<long>(earlier + (LAST - later) + 1);
+ }
+ }
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/timeutils.h b/talk/base/timeutils.h
new file mode 100644
index 0000000..713fd77
--- /dev/null
+++ b/talk/base/timeutils.h
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2005 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_TIMEUTILS_H_
+#define TALK_BASE_TIMEUTILS_H_
+
+#ifndef WIN32
+#include <time.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+static const int64 kNumMillisecsPerSec = 1000;
+static const int64 kNumMicrosecsPerSec = 1000000;
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+static const int64 kNumMicrosecsPerMillisec = kNumMicrosecsPerSec /
+ kNumMillisecsPerSec;
+static const int64 kNumNanosecsPerMillisec = kNumNanosecsPerSec /
+ kNumMillisecsPerSec;
+
+typedef uint32 TimeStamp;
+
+// Returns the current time in milliseconds.
+uint32 Time();
+
+// Approximate time when the program started.
+uint32 StartTime();
+
+// Returns a future timestamp, 'elapsed' milliseconds from now.
+uint32 TimeAfter(int32 elapsed);
+
+// Comparisons between time values, which can wrap around.
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later); // Inclusive
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later); // Inclusive
+bool TimeIsLater(uint32 earlier, uint32 later); // Exclusive
+
+// Returns the later of two timestamps.
+inline uint32 TimeMax(uint32 ts1, uint32 ts2) {
+ return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1;
+}
+
+// Returns the earlier of two timestamps.
+inline uint32 TimeMin(uint32 ts1, uint32 ts2) {
+ return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2;
+}
+
+// Number of milliseconds that would elapse between 'earlier' and 'later'
+// timestamps. The value is negative if 'later' occurs before 'earlier'.
+int32 TimeDiff(uint32 later, uint32 earlier);
+
+// The number of milliseconds that have elapsed since 'earlier'.
+inline int32 TimeSince(uint32 earlier) {
+ return TimeDiff(Time(), earlier);
+}
+
+// The number of milliseconds that will elapse between now and 'later'.
+inline int32 TimeUntil(uint32 later) {
+ return TimeDiff(later, Time());
+}
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TIMEUTILS_H_
diff --git a/talk/base/timeutils_unittest.cc b/talk/base/timeutils_unittest.cc
new file mode 100644
index 0000000..9f315ee
--- /dev/null
+++ b/talk/base/timeutils_unittest.cc
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+
+TEST(TimeTest, Comparison) {
+ // Obtain two different times, in known order
+ TimeStamp ts_earlier = Time();
+ Thread::SleepMs(100);
+ TimeStamp ts_now = Time();
+ EXPECT_NE(ts_earlier, ts_now);
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_now));
+ EXPECT_TRUE( TimeIsLater( ts_earlier, ts_now));
+ EXPECT_FALSE(TimeIsLaterOrEqual(ts_now, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_now, ts_earlier));
+
+ // Edge cases
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_earlier, ts_earlier));
+
+ // Obtain a third time
+ TimeStamp ts_later = TimeAfter(100);
+ EXPECT_NE(ts_now, ts_later);
+ EXPECT_TRUE( TimeIsLater(ts_now, ts_later));
+ EXPECT_TRUE( TimeIsLater(ts_earlier, ts_later));
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_now, ts_later));
+ EXPECT_FALSE(TimeIsBetween(ts_earlier, ts_later, ts_now));
+ EXPECT_FALSE(TimeIsBetween(ts_now, ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsBetween(ts_now, ts_later, ts_earlier));
+ EXPECT_TRUE( TimeIsBetween(ts_later, ts_earlier, ts_now));
+ EXPECT_FALSE(TimeIsBetween(ts_later, ts_now, ts_earlier));
+
+ // Edge cases
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_earlier));
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_later, ts_later));
+
+ // Earlier of two times
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_now));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_now, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier));
+
+ // Later of two times
+ EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_now, TimeMax(ts_earlier, ts_now));
+ EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later));
+ EXPECT_EQ(ts_now, TimeMax(ts_now, ts_earlier));
+ EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier));
+}
+
+TEST(TimeTest, Intervals) {
+ TimeStamp ts_earlier = Time();
+ TimeStamp ts_later = TimeAfter(500);
+
+ // We can't depend on ts_later and ts_earlier to be exactly 500 apart
+ // since time elapses between the calls to Time() and TimeAfter(500)
+ EXPECT_LE(500, TimeDiff(ts_later, ts_earlier));
+ EXPECT_GE(-500, TimeDiff(ts_earlier, ts_later));
+
+ // Time has elapsed since ts_earlier
+ EXPECT_GE(TimeSince(ts_earlier), 0);
+
+ // ts_earlier is earlier than now, so TimeUntil ts_earlier is -ve
+ EXPECT_LE(TimeUntil(ts_earlier), 0);
+
+ // ts_later likely hasn't happened yet, so TimeSince could be -ve
+ // but within 500
+ EXPECT_GE(TimeSince(ts_later), -500);
+
+ // TimeUntil ts_later is at most 500
+ EXPECT_LE(TimeUntil(ts_later), 500);
+}
+
+TEST(TimeTest, BoundaryComparison) {
+ // Obtain two different times, in known order
+ TimeStamp ts_earlier = static_cast<TimeStamp>(-50);
+ TimeStamp ts_later = ts_earlier + 100;
+ EXPECT_NE(ts_earlier, ts_later);
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsLater( ts_earlier, ts_later));
+ EXPECT_FALSE(TimeIsLaterOrEqual(ts_later, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_later, ts_earlier));
+
+ // Earlier of two times
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier));
+
+ // Later of two times
+ EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later));
+ EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier));
+
+ // Interval
+ EXPECT_EQ(100, TimeDiff(ts_later, ts_earlier));
+ EXPECT_EQ(-100, TimeDiff(ts_earlier, ts_later));
+}
+
+} // namespace talk_base
diff --git a/talk/base/timing.cc b/talk/base/timing.cc
new file mode 100644
index 0000000..5e1c6c8
--- /dev/null
+++ b/talk/base/timing.cc
@@ -0,0 +1,152 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/timing.h"
+
+#if defined(POSIX)
+#define _POSIX_C_SOURCE 199506L
+#endif
+
+#include <time.h>
+
+#if defined(POSIX)
+#include <errno.h>
+#include <math.h>
+#include <sys/time.h>
+#if defined(OSX)
+#include <mach/mach.h>
+#include <mach/clock.h>
+#endif
+#elif defined(WIN32)
+#include <sys/timeb.h>
+#include "talk/base/win32.h"
+#endif
+
+Timing::Timing() {
+#if defined(WIN32)
+ QueryPerformanceFrequency(&tick_hz_);
+
+ // This may fail, but we handle failure gracefully in the methods
+ // that use it (use alternative sleep method).
+ //
+ // TODO: Make it possible for user to tell if IdleWait will
+ // be done at lesser resolution because of this.
+ timer_handle_ = CreateWaitableTimer(NULL, // Security attributes.
+ FALSE, // Manual reset?
+ NULL); // Timer name.
+#endif
+}
+
+Timing::~Timing() {
+#if defined(WIN32)
+ if (timer_handle_ != NULL)
+ CloseHandle(timer_handle_);
+#endif
+}
+
+double Timing::WallTimeNow() {
+#if defined(POSIX)
+ struct timeval time;
+ gettimeofday(&time, NULL);
+ // Convert from second (1.0) and microsecond (1e-6).
+ return (static_cast<double>(time.tv_sec) +
+ static_cast<double>(time.tv_usec) * 1.0e-6);
+
+#elif defined(WIN32)
+ struct _timeb time;
+ _ftime(&time);
+ // Convert from second (1.0) and milliseconds (1e-3).
+ return (static_cast<double>(time.time) +
+ static_cast<double>(time.millitm) * 1.0e-3);
+#endif
+}
+
+double Timing::TimerNow() {
+#if defined(OSX)
+ // No clock_gettime on OSX.
+ clock_serv_t clock;
+ mach_timespec_t time;
+ host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock);
+ clock_get_time(clock, &time);
+ return (static_cast<double>(time.tv_sec) +
+ static_cast<double>(time.tv_nsec) * 1.0e-9);
+#elif defined(POSIX)
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ // Convert from second (1.0) and nanosecond (1e-9).
+ return (static_cast<double>(time.tv_sec) +
+ static_cast<double>(time.tv_nsec) * 1.0e-9);
+
+#elif defined(WIN32)
+ LARGE_INTEGER count;
+ QueryPerformanceCounter(&count);
+ return (static_cast<double>(count.QuadPart) /
+ static_cast<double>(tick_hz_.QuadPart));
+#endif
+}
+
+double Timing::BusyWait(double period) {
+ double start_time = TimerNow();
+ while (TimerNow() - start_time < period) {
+ }
+ return TimerNow() - start_time;
+}
+
+double Timing::IdleWait(double period) {
+ double start_time = TimerNow();
+
+#if defined(POSIX)
+ double sec_int, sec_frac = modf(period, &sec_int);
+ struct timespec ts;
+ ts.tv_sec = static_cast<time_t>(sec_int);
+ ts.tv_nsec = static_cast<long>(sec_frac * 1.0e9); // NOLINT
+
+ // NOTE(liulk): for the NOLINT above, long is the appropriate POSIX
+ // type.
+
+ // POSIX nanosleep may be interrupted by signals.
+ while (nanosleep(&ts, &ts) == -1 && errno == EINTR) {
+ }
+
+#elif defined(WIN32)
+ if (timer_handle_ != NULL) {
+ LARGE_INTEGER due_time;
+
+ // Negative indicates relative time. The unit is 100 nanoseconds.
+ due_time.QuadPart = -LONGLONG(period * 1.0e7);
+
+ SetWaitableTimer(timer_handle_, &due_time, 0, NULL, NULL, TRUE);
+ WaitForSingleObject(timer_handle_, INFINITE);
+ } else {
+ // Still attempts to sleep with lesser resolution.
+ // The unit is in milliseconds.
+ Sleep(DWORD(period * 1.0e3));
+ }
+#endif
+
+ return TimerNow() - start_time;
+}
diff --git a/talk/base/timing.h b/talk/base/timing.h
new file mode 100644
index 0000000..f94daf0
--- /dev/null
+++ b/talk/base/timing.h
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_MAGICFLUTE_TESTING_TIMING_H_
+#define TALK_MAGICFLUTE_TESTING_TIMING_H_
+
+#if defined(WIN32)
+#include "talk/base/win32.h"
+#endif
+
+class Timing {
+ public:
+ Timing();
+ virtual ~Timing();
+
+ // WallTimeNow() returns the current wall-clock time in seconds,
+ // within 10 milliseconds resolution.
+ double WallTimeNow();
+
+ // TimerNow() is like WallTimeNow(), but is monotonically
+ // increasing. It returns seconds in resolution of 10 microseconds
+ // or better. Although timer and wall-clock time have the same
+ // timing unit, they do not necessarily correlate because wall-clock
+ // time may be adjusted backwards, hence not monotonic.
+ double TimerNow();
+
+ // BusyWait() exhausts CPU as long as the time elapsed is less than
+ // the specified interval in seconds. Returns the actual waiting
+ // time based on TimerNow() measurement.
+ double BusyWait(double period);
+
+ // IdleWait() relinquishes control of CPU for specified period in
+ // seconds. It uses highest resolution sleep mechanism as possible,
+ // but does not otherwise guarantee the accuracy. Returns the
+ // actual waiting time based on TimerNow() measurement.
+ //
+ // This function is not re-entrant for an object. Create a fresh
+ // Timing object for each thread.
+ double IdleWait(double period);
+
+ private:
+#if defined(WIN32)
+ LARGE_INTEGER tick_hz_;
+ HANDLE timer_handle_;
+#endif
+};
+
+#endif // TALK_MAGICFLUTE_TESTING_TIMING_H_
diff --git a/talk/base/transformadapter.cc b/talk/base/transformadapter.cc
new file mode 100644
index 0000000..53a55a8
--- /dev/null
+++ b/talk/base/transformadapter.cc
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/transformadapter.h"
+
+#include <cstring>
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+TransformAdapter::TransformAdapter(StreamInterface * stream,
+ TransformInterface * transform,
+ bool direction_read)
+ : StreamAdapterInterface(stream), transform_(transform),
+ direction_read_(direction_read), state_(ST_PROCESSING), len_(0) {
+}
+
+TransformAdapter::~TransformAdapter() {
+ TransformAdapter::Close();
+ delete transform_;
+}
+
+StreamResult
+TransformAdapter::Read(void * buffer, size_t buffer_len,
+ size_t * read, int * error) {
+ if (!direction_read_)
+ return SR_EOS;
+
+ while (state_ != ST_ERROR) {
+ if (state_ == ST_COMPLETE)
+ return SR_EOS;
+
+ // Buffer more data
+ if ((state_ == ST_PROCESSING) && (len_ < sizeof(buffer_))) {
+ size_t subread;
+ StreamResult result = StreamAdapterInterface::Read(
+ buffer_ + len_,
+ sizeof(buffer_) - len_,
+ &subread,
+ &error_);
+ if (result == SR_BLOCK) {
+ return SR_BLOCK;
+ } else if (result == SR_ERROR) {
+ state_ = ST_ERROR;
+ break;
+ } else if (result == SR_EOS) {
+ state_ = ST_FLUSHING;
+ } else {
+ len_ += subread;
+ }
+ }
+
+ // Process buffered data
+ size_t in_len = len_;
+ size_t out_len = buffer_len;
+ StreamResult result = transform_->Transform(buffer_, &in_len,
+ buffer, &out_len,
+ (state_ == ST_FLUSHING));
+ ASSERT(result != SR_BLOCK);
+ if (result == SR_EOS) {
+ // Note: Don't signal SR_EOS this iteration, unless out_len is zero
+ state_ = ST_COMPLETE;
+ } else if (result == SR_ERROR) {
+ state_ = ST_ERROR;
+ error_ = -1; // TODO: propagate error
+ break;
+ } else if ((out_len == 0) && (state_ == ST_FLUSHING)) {
+ // If there is no output AND no more input, then something is wrong
+ state_ = ST_ERROR;
+ error_ = -1; // TODO: better error code?
+ break;
+ }
+
+ len_ -= in_len;
+ if (len_ > 0)
+ memmove(buffer_, buffer_ + in_len, len_);
+
+ if (out_len == 0)
+ continue;
+
+ if (read)
+ *read = out_len;
+ return SR_SUCCESS;
+ }
+
+ if (error)
+ *error = error_;
+ return SR_ERROR;
+}
+
+StreamResult
+TransformAdapter::Write(const void * data, size_t data_len,
+ size_t * written, int * error) {
+ if (direction_read_)
+ return SR_EOS;
+
+ size_t bytes_written = 0;
+ while (state_ != ST_ERROR) {
+ if (state_ == ST_COMPLETE)
+ return SR_EOS;
+
+ if (len_ < sizeof(buffer_)) {
+ // Process buffered data
+ size_t in_len = data_len;
+ size_t out_len = sizeof(buffer_) - len_;
+ StreamResult result = transform_->Transform(data, &in_len,
+ buffer_ + len_, &out_len,
+ (state_ == ST_FLUSHING));
+
+ ASSERT(result != SR_BLOCK);
+ if (result == SR_EOS) {
+ // Note: Don't signal SR_EOS this iteration, unless no data written
+ state_ = ST_COMPLETE;
+ } else if (result == SR_ERROR) {
+ ASSERT(false); // When this happens, think about what should be done
+ state_ = ST_ERROR;
+ error_ = -1; // TODO: propagate error
+ break;
+ }
+
+ len_ = out_len;
+ bytes_written = in_len;
+ }
+
+ size_t pos = 0;
+ while (pos < len_) {
+ size_t subwritten;
+ StreamResult result = StreamAdapterInterface::Write(buffer_ + pos,
+ len_ - pos,
+ &subwritten,
+ &error_);
+ if (result == SR_BLOCK) {
+ ASSERT(false); // TODO: we should handle this
+ return SR_BLOCK;
+ } else if (result == SR_ERROR) {
+ state_ = ST_ERROR;
+ break;
+ } else if (result == SR_EOS) {
+ state_ = ST_COMPLETE;
+ break;
+ }
+
+ pos += subwritten;
+ }
+
+ len_ -= pos;
+ if (len_ > 0)
+ memmove(buffer_, buffer_ + pos, len_);
+
+ if (bytes_written == 0)
+ continue;
+
+ if (written)
+ *written = bytes_written;
+ return SR_SUCCESS;
+ }
+
+ if (error)
+ *error = error_;
+ return SR_ERROR;
+}
+
+void
+TransformAdapter::Close() {
+ if (!direction_read_ && (state_ == ST_PROCESSING)) {
+ state_ = ST_FLUSHING;
+ do {
+ Write(0, 0, NULL, NULL);
+ } while (state_ == ST_FLUSHING);
+ }
+ state_ = ST_COMPLETE;
+ StreamAdapterInterface::Close();
+}
+
+} // namespace talk_base
diff --git a/talk/base/transformadapter.h b/talk/base/transformadapter.h
new file mode 100644
index 0000000..e96a13d
--- /dev/null
+++ b/talk/base/transformadapter.h
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_TRANSFORMADAPTER_H__
+#define TALK_BASE_TRANSFORMADAPTER_H__
+
+#include "talk/base/stream.h"
+
+namespace talk_base {
+///////////////////////////////////////////////////////////////////////////////
+
+class TransformInterface {
+public:
+ virtual ~TransformInterface() { }
+
+ // Transform should convert the in_len bytes of input into the out_len-sized
+ // output buffer. If flush is true, there will be no more data following
+ // input.
+ // After the transformation, in_len contains the number of bytes consumed, and
+ // out_len contains the number of bytes ready in output.
+ // Note: Transform should not return SR_BLOCK, as there is no asynchronous
+ // notification available.
+ virtual StreamResult Transform(const void * input, size_t * in_len,
+ void * output, size_t * out_len,
+ bool flush) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// TransformAdapter causes all data passed through to be transformed by the
+// supplied TransformInterface object, which may apply compression, encryption,
+// etc.
+
+class TransformAdapter : public StreamAdapterInterface {
+public:
+ // Note that the transformation is unidirectional, in the direction specified
+ // by the constructor. Operations in the opposite direction result in SR_EOS.
+ TransformAdapter(StreamInterface * stream,
+ TransformInterface * transform,
+ bool direction_read);
+ virtual ~TransformAdapter();
+
+ 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();
+
+ // Apriori, we can't tell what the transformation does to the stream length.
+ virtual bool GetAvailable(size_t* size) const { return false; }
+ virtual bool ReserveSize(size_t size) { return true; }
+
+ // Transformations might not be restartable
+ virtual bool Rewind() { return false; }
+
+private:
+ enum State { ST_PROCESSING, ST_FLUSHING, ST_COMPLETE, ST_ERROR };
+ enum { BUFFER_SIZE = 1024 };
+
+ TransformInterface * transform_;
+ bool direction_read_;
+ State state_;
+ int error_;
+
+ char buffer_[BUFFER_SIZE];
+ size_t len_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TRANSFORMADAPTER_H__
diff --git a/talk/base/unittest_main.cc b/talk/base/unittest_main.cc
index 56fbc54..bca3671 100644
--- a/talk/base/unittest_main.cc
+++ b/talk/base/unittest_main.cc
@@ -69,7 +69,6 @@
return path;
}
-
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
FlagList::SetFlagsFromCommandLine(&argc, argv, false);
diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc
index 096c511..02d0a73 100644
--- a/talk/base/unixfilesystem.cc
+++ b/talk/base/unixfilesystem.cc
@@ -483,9 +483,9 @@
existing_path.SetFolder(existing_path.parent_folder());
}
#ifdef ANDROID
- struct statfs fs;
- memset(&fs, 0, sizeof(fs));
- if (0 != statfs(existing_path.pathname().c_str(), &fs))
+ struct statfs vfs;
+ memset(&vfs, 0, sizeof(vfs));
+ if (0 != statfs(existing_path.pathname().c_str(), &vfs))
return false;
#else
struct statvfs vfs;
@@ -493,12 +493,10 @@
if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
return false;
#endif // ANDROID
-#ifdef LINUX
+#if defined(LINUX) || defined(ANDROID)
*freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
#elif defined(OSX)
*freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
-#elif defined(ANDROID)
- *freebytes = static_cast<int64>(fs.f_bsize) * fs.f_bavail;
#endif
return true;
diff --git a/talk/base/versionparsing.cc b/talk/base/versionparsing.cc
new file mode 100644
index 0000000..03f3dec
--- /dev/null
+++ b/talk/base/versionparsing.cc
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/versionparsing.h"
+
+#include <cstdlib>
+
+namespace talk_base {
+
+bool ParseVersionString(const std::string& version_str,
+ int num_expected_segments,
+ int version[]) {
+ size_t pos = 0;
+ for (int i = 0;;) {
+ size_t dot_pos = version_str.find('.', pos);
+ size_t n;
+ if (dot_pos == std::string::npos) {
+ // npos here is a special value meaning "to the end of the string"
+ n = std::string::npos;
+ } else {
+ n = dot_pos - pos;
+ }
+
+ version[i] = atoi(version_str.substr(pos, n).c_str());
+
+ if (++i >= num_expected_segments) break;
+
+ if (dot_pos == std::string::npos) {
+ // Previous segment was not terminated by a dot, but there's supposed to
+ // be more segments, so that's an error.
+ return false;
+ }
+ pos = dot_pos + 1;
+ }
+ return true;
+}
+
+int CompareVersions(const int version1[],
+ const int version2[],
+ int num_segments) {
+ for (int i = 0; i < num_segments; ++i) {
+ int diff = version1[i] - version2[i];
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ return 0;
+}
+
+} // namespace talk_base
diff --git a/talk/base/versionparsing.h b/talk/base/versionparsing.h
new file mode 100644
index 0000000..c66ad25
--- /dev/null
+++ b/talk/base/versionparsing.h
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_VERSIONPARSING_H_
+#define TALK_BASE_VERSIONPARSING_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Parses a version string into an array. "num_expected_segments" must be the
+// number of numerical segments that the version is expected to have (e.g.,
+// "1.1.2.0" has 4). "version" must be an array of that length to hold the
+// parsed numbers.
+// Returns "true" iff successful.
+bool ParseVersionString(const std::string& version_str,
+ int num_expected_segments,
+ int version[]);
+
+// Computes the lexicographical order of two versions. The return value
+// indicates the order in the standard way (e.g., see strcmp()).
+int CompareVersions(const int version1[],
+ const int version2[],
+ int num_segments);
+
+} // namespace talk_base
+
+#endif // TALK_BASE_VERSIONPARSING_H_
diff --git a/talk/base/versionparsing_unittest.cc b/talk/base/versionparsing_unittest.cc
new file mode 100644
index 0000000..b083265
--- /dev/null
+++ b/talk/base/versionparsing_unittest.cc
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/versionparsing.h"
+
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+static const int kExampleSegments = 4;
+
+typedef int ExampleVersion[kExampleSegments];
+
+TEST(VersionParsing, TestGoodParse) {
+ ExampleVersion ver;
+ std::string str1("1.1.2.0");
+ static const ExampleVersion expect1 = {1, 1, 2, 0};
+ EXPECT_TRUE(ParseVersionString(str1, kExampleSegments, ver));
+ EXPECT_EQ(0, CompareVersions(ver, expect1, kExampleSegments));
+ std::string str2("2.0.0.1");
+ static const ExampleVersion expect2 = {2, 0, 0, 1};
+ EXPECT_TRUE(ParseVersionString(str2, kExampleSegments, ver));
+ EXPECT_EQ(0, CompareVersions(ver, expect2, kExampleSegments));
+}
+
+TEST(VersionParsing, TestBadParse) {
+ ExampleVersion ver;
+ std::string str1("1.1.2");
+ EXPECT_FALSE(ParseVersionString(str1, kExampleSegments, ver));
+ std::string str2("");
+ EXPECT_FALSE(ParseVersionString(str2, kExampleSegments, ver));
+ std::string str3("garbarge");
+ EXPECT_FALSE(ParseVersionString(str3, kExampleSegments, ver));
+}
+
+TEST(VersionParsing, TestCompare) {
+ static const ExampleVersion ver1 = {1, 0, 21, 0};
+ static const ExampleVersion ver2 = {1, 1, 2, 0};
+ static const ExampleVersion ver3 = {1, 1, 3, 0};
+ static const ExampleVersion ver4 = {1, 1, 3, 9861};
+
+ // Test that every combination of comparisons has the expected outcome.
+ EXPECT_EQ(0, CompareVersions(ver1, ver1, kExampleSegments));
+ EXPECT_EQ(0, CompareVersions(ver2, ver2, kExampleSegments));
+ EXPECT_EQ(0, CompareVersions(ver3, ver3, kExampleSegments));
+ EXPECT_EQ(0, CompareVersions(ver4, ver4, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver1, ver2, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver2, ver1, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver1, ver3, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver3, ver1, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver1, ver4, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver4, ver1, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver2, ver3, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver3, ver2, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver2, ver4, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver4, ver2, kExampleSegments));
+
+ EXPECT_GT(0, CompareVersions(ver3, ver4, kExampleSegments));
+ EXPECT_LT(0, CompareVersions(ver4, ver3, kExampleSegments));
+}
+
+} // namespace talk_base
diff --git a/talk/base/virtualsocket_unittest.cc b/talk/base/virtualsocket_unittest.cc
new file mode 100644
index 0000000..93fb5ef
--- /dev/null
+++ b/talk/base/virtualsocket_unittest.cc
@@ -0,0 +1,1009 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#ifdef POSIX
+#include <netinet/in.h>
+#endif
+#include <cmath>
+
+#include "talk/base/logging.h"
+#include "talk/base/gunit.h"
+#include "talk/base/testclient.h"
+#include "talk/base/testutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/base/virtualsocketserver.h"
+
+using namespace talk_base;
+
+// Sends at a constant rate but with random packet sizes.
+struct Sender : public MessageHandler {
+ Sender(Thread* th, AsyncSocket* s, uint32 rt)
+ : thread(th), socket(new AsyncUDPSocket(s)),
+ done(false), rate(rt), count(0) {
+ last_send = Time();
+ thread->PostDelayed(NextDelay(), this, 1);
+ }
+
+ uint32 NextDelay() {
+ uint32 size = (rand() % 4096) + 1;
+ return 1000 * size / rate;
+ }
+
+ void OnMessage(Message* pmsg) {
+ ASSERT_EQ(1u, pmsg->message_id);
+
+ if (done)
+ return;
+
+ uint32 cur_time = Time();
+ uint32 delay = cur_time - last_send;
+ uint32 size = rate * delay / 1000;
+ size = std::min<uint32>(size, 4096);
+ size = std::max<uint32>(size, sizeof(uint32));
+
+ count += size;
+ memcpy(dummy, &cur_time, sizeof(cur_time));
+ socket->Send(dummy, size);
+
+ last_send = cur_time;
+ thread->PostDelayed(NextDelay(), this, 1);
+ }
+
+ Thread* thread;
+ scoped_ptr<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, AsyncSocket* s, uint32 bw)
+ : thread(th), socket(new AsyncUDPSocket(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(AsyncPacketSocket* s, const char* data, size_t size,
+ const SocketAddress& remote_addr) {
+ ASSERT_EQ(socket.get(), s);
+ ASSERT_GE(size, 4U);
+
+ count += size;
+ sec_count += size;
+
+ uint32 send_time = *reinterpret_cast<const uint32*>(data);
+ uint32 recv_time = Time();
+ uint32 delay = recv_time - send_time;
+ sum += delay;
+ sum_sq += delay * delay;
+ samples += 1;
+ }
+
+ void OnMessage(Message* pmsg) {
+ ASSERT_EQ(1u, pmsg->message_id);
+
+ if (done)
+ return;
+
+ // It is always possible for us to receive more than expected because
+ // packets can be further delayed in delivery.
+ if (bandwidth > 0)
+ ASSERT_TRUE(sec_count <= 5 * bandwidth / 4);
+ sec_count = 0;
+ thread->PostDelayed(1000, this, 1);
+ }
+
+ Thread* thread;
+ scoped_ptr<AsyncUDPSocket> socket;
+ uint32 bandwidth;
+ bool done;
+ uint32 count;
+ uint32 sec_count;
+ double sum;
+ double sum_sq;
+ uint32 samples;
+};
+
+class VirtualSocketServerTest : public testing::Test {
+ public:
+ VirtualSocketServerTest() : ss_(new VirtualSocketServer(NULL)),
+ kIPv4AnyAddress(IPAddress(INADDR_ANY), 0),
+ kIPv6AnyAddress(IPAddress(in6addr_any), 0) {
+ }
+
+ void CheckAddressIncrementalization(const SocketAddress& post,
+ const SocketAddress& pre) {
+ EXPECT_EQ(post.port(), pre.port() + 1);
+ IPAddress post_ip = post.ipaddr();
+ IPAddress pre_ip = pre.ipaddr();
+ EXPECT_EQ(pre_ip.family(), post_ip.family());
+ if (post_ip.family() == AF_INET) {
+ in_addr pre_ipv4 = pre_ip.ipv4_address();
+ in_addr post_ipv4 = post_ip.ipv4_address();
+ int difference = ntohl(post_ipv4.s_addr) - ntohl(pre_ipv4.s_addr);
+ EXPECT_EQ(1, difference);
+ } else if (post_ip.family() == AF_INET6) {
+ in6_addr post_ip6 = post_ip.ipv6_address();
+ in6_addr pre_ip6 = pre_ip.ipv6_address();
+ uint32* post_as_ints = reinterpret_cast<uint32*>(&post_ip6.s6_addr);
+ uint32* pre_as_ints = reinterpret_cast<uint32*>(&pre_ip6.s6_addr);
+ EXPECT_EQ(post_as_ints[3], pre_as_ints[3] + 1);
+ }
+ }
+
+ void BasicTest(const SocketAddress& initial_addr) {
+ AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ socket->Bind(initial_addr);
+ SocketAddress server_addr = socket->GetLocalAddress();
+ // Make sure VSS didn't switch families on us.
+ EXPECT_EQ(server_addr.ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+ AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
+
+ SocketAddress client2_addr;
+ EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+ EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
+
+ SocketAddress client1_addr;
+ EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+ EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+ EXPECT_EQ(client1_addr, server_addr);
+
+ for (int i = 0; i < 10; i++) {
+ client2 = new TestClient(AsyncUDPSocket::Create(ss_, SocketAddress()));
+
+ SocketAddress next_client2_addr;
+ EXPECT_EQ(3, client2->SendTo("foo", 3, server_addr));
+ EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &next_client2_addr));
+ CheckAddressIncrementalization(next_client2_addr, client2_addr);
+ // EXPECT_EQ(next_client2_addr.port(), client2_addr.port() + 1);
+
+ SocketAddress server_addr2;
+ EXPECT_EQ(6, client1->SendTo("bizbaz", 6, next_client2_addr));
+ EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &server_addr2));
+ EXPECT_EQ(server_addr2, server_addr);
+
+ client2_addr = next_client2_addr;
+ }
+ }
+
+ // initial_addr should be made from either INADDR_ANY or in6addr_any.
+ void ConnectTest(const SocketAddress& initial_addr) {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+ const SocketAddress kEmptyAddr;
+
+ // Create client
+ AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(client);
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+
+ // Create server
+ AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+ EXPECT_NE(0, server->Listen(5)); // Bind required
+ EXPECT_EQ(0, server->Bind(initial_addr));
+ EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ EXPECT_EQ(0, server->Listen(5));
+ EXPECT_EQ(server->GetState(), AsyncSocket::CS_CONNECTING);
+
+ // No pending server connections
+ EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+ EXPECT_EQ(accept_addr, kEmptyAddr);
+
+ // Attempt connect to listening socket
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+ EXPECT_NE(client->GetLocalAddress(), kEmptyAddr); // Implicit Bind
+ EXPECT_NE(client->GetLocalAddress(), server->GetLocalAddress());
+
+ // Client is connecting
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+ EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Client still connecting
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+ EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+
+ // Server has pending connection
+ EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+ Socket* accepted = server->Accept(&accept_addr);
+ EXPECT_TRUE(NULL != accepted);
+ EXPECT_NE(accept_addr, kEmptyAddr);
+ EXPECT_EQ(accepted->GetRemoteAddress(), accept_addr);
+
+ EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+ EXPECT_EQ(accepted->GetLocalAddress(), server->GetLocalAddress());
+ EXPECT_EQ(accepted->GetRemoteAddress(), client->GetLocalAddress());
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Client has connected
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTED);
+ EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+ EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+ EXPECT_EQ(client->GetRemoteAddress(), accepted->GetLocalAddress());
+ }
+
+ void ConnectToNonListenerTest(const SocketAddress& initial_addr) {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+ const SocketAddress kEmptyAddr;
+
+ // Create client
+ AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(client);
+
+ // Create server
+ AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+ EXPECT_EQ(0, server->Bind(initial_addr));
+ EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ // Attempt connect to non-listening socket
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // No pending server connections
+ EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_addr));
+ EXPECT_EQ(accept_addr, kEmptyAddr);
+
+ // Connection failed
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+ EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+ }
+
+ void CloseDuringConnectTest(const SocketAddress& initial_addr) {
+ testing::StreamSink sink;
+ SocketAddress accept_addr;
+ const SocketAddress kEmptyAddr;
+
+ // Create client and server
+ AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(client);
+ AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+
+ // Initiate connect
+ EXPECT_EQ(0, server->Bind(initial_addr));
+ EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, server->Listen(5));
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ // Server close before socket enters accept queue
+ EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+ server->Close();
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Result: connection failed
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+ // New server
+ delete server;
+ server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+
+ // Initiate connect
+ EXPECT_EQ(0, server->Bind(initial_addr));
+ EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, server->Listen(5));
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Server close while socket is in accept queue
+ EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+ server->Close();
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Result: connection failed
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_TRUE(sink.Check(client, testing::SSE_ERROR));
+
+ // New server
+ delete server;
+ server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+
+ // Initiate connect
+ EXPECT_EQ(0, server->Bind(initial_addr));
+ EXPECT_EQ(server->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, server->Listen(5));
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Server accepts connection
+ EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+ AsyncSocket* accepted = server->Accept(&accept_addr);
+ ASSERT_TRUE(NULL != accepted);
+ sink.Monitor(accepted);
+
+ // Client closes before connection complets
+ EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CONNECTED);
+
+ // Connected message has not been processed yet.
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CONNECTING);
+ client->Close();
+
+ ss_->ProcessMessagesUntilIdle();
+
+ // Result: accepted socket closes
+ EXPECT_EQ(accepted->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_TRUE(sink.Check(accepted, testing::SSE_CLOSE));
+ EXPECT_FALSE(sink.Check(client, testing::SSE_CLOSE));
+ }
+
+ void CloseTest(const SocketAddress& initial_addr) {
+ testing::StreamSink sink;
+ const SocketAddress kEmptyAddr;
+
+ // Create clients
+ AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(a);
+ a->Bind(initial_addr);
+ EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+
+ AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(b);
+ b->Bind(initial_addr);
+ EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+ EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ EXPECT_TRUE(sink.Check(a, testing::SSE_OPEN));
+ EXPECT_EQ(a->GetState(), AsyncSocket::CS_CONNECTED);
+ EXPECT_EQ(a->GetRemoteAddress(), b->GetLocalAddress());
+
+ EXPECT_TRUE(sink.Check(b, testing::SSE_OPEN));
+ EXPECT_EQ(b->GetState(), AsyncSocket::CS_CONNECTED);
+ EXPECT_EQ(b->GetRemoteAddress(), a->GetLocalAddress());
+
+ EXPECT_EQ(1, a->Send("a", 1));
+ b->Close();
+ EXPECT_EQ(1, a->Send("b", 1));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ char buffer[10];
+ EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+ EXPECT_EQ(-1, b->Recv(buffer, 10));
+
+ EXPECT_TRUE(sink.Check(a, testing::SSE_CLOSE));
+ EXPECT_EQ(a->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_EQ(a->GetRemoteAddress(), kEmptyAddr);
+
+ EXPECT_FALSE(sink.Check(b, testing::SSE_CLOSE)); // No signal for Closer
+ EXPECT_EQ(b->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_EQ(b->GetRemoteAddress(), kEmptyAddr);
+ }
+
+ void TcpSendTest(const SocketAddress& initial_addr) {
+ testing::StreamSink sink;
+ const SocketAddress kEmptyAddr;
+
+ // Connect two sockets
+ AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(a);
+ a->Bind(initial_addr);
+ EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(b);
+ b->Bind(initial_addr);
+ EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+ EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+
+ ss_->ProcessMessagesUntilIdle();
+
+ const size_t kBufferSize = 2000;
+ ss_->set_send_buffer_capacity(kBufferSize);
+ ss_->set_recv_buffer_capacity(kBufferSize);
+
+ const size_t kDataSize = 5000;
+ char send_buffer[kDataSize], recv_buffer[kDataSize];
+ for (size_t i = 0; i < kDataSize; ++i) send_buffer[i] = i;
+ memset(recv_buffer, 0, sizeof(recv_buffer));
+ size_t send_pos = 0, recv_pos = 0;
+
+ // Can't send more than send buffer in one write
+ int result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+ EXPECT_EQ(static_cast<int>(kBufferSize), result);
+ send_pos += result;
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+ EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+ // Receive buffer is already filled, fill send buffer again
+ result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+ EXPECT_EQ(static_cast<int>(kBufferSize), result);
+ send_pos += result;
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_FALSE(sink.Check(a, testing::SSE_WRITE));
+ EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+ // No more room in send or receive buffer
+ result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+ EXPECT_EQ(-1, result);
+ EXPECT_TRUE(a->IsBlocking());
+
+ // Read a subset of the data
+ result = b->Recv(recv_buffer + recv_pos, 500);
+ EXPECT_EQ(500, result);
+ recv_pos += result;
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_TRUE(sink.Check(a, testing::SSE_WRITE));
+ EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+ // Room for more on the sending side
+ result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+ EXPECT_EQ(500, result);
+ send_pos += result;
+
+ // Empty the recv buffer
+ while (true) {
+ result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+ if (result < 0) {
+ EXPECT_EQ(-1, result);
+ EXPECT_TRUE(b->IsBlocking());
+ break;
+ }
+ recv_pos += result;
+ }
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+ // Continue to empty the recv buffer
+ while (true) {
+ result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+ if (result < 0) {
+ EXPECT_EQ(-1, result);
+ EXPECT_TRUE(b->IsBlocking());
+ break;
+ }
+ recv_pos += result;
+ }
+
+ // Send last of the data
+ result = a->Send(send_buffer + send_pos, kDataSize - send_pos);
+ EXPECT_EQ(500, result);
+ send_pos += result;
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_TRUE(sink.Check(b, testing::SSE_READ));
+
+ // Receive the last of the data
+ while (true) {
+ result = b->Recv(recv_buffer + recv_pos, kDataSize - recv_pos);
+ if (result < 0) {
+ EXPECT_EQ(-1, result);
+ EXPECT_TRUE(b->IsBlocking());
+ break;
+ }
+ recv_pos += result;
+ }
+
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_FALSE(sink.Check(b, testing::SSE_READ));
+
+ // The received data matches the sent data
+ EXPECT_EQ(kDataSize, send_pos);
+ EXPECT_EQ(kDataSize, recv_pos);
+ EXPECT_EQ(0, memcmp(recv_buffer, send_buffer, kDataSize));
+ }
+
+ void TcpSendsPacketsInOrderTest(const SocketAddress& initial_addr) {
+ const SocketAddress kEmptyAddr;
+
+ // Connect two sockets
+ AsyncSocket* a = ss_->CreateAsyncSocket(SOCK_STREAM);
+ AsyncSocket* b = ss_->CreateAsyncSocket(SOCK_STREAM);
+ a->Bind(initial_addr);
+ EXPECT_EQ(a->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ b->Bind(initial_addr);
+ EXPECT_EQ(b->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+
+ EXPECT_EQ(0, a->Connect(b->GetLocalAddress()));
+ EXPECT_EQ(0, b->Connect(a->GetLocalAddress()));
+ ss_->ProcessMessagesUntilIdle();
+
+ // First, deliver all packets in 0 ms.
+ char buffer[2] = { 0, 0 };
+ const size_t cNumPackets = 10;
+ for (size_t i = 0; i < cNumPackets; ++i) {
+ buffer[0] = '0' + i;
+ EXPECT_EQ(1, a->Send(buffer, 1));
+ }
+
+ ss_->ProcessMessagesUntilIdle();
+
+ for (size_t i = 0; i < cNumPackets; ++i) {
+ EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+ EXPECT_EQ(static_cast<char>('0' + i), buffer[0]);
+ }
+
+ // Next, deliver packets at random intervals
+ const uint32 mean = 50;
+ const uint32 stddev = 50;
+
+ ss_->set_delay_mean(mean);
+ ss_->set_delay_stddev(stddev);
+ ss_->UpdateDelayDistribution();
+
+ for (size_t i = 0; i < cNumPackets; ++i) {
+ buffer[0] = 'A' + i;
+ EXPECT_EQ(1, a->Send(buffer, 1));
+ }
+
+ ss_->ProcessMessagesUntilIdle();
+
+ for (size_t i = 0; i < cNumPackets; ++i) {
+ EXPECT_EQ(1, b->Recv(buffer, sizeof(buffer)));
+ EXPECT_EQ(static_cast<char>('A' + i), buffer[0]);
+ }
+ }
+
+ void BandwidthTest(const SocketAddress& initial_addr) {
+ AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ ASSERT_EQ(0, send_socket->Bind(initial_addr));
+ ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+ EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+ uint32 bandwidth = 64 * 1024;
+ ss_->set_bandwidth(bandwidth);
+
+ Thread* pthMain = Thread::Current();
+ Sender sender(pthMain, send_socket, 80 * 1024);
+ Receiver receiver(pthMain, recv_socket, bandwidth);
+
+ pthMain->ProcessMessages(5000);
+ sender.done = true;
+ pthMain->ProcessMessages(5000);
+
+ ASSERT_TRUE(receiver.count >= 5 * 3 * bandwidth / 4);
+ ASSERT_TRUE(receiver.count <= 6 * bandwidth); // queue could drain for 1s
+
+ ss_->set_bandwidth(0);
+ }
+
+ void DelayTest(const SocketAddress& initial_addr) {
+ time_t seed = ::time(NULL);
+ LOG(LS_VERBOSE) << "seed = " << seed;
+ srand(seed);
+
+ const uint32 mean = 2000;
+ const uint32 stddev = 500;
+
+ ss_->set_delay_mean(mean);
+ ss_->set_delay_stddev(stddev);
+ ss_->UpdateDelayDistribution();
+
+ AsyncSocket* send_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ AsyncSocket* recv_socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ ASSERT_EQ(0, send_socket->Bind(initial_addr));
+ ASSERT_EQ(0, recv_socket->Bind(initial_addr));
+ EXPECT_EQ(send_socket->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ EXPECT_EQ(recv_socket->GetLocalAddress().ipaddr().family(),
+ initial_addr.ipaddr().family());
+ ASSERT_EQ(0, send_socket->Connect(recv_socket->GetLocalAddress()));
+
+ Thread* pthMain = Thread::Current();
+ // Avg packet size is 2K, so at 200KB/s for 10s, we should see about
+ // 1000 packets, which is necessary to get a good distribution.
+ Sender sender(pthMain, send_socket, 100 * 2 * 1024);
+ Receiver receiver(pthMain, recv_socket, 0);
+
+ pthMain->ProcessMessages(10000);
+ sender.done = receiver.done = true;
+ ss_->ProcessMessagesUntilIdle();
+
+ const double sample_mean = receiver.sum / receiver.samples;
+ double num =
+ receiver.samples * receiver.sum_sq - receiver.sum * receiver.sum;
+ double den = receiver.samples * (receiver.samples - 1);
+ const double sample_stddev = std::sqrt(num / den);
+ LOG(LS_VERBOSE) << "mean=" << sample_mean << " stddev=" << sample_stddev;
+
+ EXPECT_LE(500u, receiver.samples);
+ // We initially used a 0.1 fudge factor, but on the build machine, we
+ // have seen the value differ by as much as 0.13.
+ EXPECT_NEAR(mean, sample_mean, 0.15 * mean);
+ EXPECT_NEAR(stddev, sample_stddev, 0.15 * stddev);
+
+ ss_->set_delay_mean(0);
+ ss_->set_delay_stddev(0);
+ ss_->UpdateDelayDistribution();
+ }
+
+ // Test cross-family communication between a client bound to client_addr and a
+ // server bound to server_addr. shouldSucceed indicates if communication is
+ // expected to work or not.
+ void CrossFamilyConnectionTest(const SocketAddress& client_addr,
+ const SocketAddress& server_addr,
+ bool shouldSucceed) {
+ testing::StreamSink sink;
+ SocketAddress accept_address;
+ const SocketAddress kEmptyAddr;
+
+ // Client gets a IPv4 address
+ AsyncSocket* client = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(client);
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_EQ(client->GetLocalAddress(), kEmptyAddr);
+ client->Bind(client_addr);
+
+ // Server gets a non-mapped non-any IPv6 address.
+ // IPv4 sockets should not be able to connect to this.
+ AsyncSocket* server = ss_->CreateAsyncSocket(SOCK_STREAM);
+ sink.Monitor(server);
+ server->Bind(server_addr);
+ server->Listen(5);
+
+ if (shouldSucceed) {
+ EXPECT_EQ(0, client->Connect(server->GetLocalAddress()));
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_TRUE(sink.Check(server, testing::SSE_READ));
+ Socket* accepted = server->Accept(&accept_address);
+ EXPECT_TRUE(NULL != accepted);
+ EXPECT_NE(kEmptyAddr, accept_address);
+ ss_->ProcessMessagesUntilIdle();
+ EXPECT_TRUE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), server->GetLocalAddress());
+ } else {
+ // Check that the connection failed.
+ EXPECT_EQ(-1, client->Connect(server->GetLocalAddress()));
+ ss_->ProcessMessagesUntilIdle();
+
+ EXPECT_FALSE(sink.Check(server, testing::SSE_READ));
+ EXPECT_TRUE(NULL == server->Accept(&accept_address));
+ EXPECT_EQ(accept_address, kEmptyAddr);
+ EXPECT_EQ(client->GetState(), AsyncSocket::CS_CLOSED);
+ EXPECT_FALSE(sink.Check(client, testing::SSE_OPEN));
+ EXPECT_EQ(client->GetRemoteAddress(), kEmptyAddr);
+ }
+ }
+
+ // Test cross-family datagram sending between a client bound to client_addr
+ // and a server bound to server_addr. shouldSucceed indicates if sending is
+ // expected to succed or not.
+ void CrossFamilyDatagramTest(const SocketAddress& client_addr,
+ const SocketAddress& server_addr,
+ bool shouldSucceed) {
+ AsyncSocket* socket = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ socket->Bind(server_addr);
+ SocketAddress bound_server_addr = socket->GetLocalAddress();
+ TestClient* client1 = new TestClient(new AsyncUDPSocket(socket));
+
+ AsyncSocket* socket2 = ss_->CreateAsyncSocket(SOCK_DGRAM);
+ socket2->Bind(client_addr);
+ TestClient* client2 = new TestClient(new AsyncUDPSocket(socket2));
+ SocketAddress client2_addr;
+
+ if (shouldSucceed) {
+ EXPECT_EQ(3, client2->SendTo("foo", 3, bound_server_addr));
+ EXPECT_TRUE(client1->CheckNextPacket("foo", 3, &client2_addr));
+ SocketAddress client1_addr;
+ EXPECT_EQ(6, client1->SendTo("bizbaz", 6, client2_addr));
+ EXPECT_TRUE(client2->CheckNextPacket("bizbaz", 6, &client1_addr));
+ EXPECT_EQ(client1_addr, bound_server_addr);
+ } else {
+ EXPECT_EQ(-1, client2->SendTo("foo", 3, bound_server_addr));
+ EXPECT_FALSE(client1->CheckNextPacket("foo", 3, 0));
+ }
+ }
+
+ protected:
+ virtual void SetUp() {
+ Thread::Current()->set_socketserver(ss_);
+ }
+ virtual void TearDown() {
+ Thread::Current()->set_socketserver(NULL);
+ }
+
+ VirtualSocketServer* ss_;
+ const SocketAddress kIPv4AnyAddress;
+ const SocketAddress kIPv6AnyAddress;
+};
+
+TEST_F(VirtualSocketServerTest, basic_v4) {
+ SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 5000);
+ BasicTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, basic_v6) {
+ SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 5000);
+ BasicTest(ipv6_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, connect_v4) {
+ ConnectTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_v6) {
+ ConnectTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v4) {
+ ConnectToNonListenerTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, connect_to_non_listener_v6) {
+ ConnectToNonListenerTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_during_connect_v4) {
+ CloseDuringConnectTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_during_connect_v6) {
+ CloseDuringConnectTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_v4) {
+ CloseTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, close_v6) {
+ CloseTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v4) {
+ TcpSendTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, tcp_send_v6) {
+ TcpSendTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v4) {
+ TcpSendsPacketsInOrderTest(kIPv4AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, TcpSendsPacketsInOrder_v6) {
+ TcpSendsPacketsInOrderTest(kIPv6AnyAddress);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v4) {
+ SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+ BandwidthTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, bandwidth_v6) {
+ SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+ BandwidthTest(ipv6_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v4) {
+ SocketAddress ipv4_test_addr(IPAddress(INADDR_ANY), 1000);
+ DelayTest(ipv4_test_addr);
+}
+
+TEST_F(VirtualSocketServerTest, delay_v6) {
+ SocketAddress ipv6_test_addr(IPAddress(in6addr_any), 1000);
+ DelayTest(ipv6_test_addr);
+}
+
+// Works, receiving socket sees 127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromMappedIPv6ToIPv4Any) {
+ CrossFamilyConnectionTest(SocketAddress("::ffff:127.0.0.2", 0),
+ SocketAddress("0.0.0.0", 5000),
+ true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToIPv4Any) {
+ CrossFamilyConnectionTest(SocketAddress("::2", 0),
+ SocketAddress("0.0.0.0", 5000),
+ false);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromUnMappedIPv6ToMappedIPv6) {
+ CrossFamilyConnectionTest(SocketAddress("::2", 0),
+ SocketAddress("::ffff:127.0.0.1", 5000),
+ false);
+}
+
+// Works. receiving socket sees ::ffff:127.0.0.2.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToIPv6Any) {
+ CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+ SocketAddress("::", 5000),
+ true);
+}
+
+// Fails.
+TEST_F(VirtualSocketServerTest, CantConnectFromIPv4ToUnMappedIPv6) {
+ CrossFamilyConnectionTest(SocketAddress("127.0.0.2", 0),
+ SocketAddress("::1", 5000),
+ false);
+}
+
+// Works. Receiving socket sees ::ffff:127.0.0.1.
+TEST_F(VirtualSocketServerTest, CanConnectFromIPv4ToMappedIPv6) {
+ CrossFamilyConnectionTest(SocketAddress("127.0.0.1", 0),
+ SocketAddress("::ffff:127.0.0.2", 5000),
+ true);
+}
+
+// Works, receiving socket sees a result from GetNextIP.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv6ToIPv4Any) {
+ CrossFamilyConnectionTest(SocketAddress("::", 0),
+ SocketAddress("0.0.0.0", 5000),
+ true);
+}
+
+// Works, receiving socket sees whatever GetNextIP gave the client.
+TEST_F(VirtualSocketServerTest, CanConnectFromUnboundIPv4ToIPv6Any) {
+ CrossFamilyConnectionTest(SocketAddress(),
+ SocketAddress("::", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv4ToIPv6Any) {
+ CrossFamilyDatagramTest(SocketAddress(),
+ SocketAddress("::", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromMappedIPv6ToIPv4Any) {
+ CrossFamilyDatagramTest(SocketAddress("::ffff:127.0.0.1", 0),
+ SocketAddress("0.0.0.0", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToIPv4Any) {
+ CrossFamilyDatagramTest(SocketAddress("::2", 0),
+ SocketAddress("0.0.0.0", 5000),
+ false);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromUnMappedIPv6ToMappedIPv6) {
+ CrossFamilyDatagramTest(SocketAddress("::2", 0),
+ SocketAddress("::ffff:127.0.0.1", 5000),
+ false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToIPv6Any) {
+ CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+ SocketAddress("::", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CantSendDatagramFromIPv4ToUnMappedIPv6) {
+ CrossFamilyDatagramTest(SocketAddress("127.0.0.2", 0),
+ SocketAddress("::1", 5000),
+ false);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromIPv4ToMappedIPv6) {
+ CrossFamilyDatagramTest(SocketAddress("127.0.0.1", 0),
+ SocketAddress("::ffff:127.0.0.2", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CanSendDatagramFromUnboundIPv6ToIPv4Any) {
+ CrossFamilyDatagramTest(SocketAddress("::", 0),
+ SocketAddress("0.0.0.0", 5000),
+ true);
+}
+
+TEST_F(VirtualSocketServerTest, CreatesStandardDistribution) {
+ const uint32 kTestMean[] = { 10, 100, 333, 1000 };
+ const double kTestDev[] = { 0.25, 0.1, 0.01 };
+ // TODO: The current code only works for 1000 data points or more.
+ const uint32 kTestSamples[] = { /*10, 100,*/ 1000 };
+ for (size_t midx = 0; midx < ARRAY_SIZE(kTestMean); ++midx) {
+ for (size_t didx = 0; didx < ARRAY_SIZE(kTestDev); ++didx) {
+ for (size_t sidx = 0; sidx < ARRAY_SIZE(kTestSamples); ++sidx) {
+ ASSERT_LT(0u, kTestSamples[sidx]);
+ const uint32 kStdDev =
+ static_cast<uint32>(kTestDev[didx] * kTestMean[midx]);
+ VirtualSocketServer::Function* f =
+ VirtualSocketServer::CreateDistribution(kTestMean[midx],
+ kStdDev,
+ kTestSamples[sidx]);
+ ASSERT_TRUE(NULL != f);
+ ASSERT_EQ(kTestSamples[sidx], f->size());
+ double sum = 0;
+ for (uint32 i = 0; i < f->size(); ++i) {
+ sum += (*f)[i].second;
+ }
+ const double mean = sum / f->size();
+ double sum_sq_dev = 0;
+ for (uint32 i = 0; i < f->size(); ++i) {
+ double dev = (*f)[i].second - mean;
+ sum_sq_dev += dev * dev;
+ }
+ const double stddev = std::sqrt(sum_sq_dev / f->size());
+ EXPECT_NEAR(kTestMean[midx], mean, 0.1 * kTestMean[midx])
+ << "M=" << kTestMean[midx]
+ << " SD=" << kStdDev
+ << " N=" << kTestSamples[sidx];
+ EXPECT_NEAR(kStdDev, stddev, 0.1 * kStdDev)
+ << "M=" << kTestMean[midx]
+ << " SD=" << kStdDev
+ << " N=" << kTestSamples[sidx];
+ delete f;
+ }
+ }
+ }
+}
diff --git a/talk/base/virtualsocketserver.cc b/talk/base/virtualsocketserver.cc
new file mode 100644
index 0000000..c18ffb6
--- /dev/null
+++ b/talk/base/virtualsocketserver.cc
@@ -0,0 +1,1102 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/virtualsocketserver.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+
+namespace talk_base {
+#ifdef WIN32
+const in_addr kInitialNextIPv4 = { {0x01, 0, 0, 0} };
+#else
+// This value is entirely arbitrary, hence the lack of concern about endianness.
+const in_addr kInitialNextIPv4 = { 0x01000000 };
+#endif
+// Starts at ::2 so as to not cause confusion with ::1.
+const in6_addr kInitialNextIPv6 = { { {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2
+ } } };
+
+const uint16 kFirstEphemeralPort = 49152;
+const uint16 kLastEphemeralPort = 65535;
+const uint16 kEphemeralPortCount = kLastEphemeralPort - kFirstEphemeralPort + 1;
+const uint32 kDefaultNetworkCapacity = 64 * 1024;
+const uint32 kDefaultTcpBufferSize = 32 * 1024;
+
+const uint32 UDP_HEADER_SIZE = 28; // IP + UDP headers
+const uint32 TCP_HEADER_SIZE = 40; // IP + TCP headers
+const uint32 TCP_MSS = 1400; // Maximum segment size
+
+// Note: The current algorithm doesn't work for sample sizes smaller than this.
+const int NUM_SAMPLES = 1000;
+
+enum {
+ MSG_ID_PACKET,
+ MSG_ID_CONNECT,
+ MSG_ID_DISCONNECT,
+};
+
+// 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), consumed_(0), from_(from) {
+ ASSERT(NULL != data);
+ data_ = new char[size_];
+ std::memcpy(data_, data, size_);
+ }
+
+ virtual ~Packet() {
+ delete[] data_;
+ }
+
+ const char* data() const { return data_ + consumed_; }
+ size_t size() const { return size_ - consumed_; }
+ const SocketAddress& from() const { return from_; }
+
+ // Remove the first size bytes from the data.
+ void Consume(size_t size) {
+ ASSERT(size + consumed_ < size_);
+ consumed_ += size;
+ }
+
+ private:
+ char* data_;
+ size_t size_, consumed_;
+ SocketAddress from_;
+};
+
+struct MessageAddress : public MessageData {
+ explicit MessageAddress(const SocketAddress& a) : addr(a) { }
+ SocketAddress addr;
+};
+
+// 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)
+ : server_(server), type_(type), async_(async), state_(CS_CLOSED),
+ listen_queue_(NULL), write_enabled_(false), network_size_(0),
+ recv_buffer_size_(0), bound_(false), was_any_(false) {
+ ASSERT((type_ == SOCK_DGRAM) || (type_ == SOCK_STREAM));
+ ASSERT(async_ || (type_ != SOCK_STREAM)); // We only support async streams
+ }
+
+ virtual ~VirtualSocket() {
+ Close();
+
+ for (RecvBuffer::iterator it = recv_buffer_.begin();
+ it != recv_buffer_.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ virtual SocketAddress GetLocalAddress() const {
+ return local_addr_;
+ }
+
+ virtual SocketAddress GetRemoteAddress() const {
+ return remote_addr_;
+ }
+
+ // Used by server sockets to set the local address without binding.
+ void SetLocalAddress(const SocketAddress& addr) {
+ local_addr_ = addr;
+ }
+
+ virtual int Bind(const SocketAddress& addr) {
+ if (!local_addr_.IsAny()) {
+ error_ = EINVAL;
+ return -1;
+ }
+ local_addr_ = addr;
+ int result = server_->Bind(this, &local_addr_);
+ if (result != 0) {
+ local_addr_.Clear();
+ error_ = EADDRINUSE;
+ } else {
+ bound_ = true;
+ was_any_ = addr.IsAnyIP();
+ }
+ return result;
+ }
+
+ virtual int Connect(const SocketAddress& addr) {
+ return InitiateConnect(addr, true);
+ }
+
+ virtual int Close() {
+ if (!local_addr_.IsAny() && bound_) {
+ // Remove from the binding table.
+ server_->Unbind(local_addr_, this);
+ bound_ = false;
+ }
+
+ if (SOCK_STREAM == type_) {
+ // Cancel pending sockets
+ if (listen_queue_) {
+ while (!listen_queue_->empty()) {
+ SocketAddress addr = listen_queue_->front();
+
+ // Disconnect listening socket.
+ server_->Disconnect(server_->LookupBinding(addr));
+ listen_queue_->pop_front();
+ }
+ delete listen_queue_;
+ listen_queue_ = NULL;
+ }
+ // Disconnect stream sockets
+ if (CS_CONNECTED == state_) {
+ // Disconnect remote socket, check if it is a child of a server socket.
+ VirtualSocket* socket =
+ server_->LookupConnection(local_addr_, remote_addr_);
+ if (!socket) {
+ // Not a server socket child, then see if it is bound.
+ // TODO: If this is indeed a server socket that has no
+ // children this will cause the server socket to be
+ // closed. This might lead to unexpected results, how to fix this?
+ socket = server_->LookupBinding(remote_addr_);
+ }
+ server_->Disconnect(socket);
+
+ // Remove mapping for both directions.
+ server_->RemoveConnection(remote_addr_, local_addr_);
+ server_->RemoveConnection(local_addr_, remote_addr_);
+ }
+ // Cancel potential connects
+ MessageList msgs;
+ if (server_->msg_queue_) {
+ server_->msg_queue_->Clear(this, MSG_ID_CONNECT, &msgs);
+ }
+ for (MessageList::iterator it = msgs.begin(); it != msgs.end(); ++it) {
+ ASSERT(NULL != it->pdata);
+ MessageAddress* data = static_cast<MessageAddress*>(it->pdata);
+
+ // Lookup remote side.
+ VirtualSocket* socket = server_->LookupConnection(local_addr_,
+ data->addr);
+ if (socket) {
+ // Server socket, remote side is a socket retreived by
+ // accept. Accepted sockets are not bound so we will not
+ // find it by looking in the bindings table.
+ server_->Disconnect(socket);
+ server_->RemoveConnection(local_addr_, data->addr);
+ } else {
+ server_->Disconnect(server_->LookupBinding(data->addr));
+ }
+ delete data;
+ }
+ // Clear incoming packets and disconnect messages
+ if (server_->msg_queue_) {
+ server_->msg_queue_->Clear(this);
+ }
+ }
+
+ state_ = CS_CLOSED;
+ local_addr_.Clear();
+ remote_addr_.Clear();
+ return 0;
+ }
+
+ virtual int Send(const void *pv, size_t cb) {
+ if (CS_CONNECTED != state_) {
+ error_ = ENOTCONN;
+ return -1;
+ }
+ if (SOCK_DGRAM == type_) {
+ return SendUdp(pv, cb, remote_addr_);
+ } else {
+ return SendTcp(pv, cb);
+ }
+ }
+
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+ if (SOCK_DGRAM == type_) {
+ return SendUdp(pv, cb, addr);
+ } else {
+ if (CS_CONNECTED != state_) {
+ error_ = ENOTCONN;
+ return -1;
+ }
+ return SendTcp(pv, cb);
+ }
+ }
+
+ virtual int Recv(void *pv, size_t cb) {
+ SocketAddress addr;
+ return RecvFrom(pv, cb, &addr);
+ }
+
+ virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+ // If we don't have a packet, then either error or wait for one to arrive.
+ if (recv_buffer_.empty()) {
+ if (async_) {
+ error_ = EAGAIN;
+ return -1;
+ }
+ while (recv_buffer_.empty()) {
+ Message msg;
+ server_->msg_queue_->Get(&msg);
+ server_->msg_queue_->Dispatch(&msg);
+ }
+ }
+
+ // Return the packet at the front of the queue.
+ Packet* packet = recv_buffer_.front();
+ size_t data_read = _min(cb, packet->size());
+ std::memcpy(pv, packet->data(), data_read);
+ *paddr = packet->from();
+
+ if (data_read < packet->size()) {
+ packet->Consume(data_read);
+ } else {
+ recv_buffer_.pop_front();
+ delete packet;
+ }
+
+ if (SOCK_STREAM == type_) {
+ bool was_full = (recv_buffer_size_ == server_->recv_buffer_capacity_);
+ recv_buffer_size_ -= data_read;
+ if (was_full) {
+ VirtualSocket* sender = server_->LookupBinding(remote_addr_);
+ ASSERT(NULL != sender);
+ server_->SendTcp(sender);
+ }
+ }
+
+ return static_cast<int>(data_read);
+ }
+
+ virtual int Listen(int backlog) {
+ ASSERT(SOCK_STREAM == type_);
+ ASSERT(CS_CLOSED == state_);
+ if (local_addr_.IsAny()) {
+ error_ = EINVAL;
+ return -1;
+ }
+ ASSERT(NULL == listen_queue_);
+ listen_queue_ = new ListenQueue;
+ state_ = CS_CONNECTING;
+ return 0;
+ }
+
+ virtual VirtualSocket* Accept(SocketAddress *paddr) {
+ if (NULL == listen_queue_) {
+ error_ = EINVAL;
+ return NULL;
+ }
+ while (!listen_queue_->empty()) {
+ VirtualSocket* socket = new VirtualSocket(server_, type_, async_);
+
+ // Set the new local address to the same as this server socket.
+ socket->SetLocalAddress(local_addr_);
+ // Sockets made from a socket that 'was Any' need to inherit that.
+ socket->set_was_any(was_any_);
+ SocketAddress remote_addr(listen_queue_->front());
+ int result = socket->InitiateConnect(remote_addr, false);
+ listen_queue_->pop_front();
+ if (result != 0) {
+ delete socket;
+ continue;
+ }
+ socket->CompleteConnect(remote_addr, false);
+ if (paddr) {
+ *paddr = remote_addr;
+ }
+ return socket;
+ }
+ error_ = EWOULDBLOCK;
+ return NULL;
+ }
+
+ virtual int GetError() const {
+ return error_;
+ }
+
+ virtual void SetError(int error) {
+ error_ = error;
+ }
+
+ virtual ConnState GetState() const {
+ return state_;
+ }
+
+ virtual int GetOption(Option opt, int* value) {
+ OptionsMap::const_iterator it = options_map_.find(opt);
+ if (it == options_map_.end()) {
+ return -1;
+ }
+ *value = it->second;
+ return 0; // 0 is success to emulate getsockopt()
+ }
+
+ virtual int SetOption(Option opt, int value) {
+ options_map_[opt] = value;
+ return 0; // 0 is success to emulate setsockopt()
+ }
+
+ virtual int EstimateMTU(uint16* mtu) {
+ if (CS_CONNECTED != state_)
+ return ENOTCONN;
+ else
+ return 65536;
+ }
+
+ void OnMessage(Message *pmsg) {
+ if (pmsg->message_id == MSG_ID_PACKET) {
+ //ASSERT(!local_addr_.IsAny());
+ ASSERT(NULL != pmsg->pdata);
+ Packet* packet = static_cast<Packet*>(pmsg->pdata);
+
+ recv_buffer_.push_back(packet);
+
+ if (async_) {
+ SignalReadEvent(this);
+ }
+ } else if (pmsg->message_id == MSG_ID_CONNECT) {
+ ASSERT(NULL != pmsg->pdata);
+ MessageAddress* data = static_cast<MessageAddress*>(pmsg->pdata);
+ if (listen_queue_ != NULL) {
+ listen_queue_->push_back(data->addr);
+ if (async_) {
+ SignalReadEvent(this);
+ }
+ } else if ((SOCK_STREAM == type_) && (CS_CONNECTING == state_)) {
+ CompleteConnect(data->addr, true);
+ } else {
+ LOG(LS_VERBOSE) << "Socket at " << local_addr_ << " is not listening";
+ server_->Disconnect(server_->LookupBinding(data->addr));
+ }
+ delete data;
+ } else if (pmsg->message_id == MSG_ID_DISCONNECT) {
+ ASSERT(SOCK_STREAM == type_);
+ if (CS_CLOSED != state_) {
+ int error = (CS_CONNECTING == state_) ? ECONNREFUSED : 0;
+ state_ = CS_CLOSED;
+ remote_addr_.Clear();
+ if (async_) {
+ SignalCloseEvent(this, error);
+ }
+ }
+ } else {
+ ASSERT(false);
+ }
+ }
+
+ bool was_any() { return was_any_; }
+ void set_was_any(bool was_any) { was_any_ = was_any; }
+
+ private:
+ struct NetworkEntry {
+ uint32 size;
+ uint32 done_time;
+ };
+
+ typedef std::deque<SocketAddress> ListenQueue;
+ typedef std::deque<NetworkEntry> NetworkQueue;
+ typedef std::vector<char> SendBuffer;
+ typedef std::list<Packet*> RecvBuffer;
+ typedef std::map<Option, int> OptionsMap;
+
+ int InitiateConnect(const SocketAddress& addr, bool use_delay) {
+ if (!remote_addr_.IsAny()) {
+ error_ = (CS_CONNECTED == state_) ? EISCONN : EINPROGRESS;
+ return -1;
+ }
+ if (local_addr_.IsAny()) {
+ // If there's no local address set, grab a random one in the correct AF.
+ int result = 0;
+ if (addr.ipaddr().family() == AF_INET) {
+ result = Bind(SocketAddress("0.0.0.0", 0));
+ } else if (addr.ipaddr().family() == AF_INET6) {
+ result = Bind(SocketAddress("::", 0));
+ }
+ if (result != 0) {
+ return result;
+ }
+ }
+ if (type_ == SOCK_DGRAM) {
+ remote_addr_ = addr;
+ state_ = CS_CONNECTED;
+ } else {
+ int result = server_->Connect(this, addr, use_delay);
+ if (result != 0) {
+ error_ = EHOSTUNREACH;
+ return -1;
+ }
+ state_ = CS_CONNECTING;
+ }
+ return 0;
+ }
+
+ void CompleteConnect(const SocketAddress& addr, bool notify) {
+ ASSERT(CS_CONNECTING == state_);
+ remote_addr_ = addr;
+ state_ = CS_CONNECTED;
+ server_->AddConnection(remote_addr_, local_addr_, this);
+ if (async_ && notify) {
+ SignalConnectEvent(this);
+ }
+ }
+
+ int SendUdp(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()) {
+ int result = server_->Bind(this, &local_addr_);
+ if (result != 0) {
+ local_addr_.Clear();
+ error_ = EADDRINUSE;
+ return result;
+ }
+ }
+
+ // Send the data in a message to the appropriate socket.
+ return server_->SendUdp(this, static_cast<const char*>(pv), cb, addr);
+ }
+
+ int SendTcp(const void* pv, size_t cb) {
+ size_t capacity = server_->send_buffer_capacity_ - send_buffer_.size();
+ if (0 == capacity) {
+ write_enabled_ = true;
+ error_ = EWOULDBLOCK;
+ return -1;
+ }
+ size_t consumed = _min(cb, capacity);
+ const char* cpv = static_cast<const char*>(pv);
+ send_buffer_.insert(send_buffer_.end(), cpv, cpv + consumed);
+ server_->SendTcp(this);
+ return consumed;
+ }
+
+ VirtualSocketServer* server_;
+ int type_;
+ bool async_;
+ ConnState state_;
+ int error_;
+ SocketAddress local_addr_;
+ SocketAddress remote_addr_;
+
+ // Pending sockets which can be Accepted
+ ListenQueue* listen_queue_;
+
+ // Data which tcp has buffered for sending
+ SendBuffer send_buffer_;
+ bool write_enabled_;
+
+ // Critical section to protect the recv_buffer and queue_
+ CriticalSection crit_;
+
+ // Network model that enforces bandwidth and capacity constraints
+ NetworkQueue network_;
+ uint32 network_size_;
+
+ // Data which has been received from the network
+ RecvBuffer recv_buffer_;
+ // The amount of data which is in flight or in recv_buffer_
+ uint32 recv_buffer_size_;
+
+ // Is this socket bound?
+ bool bound_;
+
+ // When we bind a socket to Any, VSS's Bind gives it another address. For
+ // dual-stack sockets, we want to distinguish between sockets that were
+ // explicitly given a particular address and sockets that had one picked
+ // for them by VSS.
+ bool was_any_;
+
+ // Store the options that are set
+ OptionsMap options_map_;
+
+ friend class VirtualSocketServer;
+};
+
+VirtualSocketServer::VirtualSocketServer(SocketServer* ss)
+ : server_(ss), server_owned_(false), msg_queue_(NULL), stop_on_idle_(false),
+ network_delay_(Time()), next_ipv4_(kInitialNextIPv4),
+ next_ipv6_(kInitialNextIPv6), next_port_(kFirstEphemeralPort),
+ bindings_(new AddressMap()), connections_(new ConnectionMap()),
+ bandwidth_(0), network_capacity_(kDefaultNetworkCapacity),
+ send_buffer_capacity_(kDefaultTcpBufferSize),
+ recv_buffer_capacity_(kDefaultTcpBufferSize),
+ delay_mean_(0), delay_stddev_(0), delay_samples_(NUM_SAMPLES),
+ delay_dist_(NULL), drop_prob_(0.0) {
+ if (!server_) {
+ server_ = new PhysicalSocketServer();
+ server_owned_ = true;
+ }
+ UpdateDelayDistribution();
+}
+
+VirtualSocketServer::~VirtualSocketServer() {
+ delete bindings_;
+ delete connections_;
+ delete delay_dist_;
+ if (server_owned_) {
+ delete server_;
+ }
+}
+
+IPAddress VirtualSocketServer::GetNextIP(int family) {
+ if (family == AF_INET) {
+ IPAddress next_ip(next_ipv4_);
+ next_ipv4_.s_addr = htonl(ntohl(next_ipv4_.s_addr) + 1);
+ return next_ip;
+ } else if (family == AF_INET6) {
+ IPAddress next_ip(next_ipv6_);
+ uint32* as_ints = reinterpret_cast<uint32*>(&next_ipv6_.s6_addr);
+ as_ints[3] += 1;
+ return next_ip;
+ }
+ return IPAddress();
+}
+
+uint16 VirtualSocketServer::GetNextPort() {
+ uint16 port = next_port_;
+ if (next_port_ < kLastEphemeralPort) {
+ ++next_port_;
+ } else {
+ next_port_ = kFirstEphemeralPort;
+ }
+ return port;
+}
+
+Socket* VirtualSocketServer::CreateSocket(int type) {
+ return CreateSocketInternal(type);
+}
+
+AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) {
+ return CreateSocketInternal(type);
+}
+
+VirtualSocket* VirtualSocketServer::CreateSocketInternal(int type) {
+ return new VirtualSocket(this, type, true);
+}
+
+void VirtualSocketServer::SetMessageQueue(MessageQueue* msg_queue) {
+ msg_queue_ = msg_queue;
+ if (msg_queue_) {
+ msg_queue_->SignalQueueDestroyed.connect(this,
+ &VirtualSocketServer::OnMessageQueueDestroyed);
+ }
+}
+
+bool VirtualSocketServer::Wait(int cmsWait, bool process_io) {
+ ASSERT(msg_queue_ == Thread::Current());
+ if (stop_on_idle_ && Thread::Current()->empty()) {
+ return false;
+ }
+ return socketserver()->Wait(cmsWait, process_io);
+}
+
+void VirtualSocketServer::WakeUp() {
+ socketserver()->WakeUp();
+}
+
+bool VirtualSocketServer::ProcessMessagesUntilIdle() {
+ ASSERT(msg_queue_ == Thread::Current());
+ stop_on_idle_ = true;
+ while (!msg_queue_->empty()) {
+ Message msg;
+ if (msg_queue_->Get(&msg, kForever)) {
+ msg_queue_->Dispatch(&msg);
+ }
+ }
+ stop_on_idle_ = false;
+ return !msg_queue_->IsQuitting();
+}
+
+int VirtualSocketServer::Bind(VirtualSocket* socket,
+ const SocketAddress& addr) {
+ ASSERT(NULL != socket);
+ // Address must be completely specified at this point
+ ASSERT(!IPIsAny(addr.ipaddr()));
+ ASSERT(addr.port() != 0);
+
+ // Normalize the address (turns v6-mapped addresses into v4-addresses).
+ SocketAddress normalized(addr.ipaddr().Normalized(), addr.port());
+
+ AddressMap::value_type entry(normalized, socket);
+ return bindings_->insert(entry).second ? 0 : -1;
+}
+
+int VirtualSocketServer::Bind(VirtualSocket* socket, SocketAddress* addr) {
+ ASSERT(NULL != socket);
+
+ if (IPIsAny(addr->ipaddr())) {
+ addr->SetIP(GetNextIP(addr->ipaddr().family()));
+ } else {
+ addr->SetIP(addr->ipaddr().Normalized());
+ }
+
+ if (addr->port() == 0) {
+ for (int i = 0; i < kEphemeralPortCount; ++i) {
+ addr->SetPort(GetNextPort());
+ if (bindings_->find(*addr) == bindings_->end()) {
+ break;
+ }
+ }
+ }
+
+ return Bind(socket, *addr);
+}
+
+VirtualSocket* VirtualSocketServer::LookupBinding(const SocketAddress& addr) {
+ SocketAddress normalized(addr.ipaddr().Normalized(),
+ addr.port());
+ AddressMap::iterator it = bindings_->find(normalized);
+ return (bindings_->end() != it) ? it->second : NULL;
+}
+
+int VirtualSocketServer::Unbind(const SocketAddress& addr,
+ VirtualSocket* socket) {
+ SocketAddress normalized(addr.ipaddr().Normalized(),
+ addr.port());
+ ASSERT((*bindings_)[normalized] == socket);
+ bindings_->erase(bindings_->find(normalized));
+ return 0;
+}
+
+void VirtualSocketServer::AddConnection(const SocketAddress& local,
+ const SocketAddress& remote,
+ VirtualSocket* remote_socket) {
+ // Add this socket pair to our routing table. This will allow
+ // multiple clients to connect to the same server address.
+ SocketAddress local_normalized(local.ipaddr().Normalized(),
+ local.port());
+ SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+ remote.port());
+ SocketAddressPair address_pair(local_normalized, remote_normalized);
+ connections_->insert(std::pair<SocketAddressPair,
+ VirtualSocket*>(address_pair, remote_socket));
+}
+
+VirtualSocket* VirtualSocketServer::LookupConnection(
+ const SocketAddress& local,
+ const SocketAddress& remote) {
+ SocketAddress local_normalized(local.ipaddr().Normalized(),
+ local.port());
+ SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+ remote.port());
+ SocketAddressPair address_pair(local_normalized, remote_normalized);
+ ConnectionMap::iterator it = connections_->find(address_pair);
+ return (connections_->end() != it) ? it->second : NULL;
+}
+
+void VirtualSocketServer::RemoveConnection(const SocketAddress& local,
+ const SocketAddress& remote) {
+ SocketAddress local_normalized(local.ipaddr().Normalized(),
+ local.port());
+ SocketAddress remote_normalized(remote.ipaddr().Normalized(),
+ remote.port());
+ SocketAddressPair address_pair(local_normalized, remote_normalized);
+ connections_->erase(address_pair);
+}
+
+static double Random() {
+ return static_cast<double>(rand()) / RAND_MAX;
+}
+
+int VirtualSocketServer::Connect(VirtualSocket* socket,
+ const SocketAddress& remote_addr,
+ bool use_delay) {
+ uint32 delay = use_delay ? GetRandomTransitDelay() : 0;
+ VirtualSocket* remote = LookupBinding(remote_addr);
+ if (!CanInteractWith(socket, remote)) {
+ LOG(LS_INFO) << "Address family mismatch between "
+ << socket->GetLocalAddress() << " and " << remote_addr;
+ return -1;
+ }
+ if (remote != NULL) {
+ SocketAddress addr = socket->GetLocalAddress();
+ msg_queue_->PostDelayed(delay, remote, MSG_ID_CONNECT,
+ new MessageAddress(addr));
+ } else {
+ LOG(LS_INFO) << "No one listening at " << remote_addr;
+ msg_queue_->PostDelayed(delay, socket, MSG_ID_DISCONNECT);
+ }
+ return 0;
+}
+
+bool VirtualSocketServer::Disconnect(VirtualSocket* socket) {
+ if (socket) {
+ // Remove the mapping.
+ msg_queue_->Post(socket, MSG_ID_DISCONNECT);
+ return true;
+ }
+ return false;
+}
+
+int VirtualSocketServer::SendUdp(VirtualSocket* socket,
+ const char* data, size_t data_size,
+ const SocketAddress& remote_addr) {
+ // See if we want to drop this packet.
+ if (Random() < drop_prob_) {
+ LOG(LS_VERBOSE) << "Dropping packet: bad luck";
+ return static_cast<int>(data_size);
+ }
+
+ VirtualSocket* recipient = LookupBinding(remote_addr);
+ if (!recipient) {
+ // Make a fake recipient for address family checking.
+ scoped_ptr<VirtualSocket> dummy_socket(CreateSocketInternal(SOCK_DGRAM));
+ dummy_socket->SetLocalAddress(remote_addr);
+ if (!CanInteractWith(socket, dummy_socket.get())) {
+ LOG(LS_VERBOSE) << "Incompatible address families: "
+ << socket->GetLocalAddress() << " and " << remote_addr;
+ return -1;
+ }
+ LOG(LS_VERBOSE) << "No one listening at " << remote_addr;
+ return static_cast<int>(data_size);
+ }
+
+ if (!CanInteractWith(socket, recipient)) {
+ LOG(LS_VERBOSE) << "Incompatible address families: "
+ << socket->GetLocalAddress() << " and " << remote_addr;
+ return -1;
+ }
+
+ CritScope cs(&socket->crit_);
+
+ uint32 cur_time = Time();
+ PurgeNetworkPackets(socket, cur_time);
+
+ // 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.
+
+ size_t packet_size = data_size + UDP_HEADER_SIZE;
+ if (socket->network_size_ + packet_size > network_capacity_) {
+ LOG(LS_VERBOSE) << "Dropping packet: network capacity exceeded";
+ return static_cast<int>(data_size);
+ }
+
+ AddPacketToNetwork(socket, recipient, cur_time, data, data_size,
+ UDP_HEADER_SIZE, false);
+
+ return static_cast<int>(data_size);
+}
+
+void VirtualSocketServer::SendTcp(VirtualSocket* socket) {
+ // TCP can't send more data than will fill up the receiver's buffer.
+ // We track the data that is in the buffer plus data in flight using the
+ // recipient's recv_buffer_size_. Anything beyond that must be stored in the
+ // sender's buffer. We will trigger the buffered data to be sent when data
+ // is read from the recv_buffer.
+
+ // Lookup the local/remote pair in the connections table.
+ VirtualSocket* recipient = LookupConnection(socket->local_addr_,
+ socket->remote_addr_);
+ if (!recipient) {
+ LOG(LS_VERBOSE) << "Sending data to no one.";
+ return;
+ }
+
+ CritScope cs(&socket->crit_);
+
+ uint32 cur_time = Time();
+ PurgeNetworkPackets(socket, cur_time);
+
+ while (true) {
+ size_t available = recv_buffer_capacity_ - recipient->recv_buffer_size_;
+ size_t max_data_size = _min<size_t>(available, TCP_MSS - TCP_HEADER_SIZE);
+ size_t data_size = _min(socket->send_buffer_.size(), max_data_size);
+ if (0 == data_size)
+ break;
+
+ AddPacketToNetwork(socket, recipient, cur_time, &socket->send_buffer_[0],
+ data_size, TCP_HEADER_SIZE, true);
+ recipient->recv_buffer_size_ += data_size;
+
+ size_t new_buffer_size = socket->send_buffer_.size() - data_size;
+ // Avoid undefined access beyond the last element of the vector.
+ // This only happens when new_buffer_size is 0.
+ if (data_size < socket->send_buffer_.size()) {
+ // memmove is required for potentially overlapping source/destination.
+ memmove(&socket->send_buffer_[0], &socket->send_buffer_[data_size],
+ new_buffer_size);
+ }
+ socket->send_buffer_.resize(new_buffer_size);
+ }
+
+ if (socket->write_enabled_
+ && (socket->send_buffer_.size() < send_buffer_capacity_)) {
+ socket->write_enabled_ = false;
+ socket->SignalWriteEvent(socket);
+ }
+}
+
+void VirtualSocketServer::AddPacketToNetwork(VirtualSocket* sender,
+ VirtualSocket* recipient,
+ uint32 cur_time,
+ const char* data,
+ size_t data_size,
+ size_t header_size,
+ bool ordered) {
+ VirtualSocket::NetworkEntry entry;
+ entry.size = data_size + header_size;
+
+ sender->network_size_ += entry.size;
+ uint32 send_delay = SendDelay(sender->network_size_);
+ entry.done_time = cur_time + send_delay;
+ sender->network_.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)
+ Packet* p = new Packet(data, data_size, sender->local_addr_);
+ uint32 ts = TimeAfter(send_delay + transit_delay);
+ if (ordered) {
+ // Ensure that new packets arrive after previous ones
+ // TODO: consider ordering on a per-socket basis, since this
+ // introduces artifical delay.
+ ts = TimeMax(ts, network_delay_);
+ }
+ msg_queue_->PostAt(ts, recipient, MSG_ID_PACKET, p);
+ network_delay_ = TimeMax(ts, network_delay_);
+}
+
+void VirtualSocketServer::PurgeNetworkPackets(VirtualSocket* socket,
+ uint32 cur_time) {
+ while (!socket->network_.empty() &&
+ (socket->network_.front().done_time <= cur_time)) {
+ ASSERT(socket->network_size_ >= socket->network_.front().size);
+ socket->network_size_ -= socket->network_.front().size;
+ socket->network_.pop_front();
+ }
+}
+
+uint32 VirtualSocketServer::SendDelay(uint32 size) {
+ if (bandwidth_ == 0)
+ return 0;
+ else
+ return 1000 * size / bandwidth_;
+}
+
+#if 0
+void PrintFunction(std::vector<std::pair<double, double> >* f) {
+ return;
+ double sum = 0;
+ for (uint32 i = 0; i < f->size(); ++i) {
+ std::cout << (*f)[i].first << '\t' << (*f)[i].second << std::endl;
+ sum += (*f)[i].second;
+ }
+ if (!f->empty()) {
+ const double mean = sum / f->size();
+ double sum_sq_dev = 0;
+ for (uint32 i = 0; i < f->size(); ++i) {
+ double dev = (*f)[i].second - mean;
+ sum_sq_dev += dev * dev;
+ }
+ std::cout << "Mean = " << mean << " StdDev = "
+ << sqrt(sum_sq_dev / f->size()) << std::endl;
+ }
+}
+#endif // <unused>
+
+void VirtualSocketServer::UpdateDelayDistribution() {
+ Function* dist = CreateDistribution(delay_mean_, delay_stddev_,
+ delay_samples_);
+ // We take a lock just to make sure we don't leak memory.
+ {
+ CritScope cs(&delay_crit_);
+ delete delay_dist_;
+ delay_dist_ = dist;
+ }
+}
+
+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::CreateDistribution(
+ uint32 mean, uint32 stddev, uint32 samples) {
+ Function* f = new Function();
+
+ if (0 == stddev) {
+ f->push_back(Point(mean, 1.0));
+ } else {
+ double start = 0;
+ if (mean >= 4 * static_cast<double>(stddev))
+ start = mean - 4 * static_cast<double>(stddev);
+ double end = mean + 4 * static_cast<double>(stddev);
+
+ for (uint32 i = 0; i < samples; i++) {
+ double x = start + (end - start) * i / (samples - 1);
+ double y = Normal(x, mean, stddev);
+ f->push_back(Point(x, y));
+ }
+ }
+ return Resample(Invert(Accumulate(f)), 0, 1, samples);
+}
+
+uint32 VirtualSocketServer::GetRandomTransitDelay() {
+ size_t index = rand() % delay_dist_->size();
+ double delay = (*delay_dist_)[index].second;
+ //LOG_F(LS_INFO) << "random[" << index << "] = " << delay;
+ return static_cast<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 + 1].first - (*f)[i].first;
+ double avgy = ((*f)[i + 1].second + (*f)[i].second) / 2;
+ (*f)[i].second = v;
+ v = v + dx * avgy;
+ }
+ (*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, uint32 samples) {
+ Function* g = new Function();
+
+ for (size_t i = 0; i < samples; i++) {
+ double x = x1 + (x2 - x1) * i / (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);
+ }
+}
+
+bool VirtualSocketServer::CanInteractWith(VirtualSocket* local,
+ VirtualSocket* remote) {
+ if (!local || !remote) {
+ return false;
+ }
+ IPAddress local_ip = local->GetLocalAddress().ipaddr();
+ IPAddress remote_ip = remote->GetLocalAddress().ipaddr();
+ IPAddress local_normalized = local_ip.Normalized();
+ IPAddress remote_normalized = remote_ip.Normalized();
+ // Check if the addresses are the same family after Normalization (turns
+ // mapped IPv6 address into IPv4 addresses).
+ // This will stop unmapped V6 addresses from talking to mapped V6 addresses.
+ if (local_normalized.family() == remote_normalized.family()) {
+ return true;
+ }
+
+ // If ip1 is IPv4 and ip2 is :: and ip2 is not IPV6_V6ONLY.
+ int remote_v6_only = 0;
+ remote->GetOption(Socket::OPT_IPV6_V6ONLY, &remote_v6_only);
+ if (local_ip.family() == AF_INET && !remote_v6_only && IPIsAny(remote_ip)) {
+ return true;
+ }
+ // Same check, backwards.
+ int local_v6_only = 0;
+ local->GetOption(Socket::OPT_IPV6_V6ONLY, &local_v6_only);
+ if (remote_ip.family() == AF_INET && !local_v6_only && IPIsAny(local_ip)) {
+ return true;
+ }
+
+ // Check to see if either socket was explicitly bound to IPv6-any.
+ // These sockets can talk with anyone.
+ if (local_ip.family() == AF_INET6 && local->was_any()) {
+ return true;
+ }
+ if (remote_ip.family() == AF_INET6 && remote->was_any()) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace talk_base
diff --git a/talk/base/virtualsocketserver.h b/talk/base/virtualsocketserver.h
new file mode 100644
index 0000000..d35d586
--- /dev/null
+++ b/talk/base/virtualsocketserver.h
@@ -0,0 +1,247 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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;
+class SocketAddressPair;
+
+// 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, unless
+// they are bound to addresses from incompatible families.
+class VirtualSocketServer : public SocketServer, public sigslot::has_slots<> {
+ public:
+ // TODO: Add "owned" parameter.
+ // If "owned" is set, the supplied socketserver will be deleted later.
+ explicit VirtualSocketServer(SocketServer* ss);
+ virtual ~VirtualSocketServer();
+
+ SocketServer* socketserver() { return server_; }
+
+ // Limits the network bandwidth (maximum bytes per second). Zero means that
+ // all sends occur instantly. Defaults to 0.
+ uint32 bandwidth() const { return bandwidth_; }
+ void set_bandwidth(uint32 bandwidth) { bandwidth_ = bandwidth; }
+
+ // Limits the amount of data which can be in flight on the network without
+ // packet loss (on a per sender basis). Defaults to 64 KB.
+ uint32 network_capacity() const { return network_capacity_; }
+ void set_network_capacity(uint32 capacity) {
+ network_capacity_ = capacity;
+ }
+
+ // The amount of data which can be buffered by tcp on the sender's side
+ uint32 send_buffer_capacity() const { return send_buffer_capacity_; }
+ void set_send_buffer_capacity(uint32 capacity) {
+ send_buffer_capacity_ = capacity;
+ }
+
+ // The amount of data which can be buffered by tcp on the receiver's side
+ uint32 recv_buffer_capacity() const { return recv_buffer_capacity_; }
+ void set_recv_buffer_capacity(uint32 capacity) {
+ recv_buffer_capacity_ = 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. Defaults to no delay.
+ uint32 delay_mean() const { return delay_mean_; }
+ uint32 delay_stddev() const { return delay_stddev_; }
+ uint32 delay_samples() const { return delay_samples_; }
+ void set_delay_mean(uint32 delay_mean) { delay_mean_ = delay_mean; }
+ void set_delay_stddev(uint32 delay_stddev) {
+ delay_stddev_ = delay_stddev;
+ }
+ void set_delay_samples(uint32 delay_samples) {
+ delay_samples_ = delay_samples;
+ }
+
+ // 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 void SetMessageQueue(MessageQueue* queue);
+ virtual bool Wait(int cms, bool process_io);
+ virtual void WakeUp();
+
+ typedef std::pair<double, double> Point;
+ typedef std::vector<Point> Function;
+
+ static Function* CreateDistribution(uint32 mean, uint32 stddev,
+ uint32 samples);
+
+ // Similar to Thread::ProcessMessages, but it only processes messages until
+ // there are no immediate messages or pending network traffic. Returns false
+ // if Thread::Stop() was called.
+ bool ProcessMessagesUntilIdle();
+
+ protected:
+ // Returns a new IP not used before in this network.
+ IPAddress GetNextIP(int family);
+ uint16 GetNextPort();
+
+ VirtualSocket* CreateSocketInternal(int type);
+
+ // Binds the given socket to addr, assigning and IP and Port if necessary
+ int Bind(VirtualSocket* socket, SocketAddress* addr);
+
+ // Binds the given socket to the given (fully-defined) address.
+ int Bind(VirtualSocket* socket, const SocketAddress& addr);
+
+ // Find the socket bound to the given address
+ VirtualSocket* LookupBinding(const SocketAddress& addr);
+
+ int Unbind(const SocketAddress& addr, VirtualSocket* socket);
+
+ // Adds a mapping between this socket pair and the socket.
+ void AddConnection(const SocketAddress& client,
+ const SocketAddress& server,
+ VirtualSocket* socket);
+
+ // Find the socket pair corresponding to this server address.
+ VirtualSocket* LookupConnection(const SocketAddress& client,
+ const SocketAddress& server);
+
+ void RemoveConnection(const SocketAddress& client,
+ const SocketAddress& server);
+
+ // Connects the given socket to the socket at the given address
+ int Connect(VirtualSocket* socket, const SocketAddress& remote_addr,
+ bool use_delay);
+
+ // Sends a disconnect message to the socket at the given address
+ bool Disconnect(VirtualSocket* socket);
+
+ // Sends the given packet to the socket at the given address (if one exists).
+ int SendUdp(VirtualSocket* socket, const char* data, size_t data_size,
+ const SocketAddress& remote_addr);
+
+ // Moves as much data as possible from the sender's buffer to the network
+ void SendTcp(VirtualSocket* socket);
+
+ // Places a packet on the network.
+ void AddPacketToNetwork(VirtualSocket* socket, VirtualSocket* recipient,
+ uint32 cur_time, const char* data, size_t data_size,
+ size_t header_size, bool ordered);
+
+ // Removes stale packets from the network
+ void PurgeNetworkPackets(VirtualSocket* socket, uint32 cur_time);
+
+ // Computes the number of milliseconds required to send a packet of this size.
+ uint32 SendDelay(uint32 size);
+
+ // 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).
+ static Function* Accumulate(Function* f);
+ static Function* Invert(Function* f);
+ static Function* Resample(Function* f, double x1, double x2, uint32 samples);
+ static double Evaluate(Function* f, double x);
+
+ // NULL out our message queue if it goes away. Necessary in the case where
+ // our lifetime is greater than that of the thread we are using, since we
+ // try to send Close messages for all connected sockets when we shutdown.
+ void OnMessageQueueDestroyed() { msg_queue_ = NULL; }
+
+ // Determine if two sockets should be able to communicate.
+ // We don't (currently) specify an address family for sockets; instead,
+ // the currently bound address is used to infer the address family.
+ // Any socket that is not explicitly bound to an IPv4 address is assumed to be
+ // dual-stack capable.
+ // This function tests if two addresses can communicate, as well as the
+ // sockets to which they may be bound (the addresses may or may not yet be
+ // bound to the sockets).
+ // First the addresses are tested (after normalization):
+ // If both have the same family, then communication is OK.
+ // If only one is IPv4 then false, unless the other is bound to ::.
+ // This applies even if the IPv4 address is 0.0.0.0.
+ // The socket arguments are optional; the sockets are checked to see if they
+ // were explicitly bound to IPv6-any ('::'), and if so communication is
+ // permitted.
+ // NB: This scheme doesn't permit non-dualstack IPv6 sockets.
+ static bool CanInteractWith(VirtualSocket* local, VirtualSocket* remote);
+
+ private:
+ friend class VirtualSocket;
+
+ typedef std::map<SocketAddress, VirtualSocket*> AddressMap;
+ typedef std::map<SocketAddressPair, VirtualSocket*> ConnectionMap;
+
+ SocketServer* server_;
+ bool server_owned_;
+ MessageQueue* msg_queue_;
+ bool stop_on_idle_;
+ uint32 network_delay_;
+ in_addr next_ipv4_;
+ in6_addr next_ipv6_;
+ uint16 next_port_;
+ AddressMap* bindings_;
+ ConnectionMap* connections_;
+
+ uint32 bandwidth_;
+ uint32 network_capacity_;
+ uint32 send_buffer_capacity_;
+ uint32 recv_buffer_capacity_;
+ uint32 delay_mean_;
+ uint32 delay_stddev_;
+ uint32 delay_samples_;
+ Function* delay_dist_;
+ CriticalSection delay_crit_;
+
+ double drop_prob_;
+ DISALLOW_EVIL_CONSTRUCTORS(VirtualSocketServer);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_VIRTUALSOCKETSERVER_H_
diff --git a/talk/base/win32.cc b/talk/base/win32.cc
index 5e28aa9..3937781 100644
--- a/talk/base/win32.cc
+++ b/talk/base/win32.cc
@@ -26,13 +26,272 @@
*/
#include "talk/base/win32.h"
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
#include <algorithm>
#include "talk/base/basictypes.h"
#include "talk/base/common.h"
+#include "talk/base/logging.h"
namespace talk_base {
+// Helper function declarations for inet_ntop/inet_pton.
+static const char* inet_ntop_v4(const void* src, char* dst, socklen_t size);
+static const char* inet_ntop_v6(const void* src, char* dst, socklen_t size);
+static int inet_pton_v4(const char* src, void* dst);
+static int inet_pton_v6(const char* src, void* dst);
+
+// Implementation of inet_ntop (create a printable representation of an
+// ip address). XP doesn't have its own inet_ntop, and
+// WSAAddressToString requires both IPv6 to be installed and for Winsock
+// to be initialized.
+const char* win32_inet_ntop(int af, const void *src,
+ char* dst, socklen_t size) {
+ if (!src || !dst) {
+ return NULL;
+ }
+ switch (af) {
+ case AF_INET: {
+ return inet_ntop_v4(src, dst, size);
+ }
+ case AF_INET6: {
+ return inet_ntop_v6(src, dst, size);
+ }
+ }
+ return NULL;
+}
+
+// As above, but for inet_pton. Wraps inet_addr for v4, and implements inet_pton
+// for v6. Slightly more permissive than the RFC specified inet_pton, as it uses
+// windows' inet_addr which permits octal and hexadecimal values in v4
+// addresses, while inet_pton only allows decimal.
+// Note that our inet_ntop will output normal 'dotted' v4 addresses only.
+int win32_inet_pton(int af, const char* src, void* dst) {
+ if (!src || !dst) {
+ return 0;
+ }
+ if (af == AF_INET) {
+ return inet_pton_v4(src, dst);
+ } else if (af == AF_INET6) {
+ return inet_pton_v6(src, dst);
+ }
+ return -1;
+}
+
+// Helper function for inet_ntop for IPv4 addresses.
+// Outputs "dotted-quad" decimal notation.
+const char* inet_ntop_v4(const void* src, char* dst, socklen_t size) {
+ if (size < INET_ADDRSTRLEN) {
+ return NULL;
+ }
+ const struct in_addr* as_in_addr =
+ reinterpret_cast<const struct in_addr*>(src);
+ talk_base::sprintfn(dst, size, "%d.%d.%d.%d",
+ as_in_addr->S_un.S_un_b.s_b1,
+ as_in_addr->S_un.S_un_b.s_b2,
+ as_in_addr->S_un.S_un_b.s_b3,
+ as_in_addr->S_un.S_un_b.s_b4);
+ return dst;
+}
+
+// Helper function for inet_ntop for IPv6 addresses.
+const char* inet_ntop_v6(const void* src, char* dst, socklen_t size) {
+ if (size < INET6_ADDRSTRLEN) {
+ return NULL;
+ }
+ const uint16* as_shorts =
+ reinterpret_cast<const uint16*>(src);
+ int runpos[8];
+ int current = 1;
+ int max = 1;
+ int maxpos = -1;
+ int run_array_size = ARRAY_SIZE(runpos);
+ // Run over the address marking runs of 0s.
+ for (int i = 0; i < run_array_size; ++i) {
+ if (as_shorts[i] == 0) {
+ runpos[i] = current;
+ if (current > max) {
+ maxpos = i;
+ max = current;
+ }
+ ++current;
+ } else {
+ runpos[i] = -1;
+ current =1;
+ }
+ }
+
+ if (max > 1) {
+ int tmpmax = maxpos;
+ // Run back through, setting -1 for all but the longest run.
+ for (int i = run_array_size - 1; i >= 0; i--) {
+ if (i > tmpmax) {
+ runpos[i] = -1;
+ } else if (runpos[i] == -1) {
+ // We're less than maxpos, we hit a -1, so the 'good' run is done.
+ // Setting tmpmax -1 means all remaining positions get set to -1.
+ tmpmax = -1;
+ }
+ }
+ }
+
+ char* cursor = dst;
+ // Print IPv4 compatible and IPv4 mapped addresses using the IPv4 helper.
+ // These addresses have an initial run of either eight zero-bytes followed
+ // by 0xFFFF, or an initial run of ten zero-bytes.
+ if (runpos[0] == 1 && (maxpos == 5 ||
+ (maxpos == 4 && as_shorts[5] == 0xFFFF))) {
+ *cursor++ = ':';
+ *cursor++ = ':';
+ if (maxpos == 4) {
+ cursor += talk_base::sprintfn(cursor, INET6_ADDRSTRLEN - 2, "ffff:");
+ }
+ const struct in_addr* as_v4 =
+ reinterpret_cast<const struct in_addr*>(&(as_shorts[6]));
+ inet_ntop_v4(as_v4, cursor, (INET6_ADDRSTRLEN - (cursor - dst)));
+ } else {
+ for (int i = 0; i < run_array_size; ++i) {
+ if (runpos[i] == -1) {
+ cursor += talk_base::sprintfn(cursor,
+ INET6_ADDRSTRLEN - (cursor - dst),
+ "%x", ntohs(as_shorts[i]));
+ if (i != 7 && runpos[i + 1] != 1) {
+ *cursor++ = ':';
+ }
+ } else if (runpos[i] == 1) {
+ // Entered the run; print the colons and skip the run.
+ *cursor++ = ':';
+ *cursor++ = ':';
+ i += (max - 1);
+ }
+ }
+ }
+ return dst;
+}
+
+// Helper function for inet_pton for IPv4 addresses.
+// Uses win32's inet_addr.
+int inet_pton_v4(const char* src, void* dst) {
+ uint32 ip = inet_addr(src);
+ if (ip == 0xFFFFFFFF && strcmp(src, "255.255.255.255") != 0) {
+ return 0;
+ }
+ struct in_addr* dst_as_in_addr = reinterpret_cast<struct in_addr*>(dst);
+ dst_as_in_addr->s_addr = ip;
+ return 1;
+}
+
+// Helper function for inet_pton for IPv6 addresses.
+int inet_pton_v6(const char* src, void* dst) {
+ // sscanf will pick any other invalid chars up, but it parses 0xnnnn as hex.
+ // Check for literal x in the input string.
+ const char* readcursor = src;
+ char c = *readcursor++;
+ while (c) {
+ if (c == 'x') {
+ return 0;
+ }
+ c = *readcursor++;
+ }
+ readcursor = src;
+
+ struct in6_addr an_addr;
+ memset(&an_addr, 0, sizeof(an_addr));
+
+ uint16* addr_cursor = reinterpret_cast<uint16*>(&an_addr.s6_addr[0]);
+ uint16* addr_end = reinterpret_cast<uint16*>(&an_addr.s6_addr[16]);
+ bool seencompressed = false;
+
+ // Addresses that start with "::" (i.e., a run of initial zeros) or
+ // "::ffff:" can potentially be IPv4 mapped or compatibility addresses.
+ // These have dotted-style IPv4 addresses on the end (e.g. "::192.168.7.1").
+ if (*readcursor == ':' && *(readcursor+1) == ':' &&
+ *(readcursor + 2) != 0) {
+ // Check for periods, which we'll take as a sign of v4 addresses.
+ const char* addrstart = readcursor + 2;
+ if (talk_base::strchr(addrstart, ".")) {
+ const char* colon = talk_base::strchr(addrstart, "::");
+ if (colon) {
+ uint16 a_short;
+ int bytesread = 0;
+ if (sscanf(addrstart, "%hx%n", &a_short, &bytesread) != 1 ||
+ a_short != 0xFFFF || bytesread != 4) {
+ // Colons + periods means has to be ::ffff:a.b.c.d. But it wasn't.
+ return 0;
+ } else {
+ an_addr.s6_addr[10] = 0xFF;
+ an_addr.s6_addr[11] = 0xFF;
+ addrstart = colon + 1;
+ }
+ }
+ struct in_addr v4;
+ if (inet_pton_v4(addrstart, &v4.s_addr)) {
+ memcpy(&an_addr.s6_addr[12], &v4, sizeof(v4));
+ memcpy(dst, &an_addr, sizeof(an_addr));
+ return 1;
+ } else {
+ // Invalid v4 address.
+ return 0;
+ }
+ }
+ }
+
+ // For addresses without a trailing IPv4 component ('normal' IPv6 addresses).
+ while (*readcursor != 0 && addr_cursor < addr_end) {
+ if (*readcursor == ':') {
+ if (*(readcursor + 1) == ':') {
+ if (seencompressed) {
+ // Can only have one compressed run of zeroes ("::") per address.
+ return 0;
+ }
+ // Hit a compressed run. Count colons to figure out how much of the
+ // address is skipped.
+ readcursor += 2;
+ const char* coloncounter = readcursor;
+ int coloncount = 0;
+ if (*coloncounter == 0) {
+ // Special case - trailing ::.
+ addr_cursor = addr_end;
+ } else {
+ while (*coloncounter) {
+ if (*coloncounter == ':') {
+ ++coloncount;
+ }
+ ++coloncounter;
+ }
+ // (coloncount + 1) is the number of shorts left in the address.
+ addr_cursor = addr_end - (coloncount + 1);
+ seencompressed = true;
+ }
+ } else {
+ ++readcursor;
+ }
+ } else {
+ uint16 word;
+ int bytesread = 0;
+ if (sscanf(readcursor, "%hx%n", &word, &bytesread) != 1) {
+ return 0;
+ } else {
+ *addr_cursor = htons(word);
+ ++addr_cursor;
+ readcursor += bytesread;
+ if (*readcursor != ':' && *readcursor != '\0') {
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (*readcursor != '\0' || addr_cursor < addr_end) {
+ // Catches addresses too short or too long.
+ return 0;
+ }
+ memcpy(dst, &an_addr, sizeof(an_addr));
+ return 1;
+}
+
//
// Unix time is in seconds relative to 1/1/1970. So we compute the windows
// FILETIME of that time/date, then we add/subtract in appropriate units to
@@ -178,6 +437,4 @@
}
return ret;
}
-
} // namespace talk_base
-
diff --git a/talk/base/win32.h b/talk/base/win32.h
index 6274b0e..9c6ea3f 100644
--- a/talk/base/win32.h
+++ b/talk/base/win32.h
@@ -54,6 +54,9 @@
namespace talk_base {
+const char* win32_inet_ntop(int af, const void *src, char* dst, socklen_t size);
+int win32_inet_pton(int af, const char* src, void *dst);
+
///////////////////////////////////////////////////////////////////////////////
inline std::wstring ToUtf16(const char* utf8, size_t len) {
diff --git a/talk/base/win32_unittest.cc b/talk/base/win32_unittest.cc
new file mode 100644
index 0000000..4800e6e
--- /dev/null
+++ b/talk/base/win32_unittest.cc
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/win32.h"
+
+#ifndef WIN32
+#error Only for Windows
+#endif
+
+namespace talk_base {
+
+class Win32Test : public testing::Test {
+ public:
+ Win32Test() {
+ }
+};
+
+TEST_F(Win32Test, FileTimeToUInt64Test) {
+ FILETIME ft;
+ ft.dwHighDateTime = 0xBAADF00D;
+ ft.dwLowDateTime = 0xFEED3456;
+
+ uint64 expected = 0xBAADF00DFEED3456;
+ EXPECT_EQ(expected, ToUInt64(ft));
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32regkey.cc b/talk/base/win32regkey.cc
new file mode 100644
index 0000000..de74327
--- /dev/null
+++ b/talk/base/win32regkey.cc
@@ -0,0 +1,1119 @@
+/*
+ * libjingle
+ * Copyright 2003-2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Registry configuration wrapers class implementation
+//
+// Change made by S. Ganesh - ganesh@google.com:
+// Use SHQueryValueEx instead of RegQueryValueEx throughout.
+// A call to the SHLWAPI function is essentially a call to the standard
+// function but with post-processing:
+// * to fix REG_SZ or REG_EXPAND_SZ data that is not properly null-terminated;
+// * to expand REG_EXPAND_SZ data.
+
+#include "talk/base/win32regkey.h"
+
+#include <shlwapi.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+RegKey::RegKey() {
+ h_key_ = NULL;
+}
+
+RegKey::~RegKey() {
+ Close();
+}
+
+HRESULT RegKey::Create(HKEY parent_key, const wchar_t* key_name) {
+ return Create(parent_key,
+ key_name,
+ REG_NONE,
+ REG_OPTION_NON_VOLATILE,
+ KEY_ALL_ACCESS,
+ NULL,
+ NULL);
+}
+
+HRESULT RegKey::Open(HKEY parent_key, const wchar_t* key_name) {
+ return Open(parent_key, key_name, KEY_ALL_ACCESS);
+}
+
+bool RegKey::HasValue(const TCHAR* value_name) const {
+ return (ERROR_SUCCESS == ::RegQueryValueEx(h_key_, value_name, NULL,
+ NULL, NULL, NULL));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD value) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name, REG_DWORD, &value);
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD64 value) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name, REG_QWORD, &value);
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ float value) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name,
+ REG_BINARY, &value, sizeof(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ double value) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name,
+ REG_BINARY, &value, sizeof(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ const TCHAR* value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name,
+ REG_SZ, const_cast<wchar_t*>(value));
+}
+
+HRESULT RegKey::SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name, REG_BINARY,
+ const_cast<uint8*>(value), byte_count);
+}
+
+HRESULT RegKey::SetValueMultiSZ(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count) {
+ ASSERT(full_key_name != NULL);
+
+ return SetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ,
+ const_cast<uint8*>(value), byte_count);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD* value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ return GetValueStaticHelper(full_key_name, value_name, REG_DWORD, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD64* value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ return GetValueStaticHelper(full_key_name, value_name, REG_QWORD, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ float* value) {
+ ASSERT(value != NULL);
+ ASSERT(full_key_name != NULL);
+
+ DWORD byte_count = 0;
+ scoped_array<byte> buffer;
+ HRESULT hr = GetValueStaticHelper(full_key_name, value_name,
+ REG_BINARY, buffer.accept(), &byte_count);
+ if (SUCCEEDED(hr)) {
+ ASSERT(byte_count == sizeof(*value));
+ if (byte_count == sizeof(*value)) {
+ *value = *reinterpret_cast<float*>(buffer.get());
+ }
+ }
+ return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ double* value) {
+ ASSERT(value != NULL);
+ ASSERT(full_key_name != NULL);
+
+ DWORD byte_count = 0;
+ scoped_array<byte> buffer;
+ HRESULT hr = GetValueStaticHelper(full_key_name, value_name,
+ REG_BINARY, buffer.accept(), &byte_count);
+ if (SUCCEEDED(hr)) {
+ ASSERT(byte_count == sizeof(*value));
+ if (byte_count == sizeof(*value)) {
+ *value = *reinterpret_cast<double*>(buffer.get());
+ }
+ }
+ return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ wchar_t** value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ return GetValueStaticHelper(full_key_name, value_name, REG_SZ, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ std::wstring* value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ scoped_array<wchar_t> buffer;
+ HRESULT hr = RegKey::GetValue(full_key_name, value_name, buffer.accept());
+ if (SUCCEEDED(hr)) {
+ value->assign(buffer.get());
+ }
+ return hr;
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ std::vector<std::wstring>* value) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+
+ return GetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, value);
+}
+
+HRESULT RegKey::GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value != NULL);
+ ASSERT(byte_count != NULL);
+
+ return GetValueStaticHelper(full_key_name, value_name,
+ REG_BINARY, value, byte_count);
+}
+
+HRESULT RegKey::DeleteSubKey(const wchar_t* key_name) {
+ ASSERT(key_name != NULL);
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegDeleteKey(h_key_, key_name);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+ if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+ hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+ hr = S_FALSE;
+ }
+ return hr;
+}
+
+HRESULT RegKey::DeleteValue(const wchar_t* value_name) {
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegDeleteValue(h_key_, value_name);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+ if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+ hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+ hr = S_FALSE;
+ }
+ return hr;
+}
+
+HRESULT RegKey::Close() {
+ HRESULT hr = S_OK;
+ if (h_key_ != NULL) {
+ LONG res = ::RegCloseKey(h_key_);
+ hr = HRESULT_FROM_WIN32(res);
+ h_key_ = NULL;
+ }
+ return hr;
+}
+
+HRESULT RegKey::Create(HKEY parent_key,
+ const wchar_t* key_name,
+ wchar_t* lpszClass,
+ DWORD options,
+ REGSAM sam_desired,
+ LPSECURITY_ATTRIBUTES lpSecAttr,
+ LPDWORD lpdwDisposition) {
+ ASSERT(key_name != NULL);
+ ASSERT(parent_key != NULL);
+
+ DWORD dw = 0;
+ HKEY h_key = NULL;
+ LONG res = ::RegCreateKeyEx(parent_key, key_name, 0, lpszClass, options,
+ sam_desired, lpSecAttr, &h_key, &dw);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+
+ if (lpdwDisposition) {
+ *lpdwDisposition = dw;
+ }
+
+ // we have to close the currently opened key
+ // before replacing it with the new one
+ if (hr == S_OK) {
+ hr = Close();
+ ASSERT(hr == S_OK);
+ h_key_ = h_key;
+ }
+ return hr;
+}
+
+HRESULT RegKey::Open(HKEY parent_key,
+ const wchar_t* key_name,
+ REGSAM sam_desired) {
+ ASSERT(key_name != NULL);
+ ASSERT(parent_key != NULL);
+
+ HKEY h_key = NULL;
+ LONG res = ::RegOpenKeyEx(parent_key, key_name, 0, sam_desired, &h_key);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+
+ // we have to close the currently opened key
+ // before replacing it with the new one
+ if (hr == S_OK) {
+ // close the currently opened key if any
+ hr = Close();
+ ASSERT(hr == S_OK);
+ h_key_ = h_key;
+ }
+ return hr;
+}
+
+// save the key and all of its subkeys and values to a file
+HRESULT RegKey::Save(const wchar_t* full_key_name, const wchar_t* file_name) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(file_name != NULL);
+
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+ if (!h_key) {
+ return E_FAIL;
+ }
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, true);
+ LONG res = ::RegSaveKey(key.h_key_, file_name, NULL);
+ AdjustCurrentProcessPrivilege(SE_BACKUP_NAME, false);
+
+ return HRESULT_FROM_WIN32(res);
+}
+
+// restore the key and all of its subkeys and values which are saved into a file
+HRESULT RegKey::Restore(const wchar_t* full_key_name,
+ const wchar_t* file_name) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(file_name != NULL);
+
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+ if (!h_key) {
+ return E_FAIL;
+ }
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_WRITE);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, true);
+ LONG res = ::RegRestoreKey(key.h_key_, file_name, REG_FORCE_RESTORE);
+ AdjustCurrentProcessPrivilege(SE_RESTORE_NAME, false);
+
+ return HRESULT_FROM_WIN32(res);
+}
+
+// check if the current key has the specified subkey
+bool RegKey::HasSubkey(const wchar_t* key_name) const {
+ ASSERT(key_name != NULL);
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key_, key_name, KEY_READ);
+ key.Close();
+ return hr == S_OK;
+}
+
+// static flush key
+HRESULT RegKey::FlushKey(const wchar_t* full_key_name) {
+ ASSERT(full_key_name != NULL);
+
+ HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ LONG res = ::RegFlushKey(h_key);
+ hr = HRESULT_FROM_WIN32(res);
+ }
+ return hr;
+}
+
+// static SET helper
+HRESULT RegKey::SetValueStaticHelper(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD type,
+ LPVOID value,
+ DWORD byte_count) {
+ ASSERT(full_key_name != NULL);
+
+ HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ RegKey key;
+ hr = key.Create(h_key, key_name.c_str());
+ if (hr == S_OK) {
+ switch (type) {
+ case REG_DWORD:
+ hr = key.SetValue(value_name, *(static_cast<DWORD*>(value)));
+ break;
+ case REG_QWORD:
+ hr = key.SetValue(value_name, *(static_cast<DWORD64*>(value)));
+ break;
+ case REG_SZ:
+ hr = key.SetValue(value_name, static_cast<const wchar_t*>(value));
+ break;
+ case REG_BINARY:
+ hr = key.SetValue(value_name, static_cast<const uint8*>(value),
+ byte_count);
+ break;
+ case REG_MULTI_SZ:
+ hr = key.SetValue(value_name, static_cast<const uint8*>(value),
+ byte_count, type);
+ break;
+ default:
+ ASSERT(false);
+ hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+ break;
+ }
+ // close the key after writing
+ HRESULT temp_hr = key.Close();
+ if (hr == S_OK) {
+ hr = temp_hr;
+ }
+ }
+ }
+ return hr;
+}
+
+// static GET helper
+HRESULT RegKey::GetValueStaticHelper(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD type,
+ LPVOID value,
+ DWORD* byte_count) {
+ ASSERT(full_key_name != NULL);
+
+ HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ RegKey key;
+ hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+ if (hr == S_OK) {
+ switch (type) {
+ case REG_DWORD:
+ hr = key.GetValue(value_name, reinterpret_cast<DWORD*>(value));
+ break;
+ case REG_QWORD:
+ hr = key.GetValue(value_name, reinterpret_cast<DWORD64*>(value));
+ break;
+ case REG_SZ:
+ hr = key.GetValue(value_name, reinterpret_cast<wchar_t**>(value));
+ break;
+ case REG_MULTI_SZ:
+ hr = key.GetValue(value_name, reinterpret_cast<
+ std::vector<std::wstring>*>(value));
+ break;
+ case REG_BINARY:
+ hr = key.GetValue(value_name, reinterpret_cast<uint8**>(value),
+ byte_count);
+ break;
+ default:
+ ASSERT(false);
+ hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+ break;
+ }
+ // close the key after writing
+ HRESULT temp_hr = key.Close();
+ if (hr == S_OK) {
+ hr = temp_hr;
+ }
+ }
+ }
+ return hr;
+}
+
+// GET helper
+HRESULT RegKey::GetValueHelper(const wchar_t* value_name,
+ DWORD* type,
+ uint8** value,
+ DWORD* byte_count) const {
+ ASSERT(byte_count != NULL);
+ ASSERT(value != NULL);
+ ASSERT(type != NULL);
+
+ // init return buffer
+ *value = NULL;
+
+ // get the size of the return data buffer
+ LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, type, NULL, byte_count);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+
+ if (hr == S_OK) {
+ // if the value length is 0, nothing to do
+ if (*byte_count != 0) {
+ // allocate the buffer
+ *value = new byte[*byte_count];
+ ASSERT(*value != NULL);
+
+ // make the call again to get the data
+ res = ::SHQueryValueEx(h_key_, value_name, NULL,
+ type, *value, byte_count);
+ hr = HRESULT_FROM_WIN32(res);
+ ASSERT(hr == S_OK);
+ }
+ }
+ return hr;
+}
+
+// Int32 Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD* value) const {
+ ASSERT(value != NULL);
+
+ DWORD type = 0;
+ DWORD byte_count = sizeof(DWORD);
+ LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+ value, &byte_count);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+ ASSERT((hr != S_OK) || (type == REG_DWORD));
+ ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD)));
+ return hr;
+}
+
+// Int64 Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, DWORD64* value) const {
+ ASSERT(value != NULL);
+
+ DWORD type = 0;
+ DWORD byte_count = sizeof(DWORD64);
+ LONG res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+ value, &byte_count);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+ ASSERT((hr != S_OK) || (type == REG_QWORD));
+ ASSERT((hr != S_OK) || (byte_count == sizeof(DWORD64)));
+ return hr;
+}
+
+// String Get
+HRESULT RegKey::GetValue(const wchar_t* value_name, wchar_t** value) const {
+ ASSERT(value != NULL);
+
+ DWORD byte_count = 0;
+ DWORD type = 0;
+
+ // first get the size of the string buffer
+ LONG res = ::SHQueryValueEx(h_key_, value_name, NULL,
+ &type, NULL, &byte_count);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+
+ if (hr == S_OK) {
+ // allocate room for the string and a terminating \0
+ *value = new wchar_t[(byte_count / sizeof(wchar_t)) + 1];
+
+ if ((*value) != NULL) {
+ if (byte_count != 0) {
+ // make the call again
+ res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+ *value, &byte_count);
+ hr = HRESULT_FROM_WIN32(res);
+ } else {
+ (*value)[0] = L'\0';
+ }
+
+ ASSERT((hr != S_OK) || (type == REG_SZ) ||
+ (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ));
+ } else {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+
+ return hr;
+}
+
+// get a string value
+HRESULT RegKey::GetValue(const wchar_t* value_name, std::wstring* value) const {
+ ASSERT(value != NULL);
+
+ DWORD byte_count = 0;
+ DWORD type = 0;
+
+ // first get the size of the string buffer
+ LONG res = ::SHQueryValueEx(h_key_, value_name, NULL,
+ &type, NULL, &byte_count);
+ HRESULT hr = HRESULT_FROM_WIN32(res);
+
+ if (hr == S_OK) {
+ if (byte_count != 0) {
+ // Allocate some memory and make the call again
+ value->resize(byte_count / sizeof(wchar_t) + 1);
+ res = ::SHQueryValueEx(h_key_, value_name, NULL, &type,
+ &value->at(0), &byte_count);
+ hr = HRESULT_FROM_WIN32(res);
+ value->resize(wcslen(value->data()));
+ } else {
+ value->clear();
+ }
+
+ ASSERT((hr != S_OK) || (type == REG_SZ) ||
+ (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ));
+ }
+
+ return hr;
+}
+
+// convert REG_MULTI_SZ bytes to string array
+HRESULT RegKey::MultiSZBytesToStringArray(const uint8* buffer,
+ DWORD byte_count,
+ std::vector<std::wstring>* value) {
+ ASSERT(buffer != NULL);
+ ASSERT(value != NULL);
+
+ const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer);
+ DWORD data_len = byte_count / sizeof(wchar_t);
+ value->clear();
+ if (data_len > 1) {
+ // must be terminated by two null characters
+ if (data[data_len - 1] != 0 || data[data_len - 2] != 0) {
+ return E_INVALIDARG;
+ }
+
+ // put null-terminated strings into arrays
+ while (*data) {
+ std::wstring str(data);
+ value->push_back(str);
+ data += str.length() + 1;
+ }
+ }
+ return S_OK;
+}
+
+// get a std::vector<std::wstring> value from REG_MULTI_SZ type
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+ std::vector<std::wstring>* value) const {
+ ASSERT(value != NULL);
+
+ DWORD byte_count = 0;
+ DWORD type = 0;
+ uint8* buffer = 0;
+
+ // first get the size of the buffer
+ HRESULT hr = GetValueHelper(value_name, &type, &buffer, &byte_count);
+ ASSERT((hr != S_OK) || (type == REG_MULTI_SZ));
+
+ if (SUCCEEDED(hr)) {
+ hr = MultiSZBytesToStringArray(buffer, byte_count, value);
+ }
+
+ return hr;
+}
+
+// Binary data Get
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count) const {
+ ASSERT(byte_count != NULL);
+ ASSERT(value != NULL);
+
+ DWORD type = 0;
+ HRESULT hr = GetValueHelper(value_name, &type, value, byte_count);
+ ASSERT((hr != S_OK) || (type == REG_MULTI_SZ) || (type == REG_BINARY));
+ return hr;
+}
+
+// Raw data get
+HRESULT RegKey::GetValue(const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count,
+ DWORD*type) const {
+ ASSERT(type != NULL);
+ ASSERT(byte_count != NULL);
+ ASSERT(value != NULL);
+
+ return GetValueHelper(value_name, type, value, byte_count);
+}
+
+// Int32 set
+HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD value) const {
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_DWORD,
+ reinterpret_cast<const uint8*>(&value),
+ sizeof(DWORD));
+ return HRESULT_FROM_WIN32(res);
+}
+
+// Int64 set
+HRESULT RegKey::SetValue(const wchar_t* value_name, DWORD64 value) const {
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_QWORD,
+ reinterpret_cast<const uint8*>(&value),
+ sizeof(DWORD64));
+ return HRESULT_FROM_WIN32(res);
+}
+
+// String set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+ const wchar_t* value) const {
+ ASSERT(value != NULL);
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegSetValueEx(h_key_, value_name, NULL, REG_SZ,
+ reinterpret_cast<const uint8*>(value),
+ (lstrlen(value) + 1) * sizeof(wchar_t));
+ return HRESULT_FROM_WIN32(res);
+}
+
+// Binary data set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count) const {
+ ASSERT(h_key_ != NULL);
+
+ // special case - if 'value' is NULL make sure byte_count is zero
+ if (value == NULL) {
+ byte_count = 0;
+ }
+
+ LONG res = ::RegSetValueEx(h_key_, value_name, NULL,
+ REG_BINARY, value, byte_count);
+ return HRESULT_FROM_WIN32(res);
+}
+
+// Raw data set
+HRESULT RegKey::SetValue(const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count,
+ DWORD type) const {
+ ASSERT(value != NULL);
+ ASSERT(h_key_ != NULL);
+
+ LONG res = ::RegSetValueEx(h_key_, value_name, NULL, type, value, byte_count);
+ return HRESULT_FROM_WIN32(res);
+}
+
+bool RegKey::HasKey(const wchar_t* full_key_name) {
+ ASSERT(full_key_name != NULL);
+
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ RegKey key;
+ HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+ key.Close();
+ return S_OK == hr;
+ }
+ return false;
+}
+
+// static version of HasValue
+bool RegKey::HasValue(const wchar_t* full_key_name, const wchar_t* value_name) {
+ ASSERT(full_key_name != NULL);
+
+ bool has_value = false;
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ RegKey key;
+ if (key.Open(h_key, key_name.c_str(), KEY_READ) == S_OK) {
+ has_value = key.HasValue(value_name);
+ key.Close();
+ }
+ }
+ return has_value;
+}
+
+HRESULT RegKey::GetValueType(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD* value_type) {
+ ASSERT(full_key_name != NULL);
+ ASSERT(value_type != NULL);
+
+ *value_type = REG_NONE;
+
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+ if (SUCCEEDED(hr)) {
+ LONG res = ::SHQueryValueEx(key.h_key_, value_name, NULL, value_type,
+ NULL, NULL);
+ if (res != ERROR_SUCCESS) {
+ hr = HRESULT_FROM_WIN32(res);
+ }
+ }
+
+ return hr;
+}
+
+HRESULT RegKey::DeleteKey(const wchar_t* full_key_name) {
+ ASSERT(full_key_name != NULL);
+
+ return DeleteKey(full_key_name, true);
+}
+
+HRESULT RegKey::DeleteKey(const wchar_t* full_key_name, bool recursively) {
+ ASSERT(full_key_name != NULL);
+
+ // need to open the parent key first
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ // get the parent key
+ std::wstring parent_key(GetParentKeyInfo(&key_name));
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key, parent_key.c_str());
+
+ if (hr == S_OK) {
+ hr = recursively ? key.RecurseDeleteSubKey(key_name.c_str())
+ : key.DeleteSubKey(key_name.c_str());
+ } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
+ hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
+ hr = S_FALSE;
+ }
+
+ key.Close();
+ return hr;
+}
+
+HRESULT RegKey::DeleteValue(const wchar_t* full_key_name,
+ const wchar_t* value_name) {
+ ASSERT(full_key_name != NULL);
+
+ HRESULT hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+ // get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ if (h_key != NULL) {
+ RegKey key;
+ hr = key.Open(h_key, key_name.c_str());
+ if (hr == S_OK) {
+ hr = key.DeleteValue(value_name);
+ key.Close();
+ }
+ }
+ return hr;
+}
+
+HRESULT RegKey::RecurseDeleteSubKey(const wchar_t* key_name) {
+ ASSERT(key_name != NULL);
+
+ RegKey key;
+ HRESULT hr = key.Open(h_key_, key_name);
+
+ if (hr == S_OK) {
+ // enumerate all subkeys of this key and recursivelly delete them
+ FILETIME time = {0};
+ wchar_t key_name_buf[kMaxKeyNameChars] = {0};
+ DWORD key_name_buf_size = kMaxKeyNameChars;
+ while (hr == S_OK &&
+ ::RegEnumKeyEx(key.h_key_, 0, key_name_buf, &key_name_buf_size,
+ NULL, NULL, NULL, &time) == ERROR_SUCCESS) {
+ hr = key.RecurseDeleteSubKey(key_name_buf);
+
+ // restore the buffer size
+ key_name_buf_size = kMaxKeyNameChars;
+ }
+ // close the top key
+ key.Close();
+ }
+
+ if (hr == S_OK) {
+ // the key has no more children keys
+ // delete the key and all of its values
+ hr = DeleteSubKey(key_name);
+ }
+
+ return hr;
+}
+
+HKEY RegKey::GetRootKeyInfo(std::wstring* full_key_name) {
+ ASSERT(full_key_name != NULL);
+
+ HKEY h_key = NULL;
+ // get the root HKEY
+ int index = full_key_name->find(L'\\');
+ std::wstring root_key;
+
+ if (index == -1) {
+ root_key = *full_key_name;
+ *full_key_name = L"";
+ } else {
+ root_key = full_key_name->substr(0, index);
+ *full_key_name = full_key_name->substr(index + 1,
+ full_key_name->length() - index - 1);
+ }
+
+ for (std::wstring::iterator iter = root_key.begin();
+ iter != root_key.end(); ++iter) {
+ *iter = toupper(*iter);
+ }
+
+ if (!root_key.compare(L"HKLM") ||
+ !root_key.compare(L"HKEY_LOCAL_MACHINE")) {
+ h_key = HKEY_LOCAL_MACHINE;
+ } else if (!root_key.compare(L"HKCU") ||
+ !root_key.compare(L"HKEY_CURRENT_USER")) {
+ h_key = HKEY_CURRENT_USER;
+ } else if (!root_key.compare(L"HKU") ||
+ !root_key.compare(L"HKEY_USERS")) {
+ h_key = HKEY_USERS;
+ } else if (!root_key.compare(L"HKCR") ||
+ !root_key.compare(L"HKEY_CLASSES_ROOT")) {
+ h_key = HKEY_CLASSES_ROOT;
+ }
+
+ return h_key;
+}
+
+
+// Returns true if this key name is 'safe' for deletion
+// (doesn't specify a key root)
+bool RegKey::SafeKeyNameForDeletion(const wchar_t* key_name) {
+ ASSERT(key_name != NULL);
+ std::wstring key(key_name);
+
+ HKEY root_key = GetRootKeyInfo(&key);
+
+ if (!root_key) {
+ key = key_name;
+ }
+ if (key.empty()) {
+ return false;
+ }
+ bool found_subkey = false, backslash_found = false;
+ for (size_t i = 0 ; i < key.length() ; ++i) {
+ if (key[i] == L'\\') {
+ backslash_found = true;
+ } else if (backslash_found) {
+ found_subkey = true;
+ break;
+ }
+ }
+ return (root_key == HKEY_USERS) ? found_subkey : true;
+}
+
+std::wstring RegKey::GetParentKeyInfo(std::wstring* key_name) {
+ ASSERT(key_name != NULL);
+
+ // get the parent key
+ int index = key_name->rfind(L'\\');
+ std::wstring parent_key;
+ if (index == -1) {
+ parent_key = L"";
+ } else {
+ parent_key = key_name->substr(0, index);
+ *key_name = key_name->substr(index + 1, key_name->length() - index - 1);
+ }
+
+ return parent_key;
+}
+
+// get the number of values for this key
+uint32 RegKey::GetValueCount() {
+ DWORD num_values = 0;
+
+ LONG res = ::RegQueryInfoKey(
+ h_key_, // key handle
+ NULL, // buffer for class name
+ NULL, // size of class string
+ NULL, // reserved
+ NULL, // number of subkeys
+ NULL, // longest subkey size
+ NULL, // longest class string
+ &num_values, // number of values for this key
+ NULL, // longest value name
+ NULL, // longest value data
+ NULL, // security descriptor
+ NULL); // last write time
+
+ ASSERT(res == ERROR_SUCCESS);
+ return num_values;
+}
+
+// Enumerators for the value_names for this key
+
+// Called to get the value name for the given value name index
+// Use GetValueCount() to get the total value_name count for this key
+// Returns failure if no key at the specified index
+HRESULT RegKey::GetValueNameAt(int index, std::wstring* value_name,
+ DWORD* type) {
+ ASSERT(value_name != NULL);
+
+ LONG res = ERROR_SUCCESS;
+ wchar_t value_name_buf[kMaxValueNameChars] = {0};
+ DWORD value_name_buf_size = kMaxValueNameChars;
+ res = ::RegEnumValue(h_key_, index, value_name_buf, &value_name_buf_size,
+ NULL, type, NULL, NULL);
+
+ if (res == ERROR_SUCCESS) {
+ value_name->assign(value_name_buf);
+ }
+
+ return HRESULT_FROM_WIN32(res);
+}
+
+uint32 RegKey::GetSubkeyCount() {
+ // number of values for key
+ DWORD num_subkeys = 0;
+
+ LONG res = ::RegQueryInfoKey(
+ h_key_, // key handle
+ NULL, // buffer for class name
+ NULL, // size of class string
+ NULL, // reserved
+ &num_subkeys, // number of subkeys
+ NULL, // longest subkey size
+ NULL, // longest class string
+ NULL, // number of values for this key
+ NULL, // longest value name
+ NULL, // longest value data
+ NULL, // security descriptor
+ NULL); // last write time
+
+ ASSERT(res == ERROR_SUCCESS);
+ return num_subkeys;
+}
+
+HRESULT RegKey::GetSubkeyNameAt(int index, std::wstring* key_name) {
+ ASSERT(key_name != NULL);
+
+ LONG res = ERROR_SUCCESS;
+ wchar_t key_name_buf[kMaxKeyNameChars] = {0};
+ DWORD key_name_buf_size = kMaxKeyNameChars;
+
+ res = ::RegEnumKeyEx(h_key_, index, key_name_buf, &key_name_buf_size,
+ NULL, NULL, NULL, NULL);
+
+ if (res == ERROR_SUCCESS) {
+ key_name->assign(key_name_buf);
+ }
+
+ return HRESULT_FROM_WIN32(res);
+}
+
+// Is the key empty: having no sub-keys and values
+bool RegKey::IsKeyEmpty(const wchar_t* full_key_name) {
+ ASSERT(full_key_name != NULL);
+
+ bool is_empty = true;
+
+ // Get the root HKEY
+ std::wstring key_name(full_key_name);
+ HKEY h_key = GetRootKeyInfo(&key_name);
+
+ // Open the key to check
+ if (h_key != NULL) {
+ RegKey key;
+ HRESULT hr = key.Open(h_key, key_name.c_str(), KEY_READ);
+ if (SUCCEEDED(hr)) {
+ is_empty = key.GetSubkeyCount() == 0 && key.GetValueCount() == 0;
+ key.Close();
+ }
+ }
+
+ return is_empty;
+}
+
+bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable) {
+ ASSERT(privilege != NULL);
+
+ bool ret = false;
+ HANDLE token;
+ if (::OpenProcessToken(::GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) {
+ LUID luid;
+ memset(&luid, 0, sizeof(luid));
+ if (::LookupPrivilegeValue(NULL, privilege, &luid)) {
+ TOKEN_PRIVILEGES privs;
+ privs.PrivilegeCount = 1;
+ privs.Privileges[0].Luid = luid;
+ privs.Privileges[0].Attributes = to_enable ? SE_PRIVILEGE_ENABLED : 0;
+ if (::AdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, 0)) {
+ ret = true;
+ } else {
+ LOG_GLE(LS_ERROR) << "AdjustTokenPrivileges failed";
+ }
+ } else {
+ LOG_GLE(LS_ERROR) << "LookupPrivilegeValue failed";
+ }
+ CloseHandle(token);
+ } else {
+ LOG_GLE(LS_ERROR) << "OpenProcessToken(GetCurrentProcess) failed";
+ }
+
+ return ret;
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32regkey.h b/talk/base/win32regkey.h
new file mode 100644
index 0000000..9f01ce1
--- /dev/null
+++ b/talk/base/win32regkey.h
@@ -0,0 +1,354 @@
+/*
+ * libjingle
+ * Copyright 2003-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.
+ */
+
+// Registry configuration wrappers class
+//
+// Offers static functions for convenient
+// fast access for individual values
+//
+// Also provides a wrapper class for efficient
+// batch operations on values of a given registry key.
+//
+
+#ifndef TALK_BASE_WIN32REGKEY_H_
+#define TALK_BASE_WIN32REGKEY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+// maximum sizes registry key and value names
+const int kMaxKeyNameChars = 255 + 1;
+const int kMaxValueNameChars = 16383 + 1;
+
+class RegKey {
+ public:
+ // constructor
+ RegKey();
+
+ // destructor
+ ~RegKey();
+
+ // create a reg key
+ HRESULT Create(HKEY parent_key, const wchar_t* key_name);
+
+ HRESULT Create(HKEY parent_key,
+ const wchar_t* key_name,
+ wchar_t* reg_class,
+ DWORD options,
+ REGSAM sam_desired,
+ LPSECURITY_ATTRIBUTES lp_sec_attr,
+ LPDWORD lp_disposition);
+
+ // open an existing reg key
+ HRESULT Open(HKEY parent_key, const wchar_t* key_name);
+
+ HRESULT Open(HKEY parent_key, const wchar_t* key_name, REGSAM sam_desired);
+
+ // close this reg key
+ HRESULT Close();
+
+ // check if the key has a specified value
+ bool HasValue(const wchar_t* value_name) const;
+
+ // get the number of values for this key
+ uint32 GetValueCount();
+
+ // Called to get the value name for the given value name index
+ // Use GetValueCount() to get the total value_name count for this key
+ // Returns failure if no key at the specified index
+ // If you modify the key while enumerating, the indexes will be out of order.
+ // Since the index order is not guaranteed, you need to reset your counting
+ // loop.
+ // 'type' refers to REG_DWORD, REG_QWORD, etc..
+ // 'type' can be NULL if not interested in the value type
+ HRESULT GetValueNameAt(int index, std::wstring* value_name, DWORD* type);
+
+ // check if the current key has the specified subkey
+ bool HasSubkey(const wchar_t* key_name) const;
+
+ // get the number of subkeys for this key
+ uint32 GetSubkeyCount();
+
+ // Called to get the key name for the given key index
+ // Use GetSubkeyCount() to get the total count for this key
+ // Returns failure if no key at the specified index
+ // If you modify the key while enumerating, the indexes will be out of order.
+ // Since the index order is not guaranteed, you need to reset your counting
+ // loop.
+ HRESULT GetSubkeyNameAt(int index, std::wstring* key_name);
+
+ // SETTERS
+
+ // set an int32 value - use when reading multiple values from a key
+ HRESULT SetValue(const wchar_t* value_name, DWORD value) const;
+
+ // set an int64 value
+ HRESULT SetValue(const wchar_t* value_name, DWORD64 value) const;
+
+ // set a string value
+ HRESULT SetValue(const wchar_t* value_name, const wchar_t* value) const;
+
+ // set binary data
+ HRESULT SetValue(const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count) const;
+
+ // set raw data, including type
+ HRESULT SetValue(const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count,
+ DWORD type) const;
+
+ // GETTERS
+
+ // get an int32 value
+ HRESULT GetValue(const wchar_t* value_name, DWORD* value) const;
+
+ // get an int64 value
+ HRESULT GetValue(const wchar_t* value_name, DWORD64* value) const;
+
+ // get a string value - the caller must free the return buffer
+ HRESULT GetValue(const wchar_t* value_name, wchar_t** value) const;
+
+ // get a string value
+ HRESULT GetValue(const wchar_t* value_name, std::wstring* value) const;
+
+ // get a std::vector<std::wstring> value from REG_MULTI_SZ type
+ HRESULT GetValue(const wchar_t* value_name,
+ std::vector<std::wstring>* value) const;
+
+ // get binary data - the caller must free the return buffer
+ HRESULT GetValue(const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count) const;
+
+ // get raw data, including type - the caller must free the return buffer
+ HRESULT GetValue(const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count,
+ DWORD* type) const;
+
+ // STATIC VERSIONS
+
+ // flush
+ static HRESULT FlushKey(const wchar_t* full_key_name);
+
+ // check if a key exists
+ static bool HasKey(const wchar_t* full_key_name);
+
+ // check if the key has a specified value
+ static bool HasValue(const wchar_t* full_key_name, const wchar_t* value_name);
+
+ // SETTERS
+
+ // STATIC int32 set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD value);
+
+ // STATIC int64 set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD64 value);
+
+ // STATIC float set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ float value);
+
+ // STATIC double set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ double value);
+
+ // STATIC string set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ const wchar_t* value);
+
+ // STATIC binary data set
+ static HRESULT SetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ const uint8* value,
+ DWORD byte_count);
+
+ // STATIC multi-string set
+ static HRESULT SetValueMultiSZ(const wchar_t* full_key_name,
+ const TCHAR* value_name,
+ const uint8* value,
+ DWORD byte_count);
+
+ // GETTERS
+
+ // STATIC int32 get
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD* value);
+
+ // STATIC int64 get
+ //
+ // Note: if you are using time64 you should
+ // likely use GetLimitedTimeValue (util.h) instead of this method.
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD64* value);
+
+ // STATIC float get
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ float* value);
+
+ // STATIC double get
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ double* value);
+
+ // STATIC string get
+ // Note: the caller must free the return buffer for wchar_t* version
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ wchar_t** value);
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ std::wstring* value);
+
+ // STATIC REG_MULTI_SZ get
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ std::vector<std::wstring>* value);
+
+ // STATIC get binary data - the caller must free the return buffer
+ static HRESULT GetValue(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ uint8** value,
+ DWORD* byte_count);
+
+ // Get type of a registry value
+ static HRESULT GetValueType(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD* value_type);
+
+ // delete a subkey of the current key (with no subkeys)
+ HRESULT DeleteSubKey(const wchar_t* key_name);
+
+ // recursively delete a sub key of the current key (and all its subkeys)
+ HRESULT RecurseDeleteSubKey(const wchar_t* key_name);
+
+ // STATIC version of delete key - handles nested keys also
+ // delete a key and all its sub-keys recursively
+ // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+ // and failure otherwise.
+ static HRESULT DeleteKey(const wchar_t* full_key_name);
+
+ // STATIC version of delete key
+ // delete a key recursively or non-recursively
+ // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+ // and failure otherwise.
+ static HRESULT DeleteKey(const wchar_t* full_key_name, bool recursive);
+
+ // delete the specified value
+ HRESULT DeleteValue(const wchar_t* value_name);
+
+ // STATIC version of delete value
+ // Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
+ // and failure otherwise.
+ static HRESULT DeleteValue(const wchar_t* full_key_name,
+ const wchar_t* value_name);
+
+ // Peek inside (use a RegKey as a smart wrapper around a registry handle)
+ HKEY key() { return h_key_; }
+
+ // helper function to get the HKEY and the root key from a string
+ // modifies the argument in place and returns the key name
+ // e.g. HKLM\\Software\\Google\... returns HKLM, "Software\\Google\..."
+ // Necessary for the static versions that use the full name of the reg key
+ static HKEY GetRootKeyInfo(std::wstring* full_key_name);
+
+ // Returns true if this key name is 'safe' for deletion (doesn't specify a key
+ // root)
+ static bool SafeKeyNameForDeletion(const wchar_t* key_name);
+
+ // save the key and all of its subkeys and values to a file
+ static HRESULT Save(const wchar_t* full_key_name, const wchar_t* file_name);
+
+ // restore the key and all of its subkeys and values which are saved into a
+ // file
+ static HRESULT Restore(const wchar_t* full_key_name,
+ const wchar_t* file_name);
+
+ // Is the key empty: having no sub-keys and values
+ static bool IsKeyEmpty(const wchar_t* full_key_name);
+
+ private:
+
+ // helper function to get any value from the registry
+ // used when the size of the data is unknown
+ HRESULT GetValueHelper(const wchar_t* value_name,
+ DWORD* type, uint8** value,
+ DWORD* byte_count) const;
+
+ // helper function to get the parent key name and the subkey from a string
+ // modifies the argument in place and returns the key name
+ // Necessary for the static versions that use the full name of the reg key
+ static std::wstring GetParentKeyInfo(std::wstring* key_name);
+
+ // common SET Helper for the static case
+ static HRESULT SetValueStaticHelper(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD type,
+ LPVOID value,
+ DWORD byte_count = 0);
+
+ // common GET Helper for the static case
+ static HRESULT GetValueStaticHelper(const wchar_t* full_key_name,
+ const wchar_t* value_name,
+ DWORD type,
+ LPVOID value,
+ DWORD* byte_count = NULL);
+
+ // convert REG_MULTI_SZ bytes to string array
+ static HRESULT MultiSZBytesToStringArray(const uint8* buffer,
+ DWORD byte_count,
+ std::vector<std::wstring>* value);
+
+ // the HKEY for the current key
+ HKEY h_key_;
+
+ // for unittest
+ friend void RegKeyHelperFunctionsTest();
+
+ DISALLOW_EVIL_CONSTRUCTORS(RegKey);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WIN32REGKEY_H_
diff --git a/talk/base/win32regkey_unittest.cc b/talk/base/win32regkey_unittest.cc
new file mode 100644
index 0000000..1dd8fe4
--- /dev/null
+++ b/talk/base/win32regkey_unittest.cc
@@ -0,0 +1,607 @@
+/*
+ * libjingle
+ * Copyright 2003-2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Unittest for registry access API
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/win32regkey.h"
+
+namespace talk_base {
+
+#ifndef EXPECT_SUCCEEDED
+#define EXPECT_SUCCEEDED(x) EXPECT_TRUE(SUCCEEDED(x))
+#endif
+
+#ifndef EXPECT_FAILED
+#define EXPECT_FAILED(x) EXPECT_TRUE(FAILED(x))
+#endif
+
+#define kBaseKey L"Software\\Google\\__TEST"
+#define kSubkeyName L"subkey_test"
+
+const wchar_t kRkey1[] = kBaseKey;
+const wchar_t kRkey1SubkeyName[] = kSubkeyName;
+const wchar_t kRkey1Subkey[] = kBaseKey L"\\" kSubkeyName;
+const wchar_t kFullRkey1[] = L"HKCU\\" kBaseKey;
+const wchar_t kFullRkey1Subkey[] = L"HKCU\\" kBaseKey L"\\" kSubkeyName;
+
+const wchar_t kValNameInt[] = L"Int32 Value";
+const DWORD kIntVal = 20;
+const DWORD kIntVal2 = 30;
+
+const wchar_t kValNameInt64[] = L"Int64 Value";
+const DWORD64 kIntVal64 = 119600064000000000uI64;
+
+const wchar_t kValNameFloat[] = L"Float Value";
+const float kFloatVal = 12.3456789f;
+
+const wchar_t kValNameDouble[] = L"Double Value";
+const double kDoubleVal = 98.7654321;
+
+const wchar_t kValNameStr[] = L"Str Value";
+const wchar_t kStrVal[] = L"Some string data 1";
+const wchar_t kStrVal2[] = L"Some string data 2";
+
+const wchar_t kValNameBinary[] = L"Binary Value";
+const char kBinaryVal[] = "Some binary data abcdefghi 1";
+const char kBinaryVal2[] = "Some binary data abcdefghi 2";
+
+const wchar_t kValNameMultiStr[] = L"MultiStr Value";
+const wchar_t kMultiSZ[] = L"abc\0def\0P12345\0";
+const wchar_t kEmptyMultiSZ[] = L"";
+const wchar_t kInvalidMultiSZ[] = {L'6', L'7', L'8'};
+
+// friend function of RegKey
+void RegKeyHelperFunctionsTest() {
+ // Try out some dud values
+ std::wstring temp_key = L"";
+ EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL);
+ EXPECT_STREQ(temp_key.c_str(), L"");
+
+ temp_key = L"a";
+ EXPECT_TRUE(RegKey::GetRootKeyInfo(&temp_key) == NULL);
+ EXPECT_STREQ(temp_key.c_str(), L"");
+
+ // The basics
+ temp_key = L"HKLM\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKEY_LOCAL_MACHINE\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_LOCAL_MACHINE);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKCU\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKEY_CURRENT_USER\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CURRENT_USER);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKU\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKEY_USERS\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_USERS);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKCR\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"HKEY_CLASSES_ROOT\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ // Make sure it is case insensitive
+ temp_key = L"hkcr\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"hkey_CLASSES_ROOT\\a";
+ EXPECT_EQ(RegKey::GetRootKeyInfo(&temp_key), HKEY_CLASSES_ROOT);
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ //
+ // Test RegKey::GetParentKeyInfo
+ //
+
+ // dud cases
+ temp_key = L"";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+ EXPECT_STREQ(temp_key.c_str(), L"");
+
+ temp_key = L"a";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+ EXPECT_STREQ(temp_key.c_str(), L"a");
+
+ temp_key = L"a\\b";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"a");
+ EXPECT_STREQ(temp_key.c_str(), L"b");
+
+ temp_key = L"\\b";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(), L"");
+ EXPECT_STREQ(temp_key.c_str(), L"b");
+
+ // Some regular cases
+ temp_key = L"HKEY_CLASSES_ROOT\\moon";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(),
+ L"HKEY_CLASSES_ROOT");
+ EXPECT_STREQ(temp_key.c_str(), L"moon");
+
+ temp_key = L"HKEY_CLASSES_ROOT\\moon\\doggy";
+ EXPECT_STREQ(RegKey::GetParentKeyInfo(&temp_key).c_str(),
+ L"HKEY_CLASSES_ROOT\\moon");
+ EXPECT_STREQ(temp_key.c_str(), L"doggy");
+
+ //
+ // Test MultiSZBytesToStringArray
+ //
+
+ std::vector<std::wstring> result;
+ EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+ reinterpret_cast<const uint8*>(kMultiSZ), sizeof(kMultiSZ), &result));
+ EXPECT_EQ(result.size(), 3);
+ EXPECT_STREQ(result[0].c_str(), L"abc");
+ EXPECT_STREQ(result[1].c_str(), L"def");
+ EXPECT_STREQ(result[2].c_str(), L"P12345");
+
+ EXPECT_SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+ reinterpret_cast<const uint8*>(kEmptyMultiSZ),
+ sizeof(kEmptyMultiSZ), &result));
+ EXPECT_EQ(result.size(), 0);
+ EXPECT_FALSE(SUCCEEDED(RegKey::MultiSZBytesToStringArray(
+ reinterpret_cast<const uint8*>(kInvalidMultiSZ),
+ sizeof(kInvalidMultiSZ), &result)));
+}
+
+TEST(RegKeyTest, RegKeyHelperFunctionsTest) {
+ RegKeyHelperFunctionsTest();
+}
+
+TEST(RegKeyTest, RegKeyNonStaticFunctionsTest) {
+ DWORD int_val = 0;
+ DWORD64 int64_val = 0;
+ wchar_t* str_val = NULL;
+ uint8* binary_val = NULL;
+ DWORD uint8_count = 0;
+
+ // Just in case...
+ // make sure the no test key residue is left from previous aborted runs
+ RegKey::DeleteKey(kFullRkey1);
+
+ // initial state
+ RegKey r_key;
+ EXPECT_TRUE(r_key.key() == NULL);
+
+ // create a reg key
+ EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1));
+
+ // do the create twice - it should return the already created one
+ EXPECT_SUCCEEDED(r_key.Create(HKEY_CURRENT_USER, kRkey1));
+
+ // now do an open - should work just fine
+ EXPECT_SUCCEEDED(r_key.Open(HKEY_CURRENT_USER, kRkey1));
+
+ // get an in-existent value
+ EXPECT_EQ(r_key.GetValue(kValNameInt, &int_val),
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+ // set and get some values
+
+ // set an INT 32
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal));
+
+ // check that the value exists
+ EXPECT_TRUE(r_key.HasValue(kValNameInt));
+
+ // read it back
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val));
+ EXPECT_EQ(int_val, kIntVal);
+
+ // set it again!
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal2));
+
+ // read it again
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt, &int_val));
+ EXPECT_EQ(int_val, kIntVal2);
+
+ // delete the value
+ EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt));
+
+ // check that the value is gone
+ EXPECT_FALSE(r_key.HasValue(kValNameInt));
+
+ // set an INT 64
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64));
+
+ // check that the value exists
+ EXPECT_TRUE(r_key.HasValue(kValNameInt64));
+
+ // read it back
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameInt64, &int64_val));
+ EXPECT_EQ(int64_val, kIntVal64);
+
+ // delete the value
+ EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameInt64));
+
+ // check that the value is gone
+ EXPECT_FALSE(r_key.HasValue(kValNameInt64));
+
+ // set a string
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal));
+
+ // check that the value exists
+ EXPECT_TRUE(r_key.HasValue(kValNameStr));
+
+ // read it back
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val));
+ EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0);
+ delete[] str_val;
+
+ // set it again
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal2));
+
+ // read it again
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameStr, &str_val));
+ EXPECT_TRUE(lstrcmp(str_val, kStrVal2) == 0);
+ delete[] str_val;
+
+ // delete the value
+ EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameStr));
+
+ // check that the value is gone
+ EXPECT_FALSE(r_key.HasValue(kValNameInt));
+
+ // set a binary value
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+ reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal) - 1));
+
+ // check that the value exists
+ EXPECT_TRUE(r_key.HasValue(kValNameBinary));
+
+ // read it back
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count));
+ EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal) - 1) == 0);
+ delete[] binary_val;
+
+ // set it again
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+ reinterpret_cast<const uint8*>(kBinaryVal2), sizeof(kBinaryVal) - 1));
+
+ // read it again
+ EXPECT_SUCCEEDED(r_key.GetValue(kValNameBinary, &binary_val, &uint8_count));
+ EXPECT_TRUE(memcmp(binary_val, kBinaryVal2, sizeof(kBinaryVal2) - 1) == 0);
+ delete[] binary_val;
+
+ // delete the value
+ EXPECT_SUCCEEDED(r_key.DeleteValue(kValNameBinary));
+
+ // check that the value is gone
+ EXPECT_FALSE(r_key.HasValue(kValNameBinary));
+
+ // set some values and check the total count
+
+ // set an INT 32
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt, kIntVal));
+
+ // set an INT 64
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameInt64, kIntVal64));
+
+ // set a string
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameStr, kStrVal));
+
+ // set a binary value
+ EXPECT_SUCCEEDED(r_key.SetValue(kValNameBinary,
+ reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal) - 1));
+
+ // get the value count
+ uint32 value_count = r_key.GetValueCount();
+ EXPECT_EQ(value_count, 4);
+
+ // check the value names
+ std::wstring value_name;
+ DWORD type = 0;
+
+ EXPECT_SUCCEEDED(r_key.GetValueNameAt(0, &value_name, &type));
+ EXPECT_STREQ(value_name.c_str(), kValNameInt);
+ EXPECT_EQ(type, REG_DWORD);
+
+ EXPECT_SUCCEEDED(r_key.GetValueNameAt(1, &value_name, &type));
+ EXPECT_STREQ(value_name.c_str(), kValNameInt64);
+ EXPECT_EQ(type, REG_QWORD);
+
+ EXPECT_SUCCEEDED(r_key.GetValueNameAt(2, &value_name, &type));
+ EXPECT_STREQ(value_name.c_str(), kValNameStr);
+ EXPECT_EQ(type, REG_SZ);
+
+ EXPECT_SUCCEEDED(r_key.GetValueNameAt(3, &value_name, &type));
+ EXPECT_STREQ(value_name.c_str(), kValNameBinary);
+ EXPECT_EQ(type, REG_BINARY);
+
+ // check that there are no more values
+ EXPECT_FAILED(r_key.GetValueNameAt(4, &value_name, &type));
+
+ uint32 subkey_count = r_key.GetSubkeyCount();
+ EXPECT_EQ(subkey_count, 0);
+
+ // now create a subkey and make sure we can get the name
+ RegKey temp_key;
+ EXPECT_SUCCEEDED(temp_key.Create(HKEY_CURRENT_USER, kRkey1Subkey));
+
+ // check the subkey exists
+ EXPECT_TRUE(r_key.HasSubkey(kRkey1SubkeyName));
+
+ // check the name
+ EXPECT_EQ(r_key.GetSubkeyCount(), 1);
+
+ std::wstring subkey_name;
+ EXPECT_SUCCEEDED(r_key.GetSubkeyNameAt(0, &subkey_name));
+ EXPECT_STREQ(subkey_name.c_str(), kRkey1SubkeyName);
+
+ // delete the key
+ EXPECT_SUCCEEDED(r_key.DeleteSubKey(kRkey1));
+
+ // close this key
+ EXPECT_SUCCEEDED(r_key.Close());
+
+ // whack the whole key
+ EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1));
+}
+
+TEST(RegKeyTest, RegKeyStaticFunctionsTest) {
+ DWORD int_val = 0;
+ DWORD64 int64_val = 0;
+ float float_val = 0;
+ double double_val = 0;
+ wchar_t* str_val = NULL;
+ std::wstring wstr_val;
+ uint8* binary_val = NULL;
+ DWORD uint8_count = 0;
+
+ // Just in case...
+ // make sure the no test key residue is left from previous aborted runs
+ RegKey::DeleteKey(kFullRkey1);
+
+ // get an in-existent value from an un-existent key
+ EXPECT_EQ(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val),
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+ // set int32
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt, kIntVal));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt));
+
+ // get an in-existent value from an existent key
+ EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &int_val),
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt, &int_val));
+ EXPECT_EQ(int_val, kIntVal);
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt));
+
+ // set int64
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameInt64, kIntVal64));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameInt64));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameInt64, &int64_val));
+ EXPECT_EQ(int64_val, kIntVal64);
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameInt64));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameInt64));
+
+ // set float
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameFloat, kFloatVal));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameFloat));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val));
+ EXPECT_EQ(float_val, kFloatVal);
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameFloat));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameFloat));
+ EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameFloat, &float_val));
+
+ // set double
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameDouble, kDoubleVal));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameDouble));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val));
+ EXPECT_EQ(double_val, kDoubleVal);
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameDouble));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameDouble));
+ EXPECT_FAILED(RegKey::GetValue(kFullRkey1, kValNameDouble, &double_val));
+
+ // set string
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameStr, kStrVal));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameStr));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &str_val));
+ EXPECT_TRUE(lstrcmp(str_val, kStrVal) == 0);
+ delete[] str_val;
+
+ // read it back in std::wstring
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameStr, &wstr_val));
+ EXPECT_STREQ(wstr_val.c_str(), kStrVal);
+
+ // get an in-existent value from an existent key
+ EXPECT_EQ(RegKey::GetValue(kFullRkey1, L"bogus", &str_val),
+ HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameStr));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameStr));
+
+ // set binary
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary,
+ reinterpret_cast<const uint8*>(kBinaryVal), sizeof(kBinaryVal)-1));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+ &binary_val, &uint8_count));
+ EXPECT_TRUE(memcmp(binary_val, kBinaryVal, sizeof(kBinaryVal)-1) == 0);
+ delete[] binary_val;
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // special case - set a binary value with length 0
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary,
+ reinterpret_cast<const uint8*>(kBinaryVal), 0));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+ &binary_val, &uint8_count));
+ EXPECT_EQ(uint8_count, 0);
+ EXPECT_TRUE(binary_val == NULL);
+ delete[] binary_val;
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // special case - set a NULL binary value
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1, kValNameBinary, NULL, 100));
+
+ // check that the value exists
+ EXPECT_TRUE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // read it back
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameBinary,
+ &binary_val, &uint8_count));
+ EXPECT_EQ(uint8_count, 0);
+ EXPECT_TRUE(binary_val == NULL);
+ delete[] binary_val;
+
+ // delete the value
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1, kValNameBinary));
+
+ // check that the value is gone
+ EXPECT_FALSE(RegKey::HasValue(kFullRkey1, kValNameBinary));
+
+ // test read/write REG_MULTI_SZ value
+ std::vector<std::wstring> result;
+ EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+ reinterpret_cast<const uint8*>(kMultiSZ), sizeof(kMultiSZ)));
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+ EXPECT_EQ(result.size(), 3);
+ EXPECT_STREQ(result[0].c_str(), L"abc");
+ EXPECT_STREQ(result[1].c_str(), L"def");
+ EXPECT_STREQ(result[2].c_str(), L"P12345");
+ EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+ reinterpret_cast<const uint8*>(kEmptyMultiSZ), sizeof(kEmptyMultiSZ)));
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+ EXPECT_EQ(result.size(), 0);
+ // writing REG_MULTI_SZ value will automatically add ending null characters
+ EXPECT_SUCCEEDED(RegKey::SetValueMultiSZ(kFullRkey1, kValNameMultiStr,
+ reinterpret_cast<const uint8*>(kInvalidMultiSZ), sizeof(kInvalidMultiSZ)));
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1, kValNameMultiStr, &result));
+ EXPECT_EQ(result.size(), 1);
+ EXPECT_STREQ(result[0].c_str(), L"678");
+
+ // Run the following test only in dev machine
+ // This is because the build machine might not have admin privilege
+#ifdef IS_PRIVATE_BUILD
+ // get a temp file name
+ wchar_t temp_path[MAX_PATH] = {0};
+ EXPECT_LT(::GetTempPath(ARRAY_SIZE(temp_path), temp_path),
+ static_cast<DWORD>(ARRAY_SIZE(temp_path)));
+ wchar_t temp_file[MAX_PATH] = {0};
+ EXPECT_NE(::GetTempFileName(temp_path, L"rkut_",
+ ::GetTickCount(), temp_file), 0);
+
+ // test save
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt, kIntVal));
+ EXPECT_SUCCEEDED(RegKey::SetValue(kFullRkey1Subkey, kValNameInt64, kIntVal64));
+ EXPECT_SUCCEEDED(RegKey::Save(kFullRkey1Subkey, temp_file));
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt));
+ EXPECT_SUCCEEDED(RegKey::DeleteValue(kFullRkey1Subkey, kValNameInt64));
+
+ // test restore
+ EXPECT_SUCCEEDED(RegKey::Restore(kFullRkey1Subkey, temp_file));
+ int_val = 0;
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey, kValNameInt, &int_val));
+ EXPECT_EQ(int_val, kIntVal);
+ int64_val = 0;
+ EXPECT_SUCCEEDED(RegKey::GetValue(kFullRkey1Subkey,
+ kValNameInt64,
+ &int64_val));
+ EXPECT_EQ(int64_val, kIntVal64);
+
+ // delete the temp file
+ EXPECT_EQ(TRUE, ::DeleteFile(temp_file));
+#endif
+
+ // whack the whole key
+ EXPECT_SUCCEEDED(RegKey::DeleteKey(kFullRkey1));
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32socketserver.cc b/talk/base/win32socketserver.cc
index 0915aaf..90bb010 100644
--- a/talk/base/win32socketserver.cc
+++ b/talk/base/win32socketserver.cc
@@ -168,7 +168,7 @@
virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT& result);
- virtual void OnFinalMessage(HWND hWnd);
+ virtual void OnNcDestroy();
private:
bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result);
@@ -226,8 +226,13 @@
return true;
}
-void Win32Socket::EventSink::OnFinalMessage(HWND hWnd) {
- delete this;
+void Win32Socket::EventSink::OnNcDestroy() {
+ if (parent_) {
+ LOG(LS_ERROR) << "EventSink hwnd is being destroyed, but the event sink"
+ " hasn't yet been disposed.";
+ } else {
+ delete this;
+ }
}
/////////////////////////////////////////////////////////////////////////////
@@ -741,7 +746,10 @@
// was a message for the dialog that it handled internally.
// Otherwise, dispatch as usual via Translate/DispatchMessage.
b = GetMessage(&msg, NULL, 0, 0);
- if (b) {
+ if (b == -1) {
+ LOG_GLE(LS_ERROR) << "GetMessage failed.";
+ return false;
+ } else if(b) {
if (!hdlg_ || !IsDialogMessage(hdlg_, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
diff --git a/talk/base/win32socketserver_unittest.cc b/talk/base/win32socketserver_unittest.cc
new file mode 100644
index 0000000..8531861
--- /dev/null
+++ b/talk/base/win32socketserver_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2009 Google Inc. All Rights Reserved.
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/socket_unittest.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32socketserver.h"
+
+namespace talk_base {
+
+// Test that Win32SocketServer::Wait works as expected.
+TEST(Win32SocketServerTest, TestWait) {
+ Win32SocketServer server(NULL);
+ uint32 start = Time();
+ server.Wait(1000, true);
+ EXPECT_GE(TimeSince(start), 1000);
+}
+
+// Test that Win32Socket::Pump does not touch general Windows messages.
+TEST(Win32SocketServerTest, TestPump) {
+ Win32SocketServer server(NULL);
+ SocketServerScope scope(&server);
+ EXPECT_EQ(TRUE, PostMessage(NULL, WM_USER, 999, 0));
+ server.Pump();
+ MSG msg;
+ EXPECT_EQ(TRUE, PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
+ EXPECT_EQ(WM_USER, msg.message);
+ EXPECT_EQ(999, msg.wParam);
+}
+
+// Test that Win32Socket passes all the generic Socket tests.
+class Win32SocketTest : public SocketTest {
+ protected:
+ Win32SocketTest() : server_(NULL), scope_(&server_) {}
+ Win32SocketServer server_;
+ SocketServerScope scope_;
+};
+
+TEST_F(Win32SocketTest, TestConnect) {
+ SocketTest::TestConnect();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookup) {
+ SocketTest::TestConnectWithDnsLookup();
+}
+
+TEST_F(Win32SocketTest, TestConnectFail) {
+ SocketTest::TestConnectFail();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithDnsLookupFail) {
+ SocketTest::TestConnectWithDnsLookupFail();
+}
+
+TEST_F(Win32SocketTest, TestConnectWithClosedSocket) {
+ SocketTest::TestConnectWithClosedSocket();
+}
+
+TEST_F(Win32SocketTest, TestServerCloseDuringConnect) {
+ SocketTest::TestServerCloseDuringConnect();
+}
+
+TEST_F(Win32SocketTest, TestClientCloseDuringConnect) {
+ SocketTest::TestClientCloseDuringConnect();
+}
+
+TEST_F(Win32SocketTest, TestServerClose) {
+ SocketTest::TestServerClose();
+}
+
+TEST_F(Win32SocketTest, TestCloseInClosedCallback) {
+ SocketTest::TestCloseInClosedCallback();
+}
+
+TEST_F(Win32SocketTest, TestSocketServerWait) {
+ SocketTest::TestSocketServerWait();
+}
+
+TEST_F(Win32SocketTest, TestTcp) {
+ SocketTest::TestTcp();
+}
+
+TEST_F(Win32SocketTest, TestUdp) {
+ SocketTest::TestUdp();
+}
+
+TEST_F(Win32SocketTest, TestGetSetOptions) {
+ SocketTest::TestGetSetOptions();
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32toolhelp.h b/talk/base/win32toolhelp.h
new file mode 100644
index 0000000..64a191a
--- /dev/null
+++ b/talk/base/win32toolhelp.h
@@ -0,0 +1,166 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_BASE_WIN32TOOLHELP_H_
+#define TALK_BASE_WIN32TOOLHELP_H_
+
+#ifndef WIN32
+#error WIN32 Only
+#endif
+
+#include "talk/base/win32.h"
+
+// Should be included first, but that causes redefinitions.
+#include <tlhelp32.h>
+
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+// The toolhelp api used to enumerate processes and their modules
+// on Windows is very repetetive and clunky to use. This little
+// template wraps it to make it a little more programmer friendly.
+//
+// Traits: Traits type that adapts the enumerator to the corresponding
+// win32 toolhelp api. Each traits class need to:
+// - define the type of the enumerated data as a public symbol Type
+//
+// - implement bool First(HANDLE, T*) normally calls a
+// Xxxx32First method in the toolhelp API. Ex Process32First(...)
+//
+// - implement bool Next(HANDLE, T*) normally calls a
+// Xxxx32Next method in the toolhelp API. Ex Process32Next(...)
+//
+// - implement bool CloseHandle(HANDLE)
+//
+template<typename Traits>
+class ToolhelpEnumeratorBase {
+ public:
+ ToolhelpEnumeratorBase(HANDLE snapshot)
+ : snapshot_(snapshot), broken_(false), first_(true) {
+
+ // Clear out the Traits::Type structure instance.
+ Zero(¤t_);
+ }
+
+ virtual ~ToolhelpEnumeratorBase() {
+ Close();
+ }
+
+ // Moves forward to the next object using the First and Next
+ // pointers. If either First or Next ever indicates an failure
+ // all subsequent calls to this method will fail; the enumerator
+ // object is considered broken.
+ bool Next() {
+ if (!Valid()) {
+ return false;
+ }
+
+ // Move the iteration forward.
+ current_.dwSize = sizeof(typename Traits::Type);
+ bool incr_ok = false;
+ if (first_) {
+ incr_ok = Traits::First(snapshot_, ¤t_);
+ first_ = false;
+ } else {
+ incr_ok = Traits::Next(snapshot_, ¤t_);
+ }
+
+ if (!incr_ok) {
+ Zero(¤t_);
+ broken_ = true;
+ }
+
+ return incr_ok;
+ }
+
+ const typename Traits::Type& current() const {
+ return current_;
+ }
+
+ void Close() {
+ if (snapshot_ != INVALID_HANDLE_VALUE) {
+ Traits::CloseHandle(snapshot_);
+ snapshot_ = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ private:
+ // Checks the state of the snapshot handle.
+ bool Valid() {
+ return snapshot_ != INVALID_HANDLE_VALUE && !broken_;
+ }
+
+ static void Zero(typename Traits::Type* buff) {
+ ZeroMemory(buff, sizeof(typename Traits::Type));
+ }
+
+ HANDLE snapshot_;
+ typename Traits::Type current_;
+ bool broken_;
+ bool first_;
+};
+
+class ToolhelpTraits {
+ public:
+ static HANDLE CreateSnapshot(uint32 flags, uint32 process_id) {
+ return CreateToolhelp32Snapshot(flags, process_id);
+ }
+
+ static bool CloseHandle(HANDLE handle) {
+ return ::CloseHandle(handle) == TRUE;
+ }
+};
+
+class ToolhelpProcessTraits : public ToolhelpTraits {
+ public:
+ typedef PROCESSENTRY32 Type;
+
+ static bool First(HANDLE handle, Type* t) {
+ return ::Process32First(handle, t) == TRUE;
+ }
+
+ static bool Next(HANDLE handle, Type* t) {
+ return ::Process32Next(handle, t) == TRUE;
+ }
+};
+
+class ProcessEnumerator : public ToolhelpEnumeratorBase<ToolhelpProcessTraits> {
+ public:
+ ProcessEnumerator()
+ : ToolhelpEnumeratorBase(
+ ToolhelpProcessTraits::CreateSnapshot(TH32CS_SNAPPROCESS, 0)) {
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(ProcessEnumerator);
+};
+
+class ToolhelpModuleTraits : public ToolhelpTraits {
+ public:
+ typedef MODULEENTRY32 Type;
+
+ static bool First(HANDLE handle, Type* t) {
+ return ::Module32First(handle, t) == TRUE;
+ }
+
+ static bool Next(HANDLE handle, Type* t) {
+ return ::Module32Next(handle, t) == TRUE;
+ }
+};
+
+class ModuleEnumerator : public ToolhelpEnumeratorBase<ToolhelpModuleTraits> {
+ public:
+ explicit ModuleEnumerator(uint32 process_id)
+ : ToolhelpEnumeratorBase(
+ ToolhelpModuleTraits::CreateSnapshot(TH32CS_SNAPMODULE,
+ process_id)) {
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(ModuleEnumerator);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WIN32TOOLHELP_H_
diff --git a/talk/base/win32toolhelp_unittest.cc b/talk/base/win32toolhelp_unittest.cc
new file mode 100644
index 0000000..529bef9
--- /dev/null
+++ b/talk/base/win32toolhelp_unittest.cc
@@ -0,0 +1,296 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/win32toolhelp.h"
+
+namespace talk_base {
+
+typedef struct {
+ // Required to match the toolhelp api struct 'design'.
+ DWORD dwSize;
+ int a;
+ uint32 b;
+} TestData;
+
+class Win32ToolhelpTest : public testing::Test {
+ public:
+ Win32ToolhelpTest() {
+ }
+
+ HANDLE AsHandle() {
+ return reinterpret_cast<HANDLE>(this);
+ }
+
+ static Win32ToolhelpTest* AsFixture(HANDLE handle) {
+ return reinterpret_cast<Win32ToolhelpTest*>(handle);
+ }
+
+ static bool First(HANDLE handle, TestData* d) {
+ Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+ // This method should be called only once for every test.
+ // If it is called more than once it return false which
+ // should break the test.
+ EXPECT_EQ(0, tst->first_called_); // Just to be safe.
+ if (tst->first_called_ > 0) {
+ return false;
+ }
+
+ *d = kTestData[0];
+ tst->index_ = 1;
+ ++(tst->first_called_);
+ return true;
+ }
+
+ static bool Next(HANDLE handle, TestData* d) {
+ Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+ ++(tst->next_called_);
+
+ if (tst->index_ >= kTestDataSize) {
+ return FALSE;
+ }
+
+ *d = kTestData[tst->index_];
+ ++(tst->index_);
+ return true;
+ }
+
+ static bool Fail(HANDLE handle, TestData* d) {
+ Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+ ++(tst->fail_called_);
+ return false;
+ }
+
+ static bool CloseHandle(HANDLE handle) {
+ Win32ToolhelpTest* tst = Win32ToolhelpTest::AsFixture(handle);
+ ++(tst->close_handle_called_);
+ return true;
+ }
+
+ protected:
+ virtual void SetUp() {
+ fail_called_ = 0;
+ first_called_ = 0;
+ next_called_ = 0;
+ close_handle_called_ = 0;
+ index_ = 0;
+ }
+
+ static bool AllZero(const TestData& data) {
+ return data.dwSize == 0 && data.a == 0 && data.b == 0;
+ }
+
+ static bool Equals(const TestData& expected, const TestData& actual) {
+ return expected.dwSize == actual.dwSize
+ && expected.a == actual.a
+ && expected.b == actual.b;
+ }
+
+ bool CheckCallCounters(int first, int next, int fail, int close) {
+ bool match = first_called_ == first && next_called_ == next
+ && fail_called_ == fail && close_handle_called_ == close;
+
+ if (!match) {
+ LOG(LS_ERROR) << "Expected: ("
+ << first << ", "
+ << next << ", "
+ << fail << ", "
+ << close << ")";
+
+ LOG(LS_ERROR) << "Actual: ("
+ << first_called_ << ", "
+ << next_called_ << ", "
+ << fail_called_ << ", "
+ << close_handle_called_ << ")";
+ }
+ return match;
+ }
+
+ static const int kTestDataSize = 3;
+ static const TestData kTestData[];
+ int index_;
+ int first_called_;
+ int fail_called_;
+ int next_called_;
+ int close_handle_called_;
+};
+
+const TestData Win32ToolhelpTest::kTestData[] = {
+ {1, 1, 1}, {2, 2, 2}, {3, 3, 3}
+};
+
+
+class TestTraits {
+ public:
+ typedef TestData Type;
+
+ static bool First(HANDLE handle, Type* t) {
+ return Win32ToolhelpTest::First(handle, t);
+ }
+
+ static bool Next(HANDLE handle, Type* t) {
+ return Win32ToolhelpTest::Next(handle, t);
+ }
+
+ static bool CloseHandle(HANDLE handle) {
+ return Win32ToolhelpTest::CloseHandle(handle);
+ }
+};
+
+class BadFirstTraits {
+ public:
+ typedef TestData Type;
+
+ static bool First(HANDLE handle, Type* t) {
+ return Win32ToolhelpTest::Fail(handle, t);
+ }
+
+ static bool Next(HANDLE handle, Type* t) {
+ // This should never be called.
+ ADD_FAILURE();
+ return false;
+ }
+
+ static bool CloseHandle(HANDLE handle) {
+ return Win32ToolhelpTest::CloseHandle(handle);
+ }
+};
+
+class BadNextTraits {
+ public:
+ typedef TestData Type;
+
+ static bool First(HANDLE handle, Type* t) {
+ return Win32ToolhelpTest::First(handle, t);
+ }
+
+ static bool Next(HANDLE handle, Type* t) {
+ return Win32ToolhelpTest::Fail(handle, t);
+ }
+
+ static bool CloseHandle(HANDLE handle) {
+ return Win32ToolhelpTest::CloseHandle(handle);
+ }
+};
+
+// The toolhelp in normally inherited but most of
+// these tests only excercise the methods from the
+// traits therefore I use a typedef to make the
+// test code easier to read.
+typedef talk_base::ToolhelpEnumeratorBase<TestTraits> EnumeratorForTest;
+
+TEST_F(Win32ToolhelpTest, TestNextWithInvalidCtorHandle) {
+ EnumeratorForTest t(INVALID_HANDLE_VALUE);
+
+ EXPECT_FALSE(t.Next());
+ EXPECT_TRUE(CheckCallCounters(0, 0, 0, 0));
+}
+
+// Tests that Next() returns false if the first-pointer
+// function fails.
+TEST_F(Win32ToolhelpTest, TestNextFirstFails) {
+ typedef talk_base::ToolhelpEnumeratorBase<BadFirstTraits> BadEnumerator;
+ talk_base::scoped_ptr<BadEnumerator> t(new BadEnumerator(AsHandle()));
+
+ // If next ever fails it shall always fail.
+ EXPECT_FALSE(t->Next());
+ EXPECT_FALSE(t->Next());
+ EXPECT_FALSE(t->Next());
+ t.reset();
+ EXPECT_TRUE(CheckCallCounters(0, 0, 1, 1));
+}
+
+// Tests that Next() returns false if the next-pointer
+// function fails.
+TEST_F(Win32ToolhelpTest, TestNextNextFails) {
+ typedef talk_base::ToolhelpEnumeratorBase<BadNextTraits> BadEnumerator;
+ talk_base::scoped_ptr<BadEnumerator> t(new BadEnumerator(AsHandle()));
+
+ // If next ever fails it shall always fail. No more calls
+ // shall be dispatched to Next(...).
+ EXPECT_TRUE(t->Next());
+ EXPECT_FALSE(t->Next());
+ EXPECT_FALSE(t->Next());
+ t.reset();
+ EXPECT_TRUE(CheckCallCounters(1, 0, 1, 1));
+}
+
+
+// Tests that current returns an object is all zero's
+// if Next() hasn't been called.
+TEST_F(Win32ToolhelpTest, TestCurrentNextNotCalled) {
+ talk_base::scoped_ptr<EnumeratorForTest> t(new EnumeratorForTest(AsHandle()));
+ EXPECT_TRUE(AllZero(t->current()));
+ t.reset();
+ EXPECT_TRUE(CheckCallCounters(0, 0, 0, 1));
+}
+
+// Tests the simple everything works path through the code.
+TEST_F(Win32ToolhelpTest, TestCurrentNextCalled) {
+ talk_base::scoped_ptr<EnumeratorForTest> t(new EnumeratorForTest(AsHandle()));
+
+ EXPECT_TRUE(t->Next());
+ EXPECT_TRUE(Equals(t->current(), kTestData[0]));
+ EXPECT_TRUE(t->Next());
+ EXPECT_TRUE(Equals(t->current(), kTestData[1]));
+ EXPECT_TRUE(t->Next());
+ EXPECT_TRUE(Equals(t->current(), kTestData[2]));
+ EXPECT_FALSE(t->Next());
+ t.reset();
+ EXPECT_TRUE(CheckCallCounters(1, 3, 0, 1));
+}
+
+TEST_F(Win32ToolhelpTest, TestCurrentProcess) {
+ int size = MAX_PATH;
+ WCHAR buf[MAX_PATH];
+ GetModuleFileName(NULL, buf, ARRAY_SIZE(buf));
+ std::wstring name = ToUtf16(Pathname(ToUtf8(buf)).filename());
+
+ talk_base::ProcessEnumerator processes;
+ bool found = false;
+ while (processes.Next()) {
+ if (!name.compare(processes.current().szExeFile)) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+
+ talk_base::ModuleEnumerator modules(processes.current().th32ProcessID);
+ found = false;
+ while (modules.Next()) {
+ if (!name.compare(modules.current().szModule)) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32window.cc b/talk/base/win32window.cc
index 0e7761f..5b6275e 100644
--- a/talk/base/win32window.cc
+++ b/talk/base/win32window.cc
@@ -103,12 +103,12 @@
LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
Win32Window* that = reinterpret_cast<Win32Window*>(
- ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+ ::GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (!that && (WM_CREATE == uMsg)) {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
that = static_cast<Win32Window*>(cs->lpCreateParams);
that->wnd_ = hwnd;
- ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(that));
+ ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
}
if (that) {
LRESULT result;
@@ -120,9 +120,9 @@
}
}
if (WM_NCDESTROY == uMsg) {
- ::SetWindowLongPtr(hwnd, GWL_USERDATA, NULL);
+ ::SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
that->wnd_ = NULL;
- that->OnDestroyed();
+ that->OnNcDestroy();
}
if (handled) {
return result;
diff --git a/talk/base/win32window.h b/talk/base/win32window.h
index 66a56ce..992c8e7 100644
--- a/talk/base/win32window.h
+++ b/talk/base/win32window.h
@@ -2,26 +2,26 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@@ -59,7 +59,7 @@
LRESULT& result);
virtual bool OnClose() { return true; }
- virtual void OnDestroyed() { }
+ virtual void OnNcDestroy() { }
private:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
diff --git a/talk/base/win32window_unittest.cc b/talk/base/win32window_unittest.cc
new file mode 100644
index 0000000..96173b7
--- /dev/null
+++ b/talk/base/win32window_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/win32window.h"
+#include "talk/base/logging.h"
+
+static LRESULT kDummyResult = 0x1234ABCD;
+
+class TestWindow : public talk_base::Win32Window {
+ public:
+ TestWindow() : destroyed_(false) { memset(&msg_, 0, sizeof(msg_)); }
+ const MSG& msg() const { return msg_; }
+ bool destroyed() const { return destroyed_; }
+
+ virtual bool OnMessage(UINT uMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT& result) {
+ msg_.message = uMsg;
+ msg_.wParam = wParam;
+ msg_.lParam = lParam;
+ result = kDummyResult;
+ return true;
+ }
+ virtual void OnNcDestroy() {
+ destroyed_ = true;
+ }
+
+ private:
+ MSG msg_;
+ bool destroyed_;
+};
+
+TEST(Win32WindowTest, Basics) {
+ TestWindow wnd;
+ EXPECT_TRUE(wnd.handle() == NULL);
+ EXPECT_FALSE(wnd.destroyed());
+ EXPECT_TRUE(wnd.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+ EXPECT_TRUE(wnd.handle() != NULL);
+ EXPECT_EQ(kDummyResult, ::SendMessage(wnd.handle(), WM_USER, 1, 2));
+ EXPECT_EQ(WM_USER, wnd.msg().message);
+ EXPECT_EQ(1, wnd.msg().wParam);
+ EXPECT_EQ(2, wnd.msg().lParam);
+ wnd.Destroy();
+ EXPECT_TRUE(wnd.handle() == NULL);
+ EXPECT_TRUE(wnd.destroyed());
+}
+
+TEST(Win32WindowTest, MultipleWindows) {
+ TestWindow wnd1, wnd2;
+ EXPECT_TRUE(wnd1.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+ EXPECT_TRUE(wnd2.Create(0, L"Test", 0, 0, 0, 0, 100, 100));
+ EXPECT_TRUE(wnd1.handle() != NULL);
+ EXPECT_TRUE(wnd2.handle() != NULL);
+ wnd1.Destroy();
+ wnd2.Destroy();
+ EXPECT_TRUE(wnd2.handle() == NULL);
+ EXPECT_TRUE(wnd1.handle() == NULL);
+}
diff --git a/talk/base/win32windowpicker.cc b/talk/base/win32windowpicker.cc
new file mode 100644
index 0000000..b674d35
--- /dev/null
+++ b/talk/base/win32windowpicker.cc
@@ -0,0 +1,115 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/win32windowpicker.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+namespace {
+
+// Window class names that we want to filter out.
+const char kProgramManagerClass[] = "Progman";
+const char kButtonClass[] = "Button";
+
+} // namespace
+
+BOOL CALLBACK Win32WindowPicker::EnumProc(HWND hwnd, LPARAM l_param) {
+ WindowDescriptionList* descriptions =
+ reinterpret_cast<WindowDescriptionList*>(l_param);
+
+ // Skip windows that are invisible, minimized, have no title, or are owned,
+ // unless they have the app window style set. Except for minimized windows,
+ // this is what Alt-Tab does.
+ // TODO: Figure out how to grab a thumbnail of a minimized window and
+ // include them in the list.
+ int len = GetWindowTextLength(hwnd);
+ HWND owner = GetWindow(hwnd, GW_OWNER);
+ LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
+ if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
+ (owner && !(exstyle & WS_EX_APPWINDOW))) {
+ // TODO: Investigate if windows without title still could be
+ // interesting to share. We could use the name of the process as title:
+ //
+ // GetWindowThreadProcessId()
+ // OpenProcess()
+ // QueryFullProcessImageName()
+ return TRUE;
+ }
+
+ // Skip the Program Manager window and the Start button.
+ TCHAR class_name_w[500];
+ ::GetClassName(hwnd, class_name_w, 500);
+ std::string class_name = ToUtf8(class_name_w, wcslen(class_name_w));
+ if (class_name == kProgramManagerClass || class_name == kButtonClass) {
+ // We don't want the Program Manager window nor the Start button.
+ return TRUE;
+ }
+
+ TCHAR window_title[500];
+ GetWindowText(hwnd, window_title, ARRAY_SIZE(window_title));
+ std::string title = ToUtf8(window_title, wcslen(window_title));
+ WindowId id(hwnd);
+ WindowDescription desc(id, title);
+ descriptions->push_back(desc);
+ return TRUE;
+}
+
+BOOL CALLBACK Win32WindowPicker::MonitorEnumProc(HMONITOR h_monitor,
+ HDC hdc_monitor,
+ LPRECT lprc_monitor,
+ LPARAM l_param) {
+ DesktopDescriptionList* desktop_desc =
+ reinterpret_cast<DesktopDescriptionList*>(l_param);
+
+ DesktopId id(h_monitor, desktop_desc->size());
+ // TODO: Figure out an appropriate desktop title.
+ DesktopDescription desc(id, "");
+ desktop_desc->push_back(desc);
+ return TRUE;
+}
+
+Win32WindowPicker::Win32WindowPicker() {
+}
+
+bool Win32WindowPicker::Init() {
+ return true;
+}
+// TODO: Consider changing enumeration to clear() descriptions
+// before append().
+bool Win32WindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+ LPARAM desc = reinterpret_cast<LPARAM>(descriptions);
+ return EnumWindows(Win32WindowPicker::EnumProc, desc) != FALSE;
+}
+
+bool Win32WindowPicker::GetDesktopList(DesktopDescriptionList* descriptions) {
+ // Create a fresh WindowDescriptionList so that we can use desktop_desc.size()
+ // in MonitorEnumProc to compute the desktop index.
+ DesktopDescriptionList desktop_desc;
+ HDC hdc = GetDC(NULL);
+ bool success = false;
+ if (EnumDisplayMonitors(hdc, NULL, Win32WindowPicker::MonitorEnumProc,
+ reinterpret_cast<LPARAM>(&desktop_desc)) != FALSE) {
+ // Append the desktop descriptions to the end of the returned descriptions.
+ descriptions->insert(descriptions->end(), desktop_desc.begin(),
+ desktop_desc.end());
+ success = true;
+ }
+ ReleaseDC(NULL, hdc);
+ return success;
+}
+
+bool Win32WindowPicker::IsVisible(const WindowId& id) {
+ return (::IsWindow(id.id()) != FALSE && ::IsWindowVisible(id.id()) != FALSE);
+}
+
+bool Win32WindowPicker::MoveToFront(const WindowId& id) {
+ return SetForegroundWindow(id.id()) != FALSE;
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32windowpicker.h b/talk/base/win32windowpicker.h
new file mode 100644
index 0000000..d5f779b
--- /dev/null
+++ b/talk/base/win32windowpicker.h
@@ -0,0 +1,31 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#ifndef TALK_BASE_WIN32WINDOWPICKER_H_
+#define TALK_BASE_WIN32WINDOWPICKER_H_
+
+#include "talk/base/win32.h"
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class Win32WindowPicker : public WindowPicker {
+ public:
+ Win32WindowPicker();
+ virtual bool Init();
+ virtual bool IsVisible(const WindowId& id);
+ virtual bool MoveToFront(const WindowId& id);
+ virtual bool GetWindowList(WindowDescriptionList* descriptions);
+ virtual bool GetDesktopList(DesktopDescriptionList* descriptions);
+
+ protected:
+ static BOOL CALLBACK EnumProc(HWND hwnd, LPARAM l_param);
+ static BOOL CALLBACK MonitorEnumProc(HMONITOR h_monitor,
+ HDC hdc_monitor,
+ LPRECT lprc_monitor,
+ LPARAM l_param);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WIN32WINDOWPICKER_H_
diff --git a/talk/base/win32windowpicker_unittest.cc b/talk/base/win32windowpicker_unittest.cc
new file mode 100644
index 0000000..065193e
--- /dev/null
+++ b/talk/base/win32windowpicker_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+#include "talk/base/win32windowpicker.h"
+#include "talk/base/windowpicker.h"
+
+#ifndef WIN32
+#error Only for Windows
+#endif
+
+namespace talk_base {
+
+static const TCHAR* kVisibleWindowTitle = L"Visible Window";
+static const TCHAR* kInvisibleWindowTitle = L"Invisible Window";
+
+class Win32WindowPickerForTest : public Win32WindowPicker {
+ public:
+ Win32WindowPickerForTest() {
+ EXPECT_TRUE(visible_window_.Create(NULL, kVisibleWindowTitle, WS_VISIBLE,
+ 0, 0, 0, 0, 0));
+ EXPECT_TRUE(invisible_window_.Create(NULL, kInvisibleWindowTitle, 0,
+ 0, 0, 0, 0, 0));
+ }
+
+ ~Win32WindowPickerForTest() {
+ visible_window_.Destroy();
+ invisible_window_.Destroy();
+ }
+
+ virtual bool GetWindowList(WindowDescriptionList* descriptions) {
+ if (!Win32WindowPicker::EnumProc(visible_window_.handle(),
+ reinterpret_cast<LPARAM>(descriptions))) {
+ return false;
+ }
+ if (!Win32WindowPicker::EnumProc(invisible_window_.handle(),
+ reinterpret_cast<LPARAM>(descriptions))) {
+ return false;
+ }
+ return true;
+ }
+
+ Win32Window* visible_window() {
+ return &visible_window_;
+ }
+
+ Win32Window* invisible_window() {
+ return &invisible_window_;
+ }
+
+ private:
+ Win32Window visible_window_;
+ Win32Window invisible_window_;
+};
+
+TEST(Win32WindowPickerTest, TestGetWindowList) {
+ Win32WindowPickerForTest window_picker;
+ WindowDescriptionList descriptions;
+ EXPECT_TRUE(window_picker.GetWindowList(&descriptions));
+ EXPECT_EQ(1, descriptions.size());
+ WindowDescription desc = descriptions.front();
+ EXPECT_EQ(window_picker.visible_window()->handle(), desc.id().id());
+ TCHAR window_title[500];
+ GetWindowText(window_picker.visible_window()->handle(), window_title,
+ ARRAY_SIZE(window_title));
+ EXPECT_EQ(ToUtf8(window_title, wcslen(window_title)),
+ ToUtf8(kVisibleWindowTitle, wcslen(kVisibleWindowTitle)));
+}
+
+TEST(Win32WindowPickerTest, TestIsVisible) {
+ Win32WindowPickerForTest window_picker;
+ HWND visible_id = window_picker.visible_window()->handle();
+ HWND invisible_id = window_picker.invisible_window()->handle();
+ EXPECT_TRUE(window_picker.IsVisible(WindowId(visible_id)));
+ EXPECT_FALSE(window_picker.IsVisible(WindowId(invisible_id)));
+}
+
+TEST(Win32WindowPickerTest, TestMoveToFront) {
+ Win32WindowPickerForTest window_picker;
+ HWND visible_id = window_picker.visible_window()->handle();
+ HWND invisible_id = window_picker.invisible_window()->handle();
+
+ // There are a number of condition where SetForegroundWindow might
+ // fail depending on the state of the calling process. To be on the
+ // safe side we doesn't expect MoveToFront to return true, just test
+ // that we don't crash.
+ window_picker.MoveToFront(WindowId(visible_id));
+ window_picker.MoveToFront(WindowId(invisible_id));
+}
+
+} // namespace talk_base
diff --git a/talk/base/window.h b/talk/base/window.h
new file mode 100644
index 0000000..b855193
--- /dev/null
+++ b/talk/base/window.h
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_WINDOW_H_
+#define TALK_BASE_WINDOW_H_
+
+// Define platform specific window types.
+#if defined(LINUX)
+typedef unsigned long Window; // Avoid include <X11/Xlib.h>.
+#elif defined(WIN32)
+// We commonly include win32.h in talk/base so just include it here.
+#include "talk/base/win32.h" // Include HWND, HMONITOR.
+#elif defined(OSX)
+typedef unsigned int CGWindowID;
+typedef unsigned int CGDirectDisplayID;
+#endif
+
+namespace talk_base {
+
+class WindowId {
+ public:
+ // Define WindowT for each platform.
+#if defined(LINUX)
+ typedef Window WindowT;
+#elif defined(WIN32)
+ typedef HWND WindowT;
+#elif defined(OSX)
+ typedef CGWindowID WindowT;
+#else
+ typedef unsigned int WindowT;
+#endif
+
+ static WindowId Cast(int id) {
+#if defined(WIN32)
+ return WindowId(reinterpret_cast<WindowId::WindowT>(id));
+#else
+ return WindowId(static_cast<WindowId::WindowT>(id));
+#endif
+ }
+
+ WindowId() : id_(0) {}
+ WindowId(const WindowT& id) : id_(id) {} // NOLINT
+ const WindowT& id() const { return id_; }
+ bool IsValid() const { return id_ != 0; }
+ bool Equals(const WindowId& other) const {
+ return id_ == other.id();
+ }
+
+ private:
+ WindowT id_;
+};
+
+class DesktopId {
+ public:
+ // Define DesktopT for each platform.
+#if defined(LINUX)
+ typedef Window DesktopT;
+#elif defined(WIN32)
+ typedef HMONITOR DesktopT;
+#elif defined(OSX)
+ typedef CGDirectDisplayID DesktopT;
+#else
+ typedef unsigned int DesktopT;
+#endif
+
+ static DesktopId Cast(int id, int index) {
+#if defined(WIN32)
+ return DesktopId(reinterpret_cast<DesktopId::DesktopT>(id), index);
+#else
+ return DesktopId(static_cast<DesktopId::DesktopT>(id), index);
+#endif
+ }
+
+ DesktopId() : id_(0), index_(-1) {}
+ DesktopId(const DesktopT& id, int index) // NOLINT
+ : id_(id), index_(index) {
+ }
+ const DesktopT& id() const { return id_; }
+ int index() const { return index_; }
+ bool IsValid() const { return index_ != -1; }
+ bool Equals(const DesktopId& other) const {
+ return id_ == other.id() && index_ == other.index();
+ }
+
+ private:
+ // Id is the platform specific desktop identifier.
+ DesktopT id_;
+ // Index is the desktop index as enumerated by each platform.
+ // Desktop capturer typically takes the index instead of id.
+ int index_;
+};
+
+// Window event types.
+enum WindowEvent {
+ WE_RESIZE = 0,
+ WE_CLOSE = 1,
+ WE_MINIMIZE = 2,
+ WE_RESTORE = 3,
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WINDOW_H_
diff --git a/talk/base/windowpicker.h b/talk/base/windowpicker.h
new file mode 100644
index 0000000..242fabc
--- /dev/null
+++ b/talk/base/windowpicker.h
@@ -0,0 +1,70 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+// thorcarpenter@google.com (Thor Carpenter)
+
+#ifndef TALK_BASE_WINDOWPICKER_H_
+#define TALK_BASE_WINDOWPICKER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/window.h"
+
+namespace talk_base {
+
+class WindowDescription {
+ public:
+ WindowDescription() : id_() {}
+ WindowDescription(const WindowId& id, const std::string& title)
+ : id_(id), title_(title) {
+ }
+ const WindowId& id() const { return id_; }
+ void set_id(const WindowId& id) { id_ = id; }
+ const std::string& title() const { return title_; }
+ void set_title(const std::string& title) { title_ = title; }
+
+ private:
+ WindowId id_;
+ std::string title_;
+};
+
+class DesktopDescription {
+ public:
+ DesktopDescription() : id_() {}
+ DesktopDescription(const DesktopId& id, const std::string& title)
+ : id_(id), title_(title) {
+ }
+ const DesktopId& id() const { return id_; }
+ void set_id(const DesktopId& id) { id_ = id; }
+ const std::string& title() const { return title_; }
+ void set_title(const std::string& title) { title_ = title; }
+
+ private:
+ DesktopId id_;
+ std::string title_;
+};
+
+typedef std::vector<WindowDescription> WindowDescriptionList;
+typedef std::vector<DesktopDescription> DesktopDescriptionList;
+
+class WindowPicker {
+ public:
+ virtual ~WindowPicker() {}
+ virtual bool Init() = 0;
+
+ // TODO: Move this two methods to window.h when we no longer need to load
+ // CoreGraphics dynamically.
+ virtual bool IsVisible(const WindowId& id) = 0;
+ virtual bool MoveToFront(const WindowId& id) = 0;
+
+ // Gets a list of window description and appends to descriptions.
+ // Returns true if successful.
+ virtual bool GetWindowList(WindowDescriptionList* descriptions) = 0;
+ // Gets a list of desktop descriptions and appends to descriptions.
+ // Returns true if successful.
+ virtual bool GetDesktopList(DesktopDescriptionList* descriptions) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WINDOWPICKER_H_
diff --git a/talk/base/windowpickerfactory.h b/talk/base/windowpickerfactory.h
new file mode 100644
index 0000000..e001a2c
--- /dev/null
+++ b/talk/base/windowpickerfactory.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2010 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_WINDOWPICKERFACTORY_H_
+#define TALK_BASE_WINDOWPICKERFACTORY_H_
+
+#if defined(WIN32)
+#include "talk/base/win32windowpicker.h"
+#elif defined(OSX)
+#include "talk/base/macutils.h"
+#include "talk/base/macwindowpicker.h"
+#elif defined(LINUX)
+#include "talk/base/linuxwindowpicker.h"
+#endif
+
+#include "talk/base/windowpicker.h"
+
+namespace talk_base {
+
+class WindowPickerFactory {
+ public:
+ static WindowPicker* CreateWindowPicker() {
+#if defined(WIN32)
+ return new Win32WindowPicker();
+#elif defined(OSX)
+ return new MacWindowPicker();
+#elif defined(LINUX)
+ return new LinuxWindowPicker();
+#else
+ return NULL;
+#endif
+ }
+
+ static bool IsSupported() {
+#ifdef OSX
+ return GetOSVersionName() >= kMacOSLeopard;
+#else
+ return true;
+#endif
+ }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WINDOWPICKERFACTORY_H_
diff --git a/talk/base/winfirewall_unittest.cc b/talk/base/winfirewall_unittest.cc
new file mode 100644
index 0000000..9987716
--- /dev/null
+++ b/talk/base/winfirewall_unittest.cc
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/winfirewall.h"
+
+#include <objbase.h>
+
+namespace talk_base {
+
+TEST(WinFirewallTest, ReadStatus) {
+ ::CoInitialize(NULL);
+ WinFirewall fw;
+ HRESULT hr;
+ bool authorized;
+
+ EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized));
+ EXPECT_TRUE(fw.Initialize(&hr));
+ EXPECT_EQ(S_OK, hr);
+
+ EXPECT_TRUE(fw.QueryAuthorized("bogus.exe", &authorized));
+
+ // Unless we mock out INetFwMgr we can't really have an expectation either way
+ // about whether we're authorized. It will depend on the settings of the
+ // machine running the test. Same goes for AddApplication.
+
+ fw.Shutdown();
+ EXPECT_FALSE(fw.QueryAuthorized("bogus.exe", &authorized));
+
+ ::CoUninitialize();
+}
+
+} // namespace talk_base
diff --git a/talk/base/winping.cc b/talk/base/winping.cc
index 1802e03..a16ac78 100644
--- a/talk/base/winping.cc
+++ b/talk/base/winping.cc
@@ -180,7 +180,7 @@
}
delete[] data_;
- delete reply_;
+ delete[] reply_;
}
WinPing::PingResult WinPing::Ping(
diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc
index b9f5696..c2be1e8 100644
--- a/talk/examples/call/call_main.cc
+++ b/talk/examples/call/call_main.cc
@@ -25,14 +25,15 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include <cstdio>
+#include <cstring>
#include <time.h>
#include <iomanip>
#include <iostream>
-#include <cstdio>
-#include <cstring>
#include <vector>
-#include "talk/base/logging.h"
+
#include "talk/base/flags.h"
+#include "talk/base/logging.h"
#ifdef OSX
#include "talk/base/macsocketserver.h"
#endif
@@ -40,16 +41,19 @@
#include "talk/base/stream.h"
#include "talk/base/ssladapter.h"
#include "talk/base/win32socketserver.h"
-#include "talk/p2p/base/constants.h"
-#include "talk/xmpp/xmppclientsettings.h"
#include "talk/examples/login/xmppthread.h"
#include "talk/examples/login/xmppauth.h"
#include "talk/examples/login/xmpppump.h"
#include "talk/examples/call/callclient.h"
#include "talk/examples/call/console.h"
-#include "talk/session/phone/filemediaengine.h"
+#include "talk/examples/call/mediaenginefactory.h"
+#include "talk/p2p/base/constants.h"
+#ifdef ANDROID
+#include "talk/session/phone/androidmediaengine.h"
+#endif
#include "talk/session/phone/mediasessionclient.h"
#include "talk/session/phone/srtpfilter.h"
+#include "talk/xmpp/xmppclientsettings.h"
class DebugLog : public sigslot::has_slots<> {
public:
@@ -177,48 +181,18 @@
static DebugLog debug_log_;
static const int DEFAULT_PORT = 5222;
+#ifdef ANDROID
+static std::vector<cricket::AudioCodec> codecs;
+static const cricket::AudioCodec ISAC(103, "ISAC", 40000, 16000, 1, 0);
-cricket::MediaEngineInterface* CreateFileMediaEngine(const char* voice_in,
- const char* voice_out,
- const char* video_in,
- const char* video_out) {
- cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
- // Set the RTP dump file names.
- if (voice_in) {
- file_media_engine->set_voice_input_filename(voice_in);
- }
- if (voice_out) {
- file_media_engine->set_voice_output_filename(voice_out);
- }
- if (video_in) {
- file_media_engine->set_video_input_filename(video_in);
- }
- if (video_out) {
- file_media_engine->set_video_output_filename(video_out);
- }
+cricket::MediaEngine *AndroidMediaEngineFactory() {
+ cricket::FakeMediaEngine *engine = new cricket::FakeMediaEngine();
- // Set voice and video codecs. TODO: The codecs actually depend on
- // the the input voice and video streams.
- std::vector<cricket::AudioCodec> voice_codecs;
- voice_codecs.push_back(
- cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
- voice_codecs.push_back(
- cricket::AudioCodec(0, "PCMU", 8000, 0, 1, 0));
- voice_codecs.push_back(
- cricket::AudioCodec(13, "CN", 8000, 0, 1, 0));
- voice_codecs.push_back(
- cricket::AudioCodec(105, "CN", 16000, 0, 1, 0));
- file_media_engine->set_voice_codecs(voice_codecs);
- std::vector<cricket::VideoCodec> video_codecs;
- video_codecs.push_back(
- cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
- video_codecs.push_back(
- cricket::VideoCodec(99, "H264-SVC", 640, 360, 30, 0));
- file_media_engine->set_video_codecs(video_codecs);
-
- return file_media_engine;
+ codecs.push_back(ISAC);
+ engine->SetAudioCodecs(codecs);
+ return engine;
}
-
+#endif
// TODO: Move this into Console.
void Print(const char* chars) {
@@ -236,22 +210,25 @@
// define options
DEFINE_bool(a, false, "Turn on auto accept.");
DEFINE_bool(d, false, "Turn on debugging.");
- DEFINE_string(
- protocol, "hybrid",
+ DEFINE_string(protocol, "hybrid",
"Initial signaling protocol to use: jingle, gingle, or hybrid.");
- DEFINE_string(
- secure, "enable",
+ DEFINE_string(secure, "enable",
"Disable or enable encryption: disable, enable, require.");
+ DEFINE_string(tls, "enable",
+ "Disable or enable tls: disable, enable, require.");
+ DEFINE_bool(allowplain, false, "Allow plain authentication");
DEFINE_bool(testserver, false, "Use test server");
- DEFINE_bool(plainserver, false, "Turn off tls and allow plain password.");
DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
DEFINE_string(filterhost, NULL, "Filter out the host from all candidates.");
DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
DEFINE_string(s, "talk.google.com", "The connection server to use.");
+ DEFINE_string(capsnode, "http://code.google.com/p/libjingle/call",
+ "Caps node: A URI identifying the app.");
+ DEFINE_string(capsver, "0.6",
+ "Caps ver: A string identifying the version of the app.");
DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
- DEFINE_string(yuvvideoinput, NULL, "YUV file for video input.");
DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
DEFINE_bool(render, true, "Renders the video.");
DEFINE_bool(debugsrtp, false, "Enable debugging for srtp.");
@@ -268,11 +245,14 @@
bool debug = FLAG_d;
std::string protocol = FLAG_protocol;
bool test_server = FLAG_testserver;
- bool plain_server = FLAG_plainserver;
+ bool allow_plain = FLAG_allowplain;
+ std::string tls = FLAG_tls;
int32 portallocator_flags = FLAG_portallocator;
std::string pmuc_domain = FLAG_pmuc;
std::string server = FLAG_s;
std::string secure = FLAG_secure;
+ std::string caps_node = FLAG_capsnode;
+ std::string caps_ver = FLAG_capsver;
bool debugsrtp = FLAG_debugsrtp;
bool render = FLAG_render;
@@ -342,15 +322,24 @@
xcs.set_user(jid.node());
xcs.set_resource("call");
xcs.set_host(jid.domain());
- xcs.set_use_tls(!test_server);
+ xcs.set_allow_plain(allow_plain);
- if (plain_server) {
- xcs.set_use_tls(false);
- xcs.set_allow_plain(true);
+ if(tls == "disable") {
+ xcs.set_use_tls(buzz::TLS_DISABLED);
+ } else if (tls == "enable") {
+ xcs.set_use_tls(buzz::TLS_ENABLED);
+ } else if (tls == "require") {
+ xcs.set_use_tls(buzz::TLS_REQUIRED);
+ } else {
+ Print("Invalid TLS option, must be enable, disable, or require.\n");
+ return 1;
}
+
if (test_server) {
pass.password() = jid.node();
xcs.set_allow_plain(true);
+ xcs.set_use_tls(buzz::TLS_DISABLED);
+ xcs.set_test_server_domain("google.com");
}
xcs.set_pass(talk_base::CryptString(pass));
@@ -371,11 +360,14 @@
talk_base::InitializeSSL();
+#ifdef ANDROID
+ InitAndroidMediaEngineFactory(AndroidMediaEngineFactory);
+#endif
#if WIN32
// Need to pump messages on our main thread on Windows.
talk_base::Win32Thread w32_thread;
- talk_base::ThreadManager::SetCurrent(&w32_thread);
+ talk_base::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
#endif
talk_base::Thread* main_thread = talk_base::Thread::Current();
#ifdef OSX
@@ -384,16 +376,18 @@
#endif
XmppPump pump;
- CallClient *client = new CallClient(pump.client());
+ CallClient *client = new CallClient(pump.client(), caps_node, caps_ver);
if (FLAG_voiceinput || FLAG_voiceoutput ||
FLAG_videoinput || FLAG_videooutput) {
- // If any dump file is specified, we use FileMediaEngine.
- cricket::MediaEngineInterface* engine = CreateFileMediaEngine(
- FLAG_voiceinput, FLAG_voiceoutput, FLAG_videoinput, FLAG_videooutput);
- // The engine will be released by the client later.
+ // If any dump file is specified, we use a FileMediaEngine.
+ cricket::MediaEngineInterface* engine =
+ MediaEngineFactory::CreateFileMediaEngine(
+ FLAG_voiceinput, FLAG_voiceoutput,
+ FLAG_videoinput, FLAG_videooutput);
client->SetMediaEngine(engine);
}
+
Console *console = new Console(main_thread, client);
client->SetConsole(console);
client->SetAutoAccept(auto_accept);
@@ -410,7 +404,7 @@
pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
}
- pump.DoLogin(xcs, new XmppSocket(true), NULL);
+ pump.DoLogin(xcs, new XmppSocket(buzz::TLS_REQUIRED), NULL);
main_thread->Run();
pump.DoDisconnect();
diff --git a/talk/examples/call/call_unittest.cc b/talk/examples/call/call_unittest.cc
new file mode 100644
index 0000000..d95f1dd
--- /dev/null
+++ b/talk/examples/call/call_unittest.cc
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Main function for all unit tests in talk/examples/call
+
+#include "talk/base/logging.h"
+#include "testing/base/public/gunit.h"
+
+int main(int argc, char **argv) {
+ talk_base::LogMessage::LogToDebug(talk_base::LogMessage::NO_LOGGING);
+ testing::ParseGUnitFlags(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 3c65272..89f964b 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -43,7 +43,6 @@
#include "talk/examples/call/mucinvitesendtask.h"
#include "talk/examples/call/friendinvitesendtask.h"
#include "talk/examples/call/muc.h"
-#include "talk/examples/call/voicemailjidrequester.h"
#include "talk/p2p/base/sessionmanager.h"
#include "talk/p2p/client/basicportallocator.h"
#include "talk/p2p/client/sessionmanagertask.h"
@@ -111,6 +110,7 @@
" record Starts recording (just signalling; not actually recording.)\n"
" unrecord Stops recording (just signalling; not actually recording.)\n"
" rmute [nick] Remote mute another participant.\n"
+" block [nick] Block another participant.\n"
" quit Quits the application.\n"
"";
@@ -131,12 +131,12 @@
" given JID and with optional bandwidth.\n"
" vcall [jid] [bw] Initiates a video call to the user[/room] with\n"
" the given JID and with optional bandwidth.\n"
-" voicemail [jid] Leave a voicemail for the user with the given JID.\n"
" join [room_jid] Joins a multi-user-chat with room JID.\n"
" ljoin [room_name] Joins a MUC by looking up JID from room name.\n"
" invite user [room] Invites a friend to a multi-user-chat.\n"
" leave [room] Leaves a multi-user-chat.\n"
" nick [nick] Sets the nick.\n"
+" priority [int] Sets the priority.\n"
" getdevs Prints the available media devices.\n"
" quit Quits the application.\n"
"";
@@ -171,6 +171,7 @@
if (command == "accept") {
cricket::CallOptions options;
options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
+ options.has_video = true;
Accept(options);
} else if (command == "reject") {
Reject();
@@ -211,6 +212,11 @@
const std::string& nick = words[1];
hangout_pubsub_client_->RemoteMute(nick);
}
+ } else if ((command == "block") && (words.size() == 2)) {
+ if (InMuc()) {
+ const std::string& nick = words[1];
+ hangout_pubsub_client_->BlockMedia(nick);
+ }
} else if ((command == "dtmf") && (words.size() == 2)) {
int ev = std::string("0123456789*#").find(words[1][0]);
call_->PressDTMF(ev);
@@ -256,19 +262,22 @@
LeaveMuc(GetWord(words, 1, ""));
} else if (command == "nick") {
SetNick(GetWord(words, 1, ""));
+ } else if (command == "priority") {
+ int priority = GetInt(words, 1, 0);
+ SetPriority(priority);
+ SendStatus();
} else if (command == "getdevs") {
GetDevices();
} else if ((words.size() == 2) && (command == "setvol")) {
SetVolume(words[1]);
- } else if (command == "voicemail") {
- CallVoicemail((words.size() >= 2) ? words[1] : "");
} else {
console_->PrintLine(CONSOLE_COMMANDS);
}
}
}
-CallClient::CallClient(buzz::XmppClient* xmpp_client)
+CallClient::CallClient(buzz::XmppClient* xmpp_client,
+ const std::string& caps_node, const std::string& version)
: xmpp_client_(xmpp_client),
worker_thread_(NULL),
media_engine_(NULL),
@@ -288,6 +297,8 @@
initial_protocol_(cricket::PROTOCOL_HYBRID),
secure_policy_(cricket::SEC_DISABLED) {
xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+ my_status_.set_caps_node(caps_node);
+ my_status_.set_version(version);
}
CallClient::~CallClient() {
@@ -431,8 +442,8 @@
void CallClient::OnCallCreate(cricket::Call* call) {
call->SignalSessionState.connect(this, &CallClient::OnSessionState);
- call->SignalMediaSourcesUpdate.connect(
- this, &CallClient::OnMediaSourcesUpdate);
+ call->SignalMediaStreamsUpdate.connect(
+ this, &CallClient::OnMediaStreamsUpdate);
}
void CallClient::OnSessionState(cricket::Call* call,
@@ -452,6 +463,7 @@
}
cricket::CallOptions options;
if (auto_accept_) {
+ options.has_video = true;
Accept(options);
}
} else if (state == cricket::Session::STATE_SENTINITIATE) {
@@ -477,20 +489,38 @@
void CallClient::OnSpeakerChanged(cricket::Call* call,
cricket::Session* session,
- const cricket::NamedSource& speaker) {
- if (speaker.ssrc == 0) {
+ const cricket::StreamParams& speaker) {
+ if (!speaker.has_ssrcs()) {
console_->PrintLine("Session %s has no current speaker.",
session->id().c_str());
} else if (speaker.nick.empty()) {
console_->PrintLine("Session %s speaker change to unknown (%u).",
- session->id().c_str(), speaker.ssrc);
+ session->id().c_str(), speaker.first_ssrc());
} else {
console_->PrintLine("Session %s speaker changed to %s (%u).",
session->id().c_str(), speaker.nick.c_str(),
- speaker.ssrc);
+ speaker.first_ssrc());
}
}
+void SetMediaCaps(int media_caps, buzz::Status* status) {
+ status->set_voice_capability((media_caps & cricket::AUDIO_RECV) != 0);
+ status->set_video_capability((media_caps & cricket::VIDEO_RECV) != 0);
+ status->set_camera_capability((media_caps & cricket::VIDEO_SEND) != 0);
+}
+
+void SetCaps(int media_caps, buzz::Status* status) {
+ status->set_know_capabilities(true);
+ status->set_pmuc_capability(true);
+ SetMediaCaps(media_caps, status);
+}
+
+void SetAvailable(const buzz::Jid& jid, buzz::Status* status) {
+ status->set_jid(jid);
+ status->set_available(true);
+ status->set_show(buzz::Status::SHOW_ONLINE);
+}
+
void CallClient::InitPresence() {
presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
presence_push_->SignalStatusUpdate.connect(
@@ -502,7 +532,9 @@
presence_push_->Start();
presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
- RefreshStatus();
+ SetAvailable(xmpp_client_->jid(), &my_status_);
+ SetCaps(media_client_->GetCapabilities(), &my_status_);
+ SendStatus(my_status_);
presence_out_->Start();
muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
@@ -517,23 +549,8 @@
friend_invite_send_->Start();
}
-void CallClient::RefreshStatus() {
- int media_caps = media_client_->GetCapabilities();
- my_status_.set_jid(xmpp_client_->jid());
- my_status_.set_available(true);
- my_status_.set_show(buzz::Status::SHOW_ONLINE);
- my_status_.set_priority(0);
- my_status_.set_know_capabilities(true);
- my_status_.set_pmuc_capability(true);
- my_status_.set_voice_capability(
- (media_caps & cricket::AUDIO_RECV) != 0);
- my_status_.set_video_capability(
- (media_caps & cricket::VIDEO_RECV) != 0);
- my_status_.set_camera_capability(
- (media_caps & cricket::VIDEO_SEND) != 0);
- my_status_.set_is_google_client(true);
- my_status_.set_version("1.0.0.67");
- presence_out_->Send(my_status_);
+void CallClient::SendStatus(const buzz::Status& status) {
+ presence_out_->Send(status);
}
void CallClient::OnStatusUpdate(const buzz::Status& status) {
@@ -613,10 +630,6 @@
// if the first character is a +, assume it's a phone number
found_jid = callto_jid;
found = true;
- } else if (callto_jid.resource() == "voicemail") {
- // if the resource is /voicemail, allow that
- found_jid = callto_jid;
- found = true;
} else {
// otherwise, it's a friend
for (RosterMap::iterator iter = roster_->begin();
@@ -673,6 +686,8 @@
this, &CallClient::OnRecordingStateChange);
hangout_pubsub_client_->SignalRemoteMute.connect(
this, &CallClient::OnRemoteMuted);
+ hangout_pubsub_client_->SignalMediaBlock.connect(
+ this, &CallClient::OnMediaBlocked);
hangout_pubsub_client_->SignalRequestError.connect(
this, &CallClient::OnHangoutRequestError);
hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
@@ -709,7 +724,7 @@
const std::string& nick, bool was_recording, bool is_recording) {
if (!was_recording && is_recording) {
console_->PrintLine("%s now recording.", nick.c_str());
- } else if (was_recording && is_recording) {
+ } else if (was_recording && !is_recording) {
console_->PrintLine("%s no longer recording.", nick.c_str());
}
}
@@ -726,6 +741,12 @@
}
}
+void CallClient::OnMediaBlocked(const std::string& blockee_nick,
+ const std::string& blocker_nick) {
+ console_->PrintLine("%s blocked by %s.",
+ blockee_nick.c_str(), blocker_nick.c_str());
+}
+
void CallClient::OnHangoutRequestError(const std::string& node,
const buzz::XmlElement* stanza) {
console_->PrintLine("Failed request pub sub items for node %s.",
@@ -753,31 +774,6 @@
console_->PrintLine("Failed to remote mute.");
}
-void CallClient::CallVoicemail(const std::string& name) {
- buzz::Jid jid(name);
- if (!jid.IsValid() || jid.node() == "") {
- console_->PrintLine("Invalid JID. JIDs should be in the form user@domain.");
- return;
- }
- buzz::VoicemailJidRequester *request =
- new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
- request->SignalGotVoicemailJid.connect(this,
- &CallClient::OnFoundVoicemailJid);
- request->SignalVoicemailJidError.connect(this,
- &CallClient::OnVoicemailJidError);
- request->Start();
-}
-
-void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
- const buzz::Jid& voicemail) {
- console_->PrintLine("Calling %s's voicemail.", to.Str().c_str());
- PlaceCall(voicemail, cricket::CallOptions());
-}
-
-void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
- console_->PrintLine("Unable to voicemail %s.", to.Str().c_str());
-}
-
void CallClient::Accept(const cricket::CallOptions& options) {
ASSERT(call_ && incoming_call_);
ASSERT(call_->sessions().size() == 1);
@@ -832,9 +828,8 @@
domain = room_name.substr(room_name.find("@") + 1);
}
- buzz::MucRoomLookupTask* lookup_query_task =
- new buzz::MucRoomLookupTask(
- xmpp_client_, buzz::JID_GOOGLE_MUC_LOOKUP, room, domain);
+ buzz::MucRoomLookupTask* lookup_query_task = new buzz::MucRoomLookupTask(
+ xmpp_client_, buzz::Jid(buzz::STR_GOOGLE_MUC_LOOKUP_JID), room, domain);
lookup_query_task->SignalResult.connect(this,
&CallClient::OnRoomLookupResponse);
lookup_query_task->SignalError.connect(this,
@@ -984,20 +979,26 @@
}
bool CallClient::InMuc() {
- return FirstMucJid().IsValid();
+ const buzz::Jid* muc_jid = FirstMucJid();
+ if (!muc_jid) return false;
+ return muc_jid->IsValid();
}
-const buzz::Jid& CallClient::FirstMucJid() {
- return mucs_.begin()->first;
+const buzz::Jid* CallClient::FirstMucJid() {
+ if (mucs_.empty()) return NULL;
+ return &(mucs_.begin()->first);
}
void CallClient::LeaveMuc(const std::string& room) {
buzz::Jid room_jid;
+ const buzz::Jid* muc_jid = FirstMucJid();
if (room.length() > 0) {
room_jid = buzz::Jid(room);
} else if (mucs_.size() > 0) {
// leave the first MUC if no JID specified
- room_jid = FirstMucJid();
+ if (muc_jid) {
+ room_jid = *(muc_jid);
+ }
}
if (!room_jid.IsValid()) {
@@ -1103,31 +1104,36 @@
void CallClient::OnDevicesChange() {
console_->PrintLine("Devices changed.");
- RefreshStatus();
+ SetMediaCaps(media_client_->GetCapabilities(), &my_status_);
+ SendStatus(my_status_);
}
void CallClient::SetVolume(const std::string& level) {
media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
}
-void CallClient::OnMediaSourcesUpdate(cricket::Call* call,
+void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
cricket::Session* session,
- const cricket::MediaSources& sources) {
- for (cricket::NamedSources::const_iterator it = sources.video().begin();
- it != sources.video().end(); ++it) {
- if (it->removed) {
- RemoveStaticRenderedView(it->ssrc);
- } else {
- if (render_) {
+ const cricket::MediaStreams& added,
+ const cricket::MediaStreams& removed) {
+ if (call->video()) {
+ for (std::vector<cricket::StreamParams>::const_iterator
+ it = removed.video().begin(); it != removed.video().end(); ++it) {
+ RemoveStaticRenderedView(it->first_ssrc());
+ }
+
+ if (render_) {
+ for (std::vector<cricket::StreamParams>::const_iterator
+ it = added.video().begin(); it != added.video().end(); ++it) {
// TODO: Make dimensions and positions more configurable.
int offset = (50 * static_views_accumulated_count_) % 300;
- AddStaticRenderedView(session, it->ssrc, 640, 400, 30,
+ AddStaticRenderedView(session, it->first_ssrc(), 640, 400, 30,
offset, offset);
}
}
- }
- SendViewRequest(session);
+ SendViewRequest(session);
+ }
}
// TODO: Would these methods to add and remove views make
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 568a818..76184dc 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -47,7 +47,6 @@
class MucInviteRecvTask;
class MucInviteSendTask;
class FriendInviteSendTask;
-class VoicemailJidRequester;
class DiscoInfoQueryTask;
class Muc;
class Status;
@@ -74,7 +73,8 @@
class Call;
class SessionManagerTask;
struct CallOptions;
-struct NamedSource;
+struct MediaStreams;
+struct StreamParams;
}
struct RosterItem {
@@ -98,7 +98,9 @@
class CallClient: public sigslot::has_slots<> {
public:
- explicit CallClient(buzz::XmppClient* xmpp_client);
+ CallClient(buzz::XmppClient* xmpp_client,
+ const std::string& caps_node,
+ const std::string& version);
~CallClient();
cricket::MediaSessionClient* media_client() const { return media_client_; }
@@ -117,6 +119,13 @@
void SetConsole(Console *console) {
console_ = console;
}
+ void SetPriority(int priority) {
+ my_status_.set_priority(priority);
+ }
+ void SendStatus() {
+ SendStatus(my_status_);
+ }
+ void SendStatus(const buzz::Status& status);
void ParseLine(const std::string &str);
@@ -127,7 +136,7 @@
void LookupAndJoinMuc(const std::string& room_name);
void InviteToMuc(const std::string& user, const std::string& room);
bool InMuc();
- const buzz::Jid& FirstMucJid();
+ const buzz::Jid* FirstMucJid();
void LeaveMuc(const std::string& room);
void SetNick(const std::string& muc_nick);
void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
@@ -157,7 +166,6 @@
void InitMedia();
void InitPresence();
- void RefreshStatus();
void OnRequestSignaling();
void OnSessionCreate(cricket::Session* session, bool initiate);
void OnCallCreate(cricket::Call* call);
@@ -180,6 +188,8 @@
void OnRemoteMuted(const std::string& mutee_nick,
const std::string& muter_nick,
bool should_mute_locally);
+ void OnMediaBlocked(const std::string& blockee_nick,
+ const std::string& blocker_nick);
void OnHangoutRequestError(const std::string& node,
const buzz::XmlElement* stanza);
void OnHangoutPublishAudioMuteError(const std::string& task_id,
@@ -192,14 +202,13 @@
const std::string& mutee_nick,
const buzz::XmlElement* stanza);
void OnDevicesChange();
- void OnFoundVoicemailJid(const buzz::Jid& to, const buzz::Jid& voicemail);
- void OnVoicemailJidError(const buzz::Jid& to);
- void OnMediaSourcesUpdate(cricket::Call* call,
+ void OnMediaStreamsUpdate(cricket::Call* call,
cricket::Session* session,
- const cricket::MediaSources& sources);
+ const cricket::MediaStreams& added,
+ const cricket::MediaStreams& removed);
void OnSpeakerChanged(cricket::Call* call,
cricket::Session* session,
- const cricket::NamedSource& speaker_source);
+ const cricket::StreamParams& speaker_stream);
void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
const buzz::MucRoomInfo& room_info);
void OnRoomLookupError(buzz::IqTask* task,
@@ -223,7 +232,6 @@
void PrintRoster();
void MakeCallTo(const std::string& name, const cricket::CallOptions& options);
void PlaceCall(const buzz::Jid& jid, const cricket::CallOptions& options);
- void CallVoicemail(const std::string& name);
void Accept(const cricket::CallOptions& options);
void Reject();
void Quit();
diff --git a/talk/examples/call/callclient_unittest.cc b/talk/examples/call/callclient_unittest.cc
new file mode 100644
index 0000000..0c2fd95
--- /dev/null
+++ b/talk/examples/call/callclient_unittest.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Unit tests for CallClient
+
+#include "talk/base/gunit.h"
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/filemediaengine.h"
+
+TEST(CallClientTest, CreateCallClientWithDefaultMediaEngine) {
+ XmppPump pump;
+ CallClient *client = new CallClient(pump.client(), "app", "version");
+ delete client;
+}
+
+TEST(CallClientTest, CreateCallClientWithFileMediaEngine) {
+ XmppPump pump;
+ CallClient *client = new CallClient(pump.client(), "app", "version");
+ client->SetMediaEngine(new cricket::FileMediaEngine);
+ delete client;
+}
diff --git a/talk/examples/call/mediaenginefactory.cc b/talk/examples/call/mediaenginefactory.cc
new file mode 100644
index 0000000..7bd39c9
--- /dev/null
+++ b/talk/examples/call/mediaenginefactory.cc
@@ -0,0 +1,82 @@
+//
+// libjingle
+// Copyright 2004--2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "talk/examples/call/mediaenginefactory.h"
+
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/filemediaengine.h"
+
+std::vector<cricket::AudioCodec> RequiredAudioCodecs() {
+ std::vector<cricket::AudioCodec> audio_codecs;
+ audio_codecs.push_back(
+ cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
+ audio_codecs.push_back(
+ cricket::AudioCodec(0, "PCMU", 8000, 0, 1, 0));
+ audio_codecs.push_back(
+ cricket::AudioCodec(13, "CN", 8000, 0, 1, 0));
+ audio_codecs.push_back(
+ cricket::AudioCodec(105, "CN", 16000, 0, 1, 0));
+ return audio_codecs;
+}
+
+std::vector<cricket::VideoCodec> RequiredVideoCodecs() {
+ std::vector<cricket::VideoCodec> video_codecs;
+ video_codecs.push_back(
+ cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
+ video_codecs.push_back(
+ cricket::VideoCodec(99, "H264-SVC", 640, 360, 30, 0));
+ return video_codecs;
+}
+
+cricket::MediaEngineInterface* MediaEngineFactory::CreateFileMediaEngine(
+ const char* voice_in, const char* voice_out,
+ const char* video_in, const char* video_out) {
+
+ cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
+ // Set the RTP dump file names.
+ if (voice_in) {
+ file_media_engine->set_voice_input_filename(voice_in);
+ }
+ if (voice_out) {
+ file_media_engine->set_voice_output_filename(voice_out);
+ }
+ if (video_in) {
+ file_media_engine->set_video_input_filename(video_in);
+ }
+ if (video_out) {
+ file_media_engine->set_video_output_filename(video_out);
+ }
+
+ // Set voice and video codecs. TODO: The codecs actually depend on
+ // the the input voice and video streams.
+ file_media_engine->set_voice_codecs(RequiredAudioCodecs());
+ file_media_engine->set_video_codecs(RequiredVideoCodecs());
+
+ return file_media_engine;
+}
diff --git a/talk/examples/call/mediaenginefactory.h b/talk/examples/call/mediaenginefactory.h
new file mode 100644
index 0000000..f7ba68e
--- /dev/null
+++ b/talk/examples/call/mediaenginefactory.h
@@ -0,0 +1,40 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+#define TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
+
+#include "talk/session/phone/mediaengine.h"
+
+class MediaEngineFactory {
+ public:
+ static cricket::MediaEngineInterface* CreateFileMediaEngine(
+ const char* voice_in, const char* voice_out,
+ const char* video_in, const char* video_out);
+};
+
+#endif // TALK_EXAMPLES_CALL_MEDIAENGINEFACTORY_H_
diff --git a/talk/examples/call/presenceouttask.cc b/talk/examples/call/presenceouttask.cc
index 919a1af..222036f 100644
--- a/talk/examples/call/presenceouttask.cc
+++ b/talk/examples/call/presenceouttask.cc
@@ -39,7 +39,9 @@
if (GetState() != STATE_INIT && GetState() != STATE_START)
return XMPP_RETURN_BADSTATE;
- QueueStanza(TranslateStatus(s));
+ XmlElement * presence = TranslateStatus(s);
+ QueueStanza(presence);
+ delete presence;
return XMPP_RETURN_OK;
}
@@ -51,6 +53,7 @@
XmlElement * presence = TranslateStatus(s);
presence->AddAttr(QN_TO, j.Str());
QueueStanza(presence);
+ delete presence;
return XMPP_RETURN_OK;
}
@@ -63,6 +66,7 @@
presence->AddAttr(QN_TYPE, "probe");
QueueStanza(presence);
+ delete presence;
return XMPP_RETURN_OK;
}
@@ -117,9 +121,9 @@
result->AddElement(new XmlElement(QN_PRIORITY));
result->AddText(pri, 1);
- if (s.know_capabilities() && s.is_google_client()) {
+ if (s.know_capabilities()) {
result->AddElement(new XmlElement(QN_CAPS_C, true));
- result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1);
+ result->AddAttr(QN_NODE, s.caps_node(), 1);
result->AddAttr(QN_VER, s.version(), 1);
std::string caps;
diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc
index fba3322..97e0d35 100644
--- a/talk/examples/call/presencepushtask.cc
+++ b/talk/examples/call/presencepushtask.cc
@@ -187,11 +187,8 @@
std::string exts = caps->Attr(QN_EXT);
s->set_know_capabilities(true);
-
- if (node == GOOGLE_CLIENT_NODE) {
- s->set_is_google_client(true);
- s->set_version(ver);
- }
+ s->set_caps_node(node);
+ s->set_version(ver);
if (ListContainsToken(exts, "voice-v1")) {
s->set_voice_capability(true);
diff --git a/talk/examples/call/status.h b/talk/examples/call/status.h
index c3778e5..fb8fbc8 100644
--- a/talk/examples/call/status.h
+++ b/talk/examples/call/status.h
@@ -31,24 +31,22 @@
#include "talk/xmpp/jid.h"
#include "talk/xmpp/constants.h"
-#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps"
-
namespace buzz {
class Status {
public:
- Status() :
- pri_(0),
- show_(SHOW_NONE),
- available_(false),
- e_code_(0),
- feedback_probation_(false),
- know_capabilities_(false),
- voice_capability_(false),
- pmuc_capability_(false),
- video_capability_(false),
- camera_capability_(false),
- is_google_client_(false) {}
+ Status()
+ : pri_(0),
+ show_(SHOW_NONE),
+ available_(false),
+ e_code_(0),
+ feedback_probation_(false),
+ know_capabilities_(false),
+ voice_capability_(false),
+ pmuc_capability_(false),
+ video_capability_(false),
+ camera_capability_(false) {
+ }
~Status() {}
@@ -66,29 +64,29 @@
SHOW_CHAT = 6,
};
- const Jid & jid() const { return jid_; }
+ const Jid& jid() const { return jid_; }
int priority() const { return pri_; }
Show show() const { return show_; }
- const std::string & status() const { return status_; }
- const std::string & nick() const { return nick_; }
+ const std::string& status() const { return status_; }
+ const std::string& nick() const { return nick_; }
bool available() const { return available_ ; }
int error_code() const { return e_code_; }
- const std::string & error_string() const { return e_str_; }
+ const std::string& error_string() const { return e_str_; }
bool know_capabilities() const { return know_capabilities_; }
bool voice_capability() const { return voice_capability_; }
bool pmuc_capability() const { return pmuc_capability_; }
bool video_capability() const { return video_capability_; }
bool camera_capability() const { return camera_capability_; }
- bool is_google_client() const { return is_google_client_; }
- const std::string & version() const { return version_; }
+ const std::string& caps_node() const { return caps_node_; }
+ const std::string& version() const { return version_; }
bool feedback_probation() const { return feedback_probation_; }
const std::string& sent_time() const { return sent_time_; }
- void set_jid(const Jid & jid) { jid_ = jid; }
+ void set_jid(const Jid& jid) { jid_ = jid; }
void set_priority(int pri) { pri_ = pri; }
void set_show(Show show) { show_ = show; }
- void set_status(const std::string & status) { status_ = status; }
- void set_nick(const std::string & nick) { nick_ = nick; }
+ void set_status(const std::string& status) { status_ = status; }
+ void set_nick(const std::string& nick) { nick_ = nick; }
void set_available(bool a) { available_ = a; }
void set_error(int e_code, const std::string e_str)
{ e_code_ = e_code; e_str_ = e_str; }
@@ -97,22 +95,22 @@
void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
void set_video_capability(bool f) { video_capability_ = f; }
void set_camera_capability(bool f) { camera_capability_ = f; }
- void set_is_google_client(bool f) { is_google_client_ = f; }
- void set_version(const std::string & v) { version_ = v; }
+ void set_caps_node(const std::string& f) { caps_node_ = f; }
+ void set_version(const std::string& v) { version_ = v; }
void set_feedback_probation(bool f) { feedback_probation_ = f; }
void set_sent_time(const std::string& time) { sent_time_ = time; }
- void UpdateWith(const Status & new_value) {
+ void UpdateWith(const Status& new_value) {
if (!new_value.know_capabilities()) {
bool k = know_capabilities();
- bool i = is_google_client();
bool p = voice_capability();
+ std::string node = caps_node();
std::string v = version();
*this = new_value;
set_know_capabilities(k);
- set_is_google_client(i);
+ set_caps_node(node);
set_voice_capability(p);
set_version(v);
}
@@ -175,7 +173,7 @@
return result;
}
- static std::string TrimStatus(const std::string & st) {
+ static std::string TrimStatus(const std::string& st) {
std::string s(st);
int j = 0;
bool collapsing = true;
@@ -220,7 +218,7 @@
bool pmuc_capability_;
bool video_capability_;
bool camera_capability_;
- bool is_google_client_;
+ std::string caps_node_;
std::string version_;
std::string sent_time_; // from the jabber:x:delay element
diff --git a/talk/examples/login/autoportallocator.h b/talk/examples/login/autoportallocator.h
new file mode 100644
index 0000000..7e0b062
--- /dev/null
+++ b/talk/examples/login/autoportallocator.h
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
+#define TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/examples/login/jingleinfotask.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/xmppclient.h"
+
+// This class sets the relay and stun servers using XmppClient.
+// It enables the client to traverse Proxy and NAT.
+class AutoPortAllocator : public cricket::HttpPortAllocator {
+ public:
+ AutoPortAllocator(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent)
+ : cricket::HttpPortAllocator(network_manager, user_agent) {
+ }
+
+ // Creates and initiates a task to get relay token from XmppClient and set
+ // it appropriately.
+ void SetXmppClient(buzz::XmppClient* client) {
+ // The JingleInfoTask is freed by the task-runner.
+ buzz::JingleInfoTask* jit = new buzz::JingleInfoTask(client);
+ jit->SignalJingleInfo.connect(this, &AutoPortAllocator::OnJingleInfo);
+ jit->Start();
+ jit->RefreshJingleInfoNow();
+ }
+
+ private:
+ void OnJingleInfo(
+ const std::string& token,
+ const std::vector<std::string>& relay_hosts,
+ const std::vector<talk_base::SocketAddress>& stun_hosts) {
+ SetRelayToken(token);
+ SetStunHosts(stun_hosts);
+ SetRelayHosts(relay_hosts);
+ }
+};
+
+#endif // TALK_EXAMPLES_LOGIN_AUTOPORTALLOCATOR_H_
diff --git a/talk/examples/login/jingleinfotask.cc b/talk/examples/login/jingleinfotask.cc
new file mode 100644
index 0000000..c8932cd
--- /dev/null
+++ b/talk/examples/login/jingleinfotask.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/login/jingleinfotask.h"
+#include "talk/base/socketaddress.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class JingleInfoTask::JingleInfoGetTask : public XmppTask {
+ public:
+ explicit JingleInfoGetTask(XmppTaskParentInterface* parent)
+ : XmppTask(parent, XmppEngine::HL_SINGLE),
+ done_(false) {}
+
+ virtual int ProcessStart() {
+ talk_base::scoped_ptr<XmlElement> get(
+ MakeIq(STR_GET, Jid(), task_id()));
+ get->AddElement(new XmlElement(QN_JINGLE_INFO_QUERY, true));
+ if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+ return STATE_ERROR;
+ }
+ return STATE_RESPONSE;
+ }
+ virtual int ProcessResponse() {
+ if (done_)
+ return STATE_DONE;
+ return STATE_BLOCKED;
+ }
+
+ protected:
+ virtual bool HandleStanza(const XmlElement * stanza) {
+ if (!MatchResponseIq(stanza, Jid(), task_id()))
+ return false;
+
+ if (stanza->Attr(QN_TYPE) != STR_RESULT)
+ return false;
+
+ // Queue the stanza with the parent so these don't get handled out of order
+ JingleInfoTask* parent = static_cast<JingleInfoTask*>(GetParent());
+ parent->QueueStanza(stanza);
+
+ // Wake ourselves so we can go into the done state
+ done_ = true;
+ Wake();
+ return true;
+ }
+
+ bool done_;
+};
+
+
+void JingleInfoTask::RefreshJingleInfoNow() {
+ JingleInfoGetTask* get_task = new JingleInfoGetTask(this);
+ get_task->Start();
+}
+
+bool
+JingleInfoTask::HandleStanza(const XmlElement * stanza) {
+ if (!MatchRequestIq(stanza, "set", QN_JINGLE_INFO_QUERY))
+ return false;
+
+ // only respect relay push from the server
+ Jid from(stanza->Attr(QN_FROM));
+ if (!from.IsEmpty() &&
+ !from.BareEquals(GetClient()->jid()) &&
+ from != Jid(GetClient()->jid().domain()))
+ return false;
+
+ QueueStanza(stanza);
+ return true;
+}
+
+int
+JingleInfoTask::ProcessStart() {
+ std::vector<std::string> relay_hosts;
+ std::vector<talk_base::SocketAddress> stun_hosts;
+ std::string relay_token;
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+ const XmlElement * query = stanza->FirstNamed(QN_JINGLE_INFO_QUERY);
+ if (query == NULL)
+ return STATE_START;
+ const XmlElement *stun = query->FirstNamed(QN_JINGLE_INFO_STUN);
+ if (stun) {
+ for (const XmlElement *server = stun->FirstNamed(QN_JINGLE_INFO_SERVER);
+ server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) {
+ std::string host = server->Attr(QN_JINGLE_INFO_HOST);
+ std::string port = server->Attr(QN_JINGLE_INFO_UDP);
+ if (host != STR_EMPTY && host != STR_EMPTY) {
+ stun_hosts.push_back(talk_base::SocketAddress(host, atoi(port.c_str())));
+ }
+ }
+ }
+
+ const XmlElement *relay = query->FirstNamed(QN_JINGLE_INFO_RELAY);
+ if (relay) {
+ relay_token = relay->TextNamed(QN_JINGLE_INFO_TOKEN);
+ for (const XmlElement *server = relay->FirstNamed(QN_JINGLE_INFO_SERVER);
+ server != NULL; server = server->NextNamed(QN_JINGLE_INFO_SERVER)) {
+ std::string host = server->Attr(QN_JINGLE_INFO_HOST);
+ if (host != STR_EMPTY) {
+ relay_hosts.push_back(host);
+ }
+ }
+ }
+ SignalJingleInfo(relay_token, relay_hosts, stun_hosts);
+ return STATE_START;
+}
+}
diff --git a/talk/examples/login/jingleinfotask.h b/talk/examples/login/jingleinfotask.h
new file mode 100644
index 0000000..dbc3fb0
--- /dev/null
+++ b/talk/examples/login/jingleinfotask.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
+#define TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
+
+#include <vector>
+
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+
+namespace buzz {
+
+class JingleInfoTask : public XmppTask {
+ public:
+ explicit JingleInfoTask(XmppTaskParentInterface* parent) :
+ XmppTask(parent, XmppEngine::HL_TYPE) {}
+
+ virtual int ProcessStart();
+ void RefreshJingleInfoNow();
+
+ sigslot::signal3<const std::string &,
+ const std::vector<std::string> &,
+ const std::vector<talk_base::SocketAddress> &>
+ SignalJingleInfo;
+
+ protected:
+ class JingleInfoGetTask;
+ friend class JingleInfoGetTask;
+
+ virtual bool HandleStanza(const XmlElement * stanza);
+};
+}
+
+#endif // TALK_EXAMPLES_LOGIN_JINGLEINFOTASK_H_
diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc
index 186020a..9cd00b9 100644
--- a/talk/examples/login/login_main.cc
+++ b/talk/examples/login/login_main.cc
@@ -31,6 +31,7 @@
#include "talk/base/thread.h"
#include "talk/xmpp/xmppclientsettings.h"
#include "talk/examples/login/xmppthread.h"
+#include "talk/xmpp/xmppengine.h"
int main(int argc, char **argv) {
std::cout << "Auth Cookie: ";
@@ -48,7 +49,7 @@
buzz::XmppClientSettings xcs;
xcs.set_user(username.c_str());
xcs.set_host("gmail.com");
- xcs.set_use_tls(false);
+ xcs.set_use_tls(buzz::TLS_DISABLED);
xcs.set_auth_cookie(auth_cookie.c_str());
xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
thread.Login(xcs);
diff --git a/talk/examples/login/xmppauth.cc b/talk/examples/login/xmppauth.cc
index 3551b97..7773e00 100644
--- a/talk/examples/login/xmppauth.cc
+++ b/talk/examples/login/xmppauth.cc
@@ -2,26 +2,26 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@@ -37,7 +37,7 @@
XmppAuth::~XmppAuth() {
}
-
+
void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid,
const talk_base::SocketAddress & server,
const talk_base::CryptString & pass,
@@ -49,25 +49,26 @@
SignalAuthDone();
}
-
+
std::string XmppAuth::ChooseBestSaslMechanism(
const std::vector<std::string> & mechanisms,
bool encrypted) {
std::vector<std::string>::const_iterator it;
- // a token is the weakest auth - 15s, service-limited, so prefer it.
+ // A token is the weakest auth - 15s, service-limited, so prefer it.
it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
if (it != mechanisms.end() && !auth_cookie_.empty())
return "X-GOOGLE-TOKEN";
- // a cookie is the next weakest - 14 days
+ // A cookie is the next weakest - 14 days.
it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
if (it != mechanisms.end() && !auth_cookie_.empty())
return "X-GOOGLE-COOKIE";
- it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
- if (it != mechanisms.end())
- return "PLAIN";
+ // As a last resort, use plain authentication.
+ it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it != mechanisms.end())
+ return "PLAIN";
// No good mechanism found
return "";
diff --git a/talk/examples/login/xmpppump.h b/talk/examples/login/xmpppump.h
index 4e79748..6f705ec 100644
--- a/talk/examples/login/xmpppump.h
+++ b/talk/examples/login/xmpppump.h
@@ -31,7 +31,7 @@
#include "talk/base/messagequeue.h"
#include "talk/base/taskrunner.h"
#include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/xmpp/xmppclient.h"
#include "talk/xmpp/xmppengine.h"
#include "talk/xmpp/xmpptask.h"
diff --git a/talk/examples/login/xmppsocket.cc b/talk/examples/login/xmppsocket.cc
index 75325e4..cbd4661 100644
--- a/talk/examples/login/xmppsocket.cc
+++ b/talk/examples/login/xmppsocket.cc
@@ -45,13 +45,13 @@
#endif // FEATURE_ENABLE_SSL
#endif // USE_SSLSTREAM
-XmppSocket::XmppSocket(bool tls) : tls_(tls) {
+XmppSocket::XmppSocket(buzz::TlsOptions tls) : tls_(tls) {
talk_base::Thread* pth = talk_base::Thread::Current();
talk_base::AsyncSocket* socket =
pth->socketserver()->CreateAsyncSocket(SOCK_STREAM);
#ifndef USE_SSLSTREAM
#ifdef FEATURE_ENABLE_SSL
- if (tls_) {
+ if (tls_ != buzz::TLS_DISABLED) {
socket = talk_base::SSLAdapter::Create(socket);
}
#endif // FEATURE_ENABLE_SSL
@@ -65,7 +65,7 @@
cricket_socket_ = socket;
stream_ = new talk_base::SocketStream(cricket_socket_);
#ifdef FEATURE_ENABLE_SSL
- if (tls_)
+ if (tls_ != buzz::TLS_DISABLED)
stream_ = talk_base::SSLStreamAdapter::Create(stream_);
#endif // FEATURE_ENABLE_SSL
stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
@@ -226,7 +226,7 @@
bool XmppSocket::StartTls(const std::string & domainname) {
#if defined(FEATURE_ENABLE_SSL)
- if (!tls_)
+ if (tls_ == buzz::TLS_DISABLED)
return false;
#ifndef USE_SSLSTREAM
talk_base::SSLAdapter* ssl_adapter =
diff --git a/talk/examples/login/xmppsocket.h b/talk/examples/login/xmppsocket.h
index bce8ee3..ed9f6e1 100644
--- a/talk/examples/login/xmppsocket.h
+++ b/talk/examples/login/xmppsocket.h
@@ -32,6 +32,7 @@
#include "talk/base/bytebuffer.h"
#include "talk/base/sigslot.h"
#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppengine.h"
// The below define selects the SSLStreamAdapter implementation for
// SSL, as opposed to the SSLAdapter socket adapter.
@@ -44,7 +45,7 @@
class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
public:
- XmppSocket(bool tls);
+ XmppSocket(buzz::TlsOptions tls);
~XmppSocket();
virtual buzz::AsyncSocket::State state();
@@ -75,7 +76,7 @@
#endif // USE_SSLSTREAM
buzz::AsyncSocket::State state_;
talk_base::ByteBuffer buffer_;
- bool tls_;
+ buzz::TlsOptions tls_;
};
#endif // _XMPPSOCKET_H_
diff --git a/talk/examples/login/xmppthread.cc b/talk/examples/login/xmppthread.cc
index c030391..5345643 100644
--- a/talk/examples/login/xmppthread.cc
+++ b/talk/examples/login/xmppthread.cc
@@ -25,8 +25,9 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "talk/xmpp/xmppclientsettings.h"
#include "talk/examples/login/xmppthread.h"
+
+#include "talk/xmpp/xmppclientsettings.h"
#include "talk/examples/login/xmppauth.h"
namespace {
@@ -70,7 +71,8 @@
if (pmsg->message_id == MSG_LOGIN) {
ASSERT(pmsg->pdata != NULL);
LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
- pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth());
+ pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED),
+ new XmppAuth());
delete data;
} else if (pmsg->message_id == MSG_DISCONNECT) {
pump_->DoDisconnect();
diff --git a/talk/examples/login/xmppthread.h b/talk/examples/login/xmppthread.h
index bfed5e0..efaa67a 100644
--- a/talk/examples/login/xmppthread.h
+++ b/talk/examples/login/xmppthread.h
@@ -25,13 +25,15 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _XMPPTHREAD_H_
-#define _XMPPTHREAD_H_
+#ifndef TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
+#define TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
-#include "talk/xmpp/xmppclientsettings.h"
#include "talk/base/thread.h"
#include "talk/examples/login/xmpppump.h"
#include "talk/examples/login/xmppsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
class XmppThread:
public talk_base::Thread, XmppPumpNotify, talk_base::MessageHandler {
@@ -53,4 +55,4 @@
void OnMessage(talk_base::Message* pmsg);
};
-#endif // _XMPPTHREAD_H_
+#endif // TALK_EXAMPLES_LOGIN_XMPPTHREAD_H_
diff --git a/talk/examples/pcp/pcp_main.cc b/talk/examples/pcp/pcp_main.cc
new file mode 100644
index 0000000..3ec263f
--- /dev/null
+++ b/talk/examples/pcp/pcp_main.cc
@@ -0,0 +1,709 @@
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#include <time.h>
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+#define USE_SSL_TUNNEL
+#endif
+
+#include "talk/base/basicdefs.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/examples/login/autoportallocator.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/login/xmppsocket.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#include "talk/xmpp/xmppengine.h"
+#ifdef USE_SSL_TUNNEL
+#include "talk/session/tunnel/securetunnelsessionclient.h"
+#endif
+#include "talk/session/tunnel/tunnelsessionclient.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+#ifndef MAX_PATH
+#define MAX_PATH 256
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+// The following are necessary to properly link when compiling STL without
+// /EHsc, otherwise known as C++ exceptions.
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler = 0;
+#endif
+
+enum {
+ MSG_LOGIN_COMPLETE = 1,
+ MSG_LOGIN_FAILED,
+ MSG_DONE,
+};
+
+buzz::Jid gUserJid;
+talk_base::InsecureCryptStringImpl gUserPass;
+std::string gXmppHost = "talk.google.com";
+int gXmppPort = 5222;
+buzz::TlsOptions gXmppUseTls = buzz::TLS_REQUIRED;
+
+class DebugLog : public sigslot::has_slots<> {
+public:
+ DebugLog() :
+ debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
+ debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
+ censor_password_(false)
+ {}
+ char * debug_input_buf_;
+ int debug_input_len_;
+ int debug_input_alloc_;
+ char * debug_output_buf_;
+ int debug_output_len_;
+ int debug_output_alloc_;
+ bool censor_password_;
+
+ void Input(const char * data, int len) {
+ if (debug_input_len_ + len > debug_input_alloc_) {
+ char * old_buf = debug_input_buf_;
+ debug_input_alloc_ = 4096;
+ while (debug_input_alloc_ < debug_input_len_ + len) {
+ debug_input_alloc_ *= 2;
+ }
+ debug_input_buf_ = new char[debug_input_alloc_];
+ memcpy(debug_input_buf_, old_buf, debug_input_len_);
+ delete[] old_buf;
+ }
+ memcpy(debug_input_buf_ + debug_input_len_, data, len);
+ debug_input_len_ += len;
+ DebugPrint(debug_input_buf_, &debug_input_len_, false);
+ }
+
+ void Output(const char * data, int len) {
+ if (debug_output_len_ + len > debug_output_alloc_) {
+ char * old_buf = debug_output_buf_;
+ debug_output_alloc_ = 4096;
+ while (debug_output_alloc_ < debug_output_len_ + len) {
+ debug_output_alloc_ *= 2;
+ }
+ debug_output_buf_ = new char[debug_output_alloc_];
+ memcpy(debug_output_buf_, old_buf, debug_output_len_);
+ delete[] old_buf;
+ }
+ memcpy(debug_output_buf_ + debug_output_len_, data, len);
+ debug_output_len_ += len;
+ DebugPrint(debug_output_buf_, &debug_output_len_, true);
+ }
+
+ static bool
+ IsAuthTag(const char * str, size_t len) {
+ if (str[0] == '<' && str[1] == 'a' &&
+ str[2] == 'u' &&
+ str[3] == 't' &&
+ str[4] == 'h' &&
+ str[5] <= ' ') {
+ std::string tag(str, len);
+
+ if (tag.find("mechanism") != std::string::npos)
+ return true;
+
+ }
+ return false;
+ }
+
+ void
+ DebugPrint(char * buf, int * plen, bool output) {
+ int len = *plen;
+ if (len > 0) {
+ time_t tim = time(NULL);
+ struct tm * now = localtime(&tim);
+ char *time_string = asctime(now);
+ if (time_string) {
+ size_t time_len = strlen(time_string);
+ if (time_len > 0) {
+ time_string[time_len-1] = 0; // trim off terminating \n
+ }
+ }
+ LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<<<<<<<<<<")
+ << " : " << time_string;
+
+ bool indent;
+ int start = 0, nest = 3;
+ for (int i = 0; i < len; i += 1) {
+ if (buf[i] == '>') {
+ if ((i > 0) && (buf[i-1] == '/')) {
+ indent = false;
+ } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
+ indent = false;
+ nest -= 2;
+ } else {
+ indent = true;
+ }
+
+ // Output a tag
+ LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i + 1 - start);
+
+ if (indent)
+ nest += 2;
+
+ // Note if it's a PLAIN auth tag
+ if (IsAuthTag(buf + start, i + 1 - start)) {
+ censor_password_ = true;
+ }
+
+ // incr
+ start = i + 1;
+ }
+
+ if (buf[i] == '<' && start < i) {
+ if (censor_password_) {
+ LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+ censor_password_ = false;
+ }
+ else {
+ LOG(INFO) << std::setw(nest) << " " << std::string(buf + start, i - start);
+ }
+ start = i;
+ }
+ }
+ len = len - start;
+ memcpy(buf, buf + start, len);
+ *plen = len;
+ }
+ }
+
+};
+
+static DebugLog debug_log_;
+
+// Prints out a usage message then exits.
+void Usage() {
+ std::cerr << "Usage:" << std::endl;
+ std::cerr << " pcp [options] <my_jid> (server mode)" << std::endl;
+ std::cerr << " pcp [options] <my_jid> <src_file> <dst_full_jid>:<dst_file> (client sending)" << std::endl;
+ std::cerr << " pcp [options] <my_jid> <src_full_jid>:<src_file> <dst_file> (client rcv'ing)" << std::endl;
+ std::cerr << " --verbose" << std::endl;
+ std::cerr << " --xmpp-host=<host>" << std::endl;
+ std::cerr << " --xmpp-port=<port>" << std::endl;
+ std::cerr << " --xmpp-use-tls=(true|false)" << std::endl;
+ exit(1);
+}
+
+// Prints out an error message, a usage message, then exits.
+void Error(const std::string& msg) {
+ std::cerr << "error: " << msg << std::endl;
+ std::cerr << std::endl;
+ Usage();
+}
+
+void FatalError(const std::string& msg) {
+ std::cerr << "error: " << msg << std::endl;
+ std::cerr << std::endl;
+ exit(1);
+}
+
+// Determines whether the given string is an option. If so, the name and
+// value are appended to the given strings.
+bool ParseArg(const char* arg, std::string* name, std::string* value) {
+ if (strncmp(arg, "--", 2) != 0)
+ return false;
+
+ const char* eq = strchr(arg + 2, '=');
+ if (eq) {
+ if (name)
+ name->append(arg + 2, eq);
+ if (value)
+ value->append(eq + 1, arg + strlen(arg));
+ } else {
+ if (name)
+ name->append(arg + 2, arg + strlen(arg));
+ if (value)
+ value->clear();
+ }
+
+ return true;
+}
+
+int ParseIntArg(const std::string& name, const std::string& value) {
+ char* end;
+ long val = strtol(value.c_str(), &end, 10);
+ if (*end != '\0')
+ Error(std::string("value of option ") + name + " must be an integer");
+ return static_cast<int>(val);
+}
+
+#ifdef WIN32
+#pragma warning(push)
+// disable "unreachable code" warning b/c it varies between dbg and opt
+#pragma warning(disable: 4702)
+#endif
+bool ParseBoolArg(const std::string& name, const std::string& value) {
+ if (value == "true")
+ return true;
+ else if (value == "false")
+ return false;
+ else {
+ Error(std::string("value of option ") + name + " must be true or false");
+ return false;
+ }
+}
+#ifdef WIN32
+#pragma warning(pop)
+#endif
+
+void ParseFileArg(const char* arg, buzz::Jid* jid, std::string* file) {
+ const char* sep = strchr(arg, ':');
+ if (!sep) {
+ *file = arg;
+ } else {
+ buzz::Jid jid_arg(std::string(arg, sep-arg));
+ if (jid_arg.IsBare())
+ Error("A full JID is required for the source or destination arguments.");
+ *jid = jid_arg;
+ *file = std::string(sep+1);
+ }
+}
+
+
+void SetConsoleEcho(bool on) {
+#ifdef WIN32
+ HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+ if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+ return;
+
+ DWORD mode;
+ if (!GetConsoleMode(hIn, &mode))
+ return;
+
+ if (on) {
+ mode = mode | ENABLE_ECHO_INPUT;
+ } else {
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ }
+
+ SetConsoleMode(hIn, mode);
+#else
+ int re;
+ if (on)
+ re = system("stty echo");
+ else
+ re = system("stty -echo");
+ if (-1 == re)
+ return;
+#endif
+}
+
+// Fills in a settings object with the values from the arguments.
+buzz::XmppClientSettings LoginSettings() {
+ buzz::XmppClientSettings xcs;
+ xcs.set_user(gUserJid.node());
+ xcs.set_host(gUserJid.domain());
+ xcs.set_resource("pcp");
+ xcs.set_pass(talk_base::CryptString(gUserPass));
+ talk_base::SocketAddress server(gXmppHost, gXmppPort);
+ xcs.set_server(server);
+ xcs.set_use_tls(gXmppUseTls);
+ return xcs;
+}
+
+// Runs the current thread until a message with the given ID is seen.
+uint32 Loop(const std::vector<uint32>& ids) {
+ talk_base::Message msg;
+ while (talk_base::Thread::Current()->Get(&msg)) {
+ if (msg.phandler == NULL) {
+ if (std::find(ids.begin(), ids.end(), msg.message_id) != ids.end())
+ return msg.message_id;
+ std::cout << "orphaned message: " << msg.message_id;
+ continue;
+ }
+ talk_base::Thread::Current()->Dispatch(&msg);
+ }
+ return 0;
+}
+
+#ifdef WIN32
+#pragma warning(disable:4355)
+#endif
+
+class CustomXmppPump : public XmppPumpNotify, public XmppPump {
+public:
+ CustomXmppPump() : XmppPump(this), server_(false) { }
+
+ void Serve(cricket::TunnelSessionClient* client) {
+ client->SignalIncomingTunnel.connect(this,
+ &CustomXmppPump::OnIncomingTunnel);
+ server_ = true;
+ }
+
+ void OnStateChange(buzz::XmppEngine::State state) {
+ switch (state) {
+ case buzz::XmppEngine::STATE_START:
+ std::cout << "connecting..." << std::endl;
+ break;
+ case buzz::XmppEngine::STATE_OPENING:
+ std::cout << "logging in..." << std::endl;
+ break;
+ case buzz::XmppEngine::STATE_OPEN:
+ std::cout << "logged in..." << std::endl;
+ talk_base::Thread::Current()->Post(NULL, MSG_LOGIN_COMPLETE);
+ break;
+ case buzz::XmppEngine::STATE_CLOSED:
+ std::cout << "logged out..." << std::endl;
+ talk_base::Thread::Current()->Post(NULL, MSG_LOGIN_FAILED);
+ break;
+ }
+ }
+
+ void OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid,
+ std::string description, cricket::Session* session) {
+ std::cout << "IncomingTunnel from " << jid.Str()
+ << ": " << description << std::endl;
+ if (!server_ || (file_.get() != NULL)) {
+ client->DeclineTunnel(session);
+ return;
+ }
+ std::string filename;
+ bool send;
+ if (strncmp(description.c_str(), "send:", 5) == 0) {
+ send = true;
+ } else if (strncmp(description.c_str(), "recv:", 5) == 0) {
+ send = false;
+ } else {
+ client->DeclineTunnel(session);
+ return;
+ }
+ filename = description.substr(5);
+ talk_base::StreamInterface* stream = client->AcceptTunnel(session);
+ if (!ProcessStream(stream, filename, send))
+ talk_base::Thread::Current()->Post(NULL, MSG_DONE);
+
+ // TODO: There is a potential memory leak, however, since the PCP
+ // app doesn't work right now, I can't verify the fix actually works, so
+ // comment out the following line until we fix the PCP app.
+
+ // delete stream;
+ }
+
+ bool ProcessStream(talk_base::StreamInterface* stream,
+ const std::string& filename, bool send) {
+ ASSERT(file_.get() == NULL);
+ sending_ = send;
+ file_.reset(new talk_base::FileStream);
+ buffer_len_ = 0;
+ int err;
+ if (!file_->Open(filename.c_str(), sending_ ? "rb" : "wb", &err)) {
+ std::cerr << "Error opening <" << filename << ">: "
+ << std::strerror(err) << std::endl;
+ return false;
+ }
+ stream->SignalEvent.connect(this, &CustomXmppPump::OnStreamEvent);
+ if (stream->GetState() == talk_base::SS_CLOSED) {
+ std::cerr << "Failed to establish P2P tunnel" << std::endl;
+ return false;
+ }
+ if (stream->GetState() == talk_base::SS_OPEN) {
+ OnStreamEvent(stream,
+ talk_base::SE_OPEN | talk_base::SE_READ | talk_base::SE_WRITE, 0);
+ }
+ return true;
+ }
+
+ void OnStreamEvent(talk_base::StreamInterface* stream, int events,
+ int error) {
+ if (events & talk_base::SE_CLOSE) {
+ if (error == 0) {
+ std::cout << "Tunnel closed normally" << std::endl;
+ } else {
+ std::cout << "Tunnel closed with error: " << error << std::endl;
+ }
+ Cleanup(stream);
+ return;
+ }
+ if (events & talk_base::SE_OPEN) {
+ std::cout << "Tunnel connected" << std::endl;
+ }
+ talk_base::StreamResult result;
+ size_t count;
+ if (sending_ && (events & talk_base::SE_WRITE)) {
+ LOG(LS_VERBOSE) << "Tunnel SE_WRITE";
+ while (true) {
+ size_t write_pos = 0;
+ while (write_pos < buffer_len_) {
+ result = stream->Write(buffer_ + write_pos, buffer_len_ - write_pos,
+ &count, &error);
+ if (result == talk_base::SR_SUCCESS) {
+ write_pos += count;
+ continue;
+ }
+ if (result == talk_base::SR_BLOCK) {
+ buffer_len_ -= write_pos;
+ memmove(buffer_, buffer_ + write_pos, buffer_len_);
+ LOG(LS_VERBOSE) << "Tunnel write block";
+ return;
+ }
+ if (result == talk_base::SR_EOS) {
+ std::cout << "Tunnel closed unexpectedly on write" << std::endl;
+ } else {
+ std::cout << "Tunnel write error: " << error << std::endl;
+ }
+ Cleanup(stream);
+ return;
+ }
+ buffer_len_ = 0;
+ while (buffer_len_ < sizeof(buffer_)) {
+ result = file_->Read(buffer_ + buffer_len_,
+ sizeof(buffer_) - buffer_len_,
+ &count, &error);
+ if (result == talk_base::SR_SUCCESS) {
+ buffer_len_ += count;
+ continue;
+ }
+ if (result == talk_base::SR_EOS) {
+ if (buffer_len_ > 0)
+ break;
+ std::cout << "End of file" << std::endl;
+ // A hack until we have friendly shutdown
+ Cleanup(stream, true);
+ return;
+ } else if (result == talk_base::SR_BLOCK) {
+ std::cout << "File blocked unexpectedly on read" << std::endl;
+ } else {
+ std::cout << "File read error: " << error << std::endl;
+ }
+ Cleanup(stream);
+ return;
+ }
+ }
+ }
+ if (!sending_ && (events & talk_base::SE_READ)) {
+ LOG(LS_VERBOSE) << "Tunnel SE_READ";
+ while (true) {
+ buffer_len_ = 0;
+ while (buffer_len_ < sizeof(buffer_)) {
+ result = stream->Read(buffer_ + buffer_len_,
+ sizeof(buffer_) - buffer_len_,
+ &count, &error);
+ if (result == talk_base::SR_SUCCESS) {
+ buffer_len_ += count;
+ continue;
+ }
+ if (result == talk_base::SR_BLOCK) {
+ if (buffer_len_ > 0)
+ break;
+ LOG(LS_VERBOSE) << "Tunnel read block";
+ return;
+ }
+ if (result == talk_base::SR_EOS) {
+ std::cout << "Tunnel closed unexpectedly on read" << std::endl;
+ } else {
+ std::cout << "Tunnel read error: " << error << std::endl;
+ }
+ Cleanup(stream);
+ return;
+ }
+ size_t write_pos = 0;
+ while (write_pos < buffer_len_) {
+ result = file_->Write(buffer_ + write_pos, buffer_len_ - write_pos,
+ &count, &error);
+ if (result == talk_base::SR_SUCCESS) {
+ write_pos += count;
+ continue;
+ }
+ if (result == talk_base::SR_EOS) {
+ std::cout << "File closed unexpectedly on write" << std::endl;
+ } else if (result == talk_base::SR_BLOCK) {
+ std::cout << "File blocked unexpectedly on write" << std::endl;
+ } else {
+ std::cout << "File write error: " << error << std::endl;
+ }
+ Cleanup(stream);
+ return;
+ }
+ }
+ }
+ }
+
+ void Cleanup(talk_base::StreamInterface* stream, bool delay = false) {
+ LOG(LS_VERBOSE) << "Closing";
+ stream->Close();
+ file_.reset();
+ if (!server_) {
+ if (delay)
+ talk_base::Thread::Current()->PostDelayed(2000, NULL, MSG_DONE);
+ else
+ talk_base::Thread::Current()->Post(NULL, MSG_DONE);
+ }
+ }
+
+private:
+ bool server_, sending_;
+ talk_base::scoped_ptr<talk_base::FileStream> file_;
+ char buffer_[1024 * 64];
+ size_t buffer_len_;
+};
+
+int main(int argc, char **argv) {
+ talk_base::LogMessage::LogThreads();
+ talk_base::LogMessage::LogTimestamps();
+
+ // TODO: Default the username to the current users's name.
+
+ // Parse the arguments.
+
+ int index = 1;
+ while (index < argc) {
+ std::string name, value;
+ if (!ParseArg(argv[index], &name, &value))
+ break;
+
+ if (name == "help") {
+ Usage();
+ } else if (name == "verbose") {
+ talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+ } else if (name == "xmpp-host") {
+ gXmppHost = value;
+ } else if (name == "xmpp-port") {
+ gXmppPort = ParseIntArg(name, value);
+ } else if (name == "xmpp-use-tls") {
+ gXmppUseTls = ParseBoolArg(name, value)?
+ buzz::TLS_REQUIRED : buzz::TLS_DISABLED;
+ } else {
+ Error(std::string("unknown option: ") + name);
+ }
+
+ index += 1;
+ }
+
+ if (index >= argc)
+ Error("bad arguments");
+ gUserJid = buzz::Jid(argv[index++]);
+ if (!gUserJid.IsValid())
+ Error("bad arguments");
+
+ char path[MAX_PATH];
+#if WIN32
+ GetCurrentDirectoryA(MAX_PATH, path);
+#else
+ if (NULL == getcwd(path, MAX_PATH))
+ Error("Unable to get current path");
+#endif
+
+ std::cout << "Directory: " << std::string(path) << std::endl;
+
+ buzz::Jid gSrcJid;
+ buzz::Jid gDstJid;
+ std::string gSrcFile;
+ std::string gDstFile;
+
+ bool as_server = true;
+ if (index + 2 == argc) {
+ ParseFileArg(argv[index], &gSrcJid, &gSrcFile);
+ ParseFileArg(argv[index+1], &gDstJid, &gDstFile);
+ if(gSrcJid.Str().empty() == gDstJid.Str().empty())
+ Error("Exactly one of source JID or destination JID must be empty.");
+ as_server = false;
+ } else if (index != argc) {
+ Error("bad arguments");
+ }
+
+ std::cout << "Password: ";
+ SetConsoleEcho(false);
+ std::cin >> gUserPass.password();
+ SetConsoleEcho(true);
+ std::cout << std::endl;
+
+ talk_base::InitializeSSL();
+ // Log in.
+ CustomXmppPump pump;
+ pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+ pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+ pump.DoLogin(LoginSettings(), new XmppSocket(gXmppUseTls), 0);
+ //new XmppAuth());
+
+ // Wait until login succeeds.
+ std::vector<uint32> ids;
+ ids.push_back(MSG_LOGIN_COMPLETE);
+ ids.push_back(MSG_LOGIN_FAILED);
+ if (MSG_LOGIN_FAILED == Loop(ids))
+ FatalError("Failed to connect");
+
+ {
+ talk_base::scoped_ptr<buzz::XmlElement> presence(
+ new buzz::XmlElement(buzz::QN_PRESENCE));
+ presence->AddElement(new buzz::XmlElement(buzz::QN_PRIORITY));
+ presence->AddText("-1", 1);
+ pump.SendStanza(presence.get());
+ }
+
+ std::string user_jid_str = pump.client()->jid().Str();
+ std::cout << "Logged in as " << user_jid_str << std::endl;
+
+ // Prepare the random number generator.
+ talk_base::InitRandom(user_jid_str.c_str(), user_jid_str.size());
+
+ // Create the P2P session manager.
+ talk_base::BasicNetworkManager network_manager;
+ AutoPortAllocator allocator(&network_manager, "pcp_agent");
+ allocator.SetXmppClient(pump.client());
+ cricket::SessionManager session_manager(&allocator);
+#ifdef USE_SSL_TUNNEL
+ cricket::SecureTunnelSessionClient session_client(pump.client()->jid(),
+ &session_manager);
+ if (!session_client.GenerateIdentity())
+ FatalError("Failed to generate SSL identity");
+#else // !USE_SSL_TUNNEL
+ cricket::TunnelSessionClient session_client(pump.client()->jid(),
+ &session_manager);
+#endif // USE_SSL_TUNNEL
+ cricket::SessionManagerTask *receiver =
+ new cricket::SessionManagerTask(pump.client(), &session_manager);
+ receiver->EnableOutgoingMessages();
+ receiver->Start();
+
+ bool success = true;
+
+ // Establish the appropriate connection.
+ if (as_server) {
+ pump.Serve(&session_client);
+ } else {
+ talk_base::StreamInterface* stream = NULL;
+ std::string filename;
+ bool sending;
+ if (gSrcJid.Str().empty()) {
+ std::string message("recv:");
+ message.append(gDstFile);
+ stream = session_client.CreateTunnel(gDstJid, message);
+ filename = gSrcFile;
+ sending = true;
+ } else {
+ std::string message("send:");
+ message.append(gSrcFile);
+ stream = session_client.CreateTunnel(gSrcJid, message);
+ filename = gDstFile;
+ sending = false;
+ }
+ success = pump.ProcessStream(stream, filename, sending);
+ }
+
+ if (success) {
+ // Wait until the copy is done.
+ ids.clear();
+ ids.push_back(MSG_DONE);
+ ids.push_back(MSG_LOGIN_FAILED);
+ Loop(ids);
+ }
+
+ // Log out.
+ pump.DoDisconnect();
+
+ return 0;
+}
diff --git a/talk/examples/peerconnection/client/conductor.cc b/talk/examples/peerconnection/client/conductor.cc
new file mode 100644
index 0000000..3557f11
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.cc
@@ -0,0 +1,399 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/client/conductor.h"
+
+#include <utility>
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture_factory.h"
+#else
+#include "third_party/webrtc/files/include/video_capture_factory.h"
+#endif
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/videorendererfactory.h"
+
+Conductor::Conductor(PeerConnectionClient* client, MainWindow* main_wnd)
+ : peer_id_(-1),
+ client_(client),
+ main_wnd_(main_wnd) {
+ client_->RegisterObserver(this);
+ main_wnd->RegisterObserver(this);
+}
+
+Conductor::~Conductor() {
+ ASSERT(peer_connection_.get() == NULL);
+}
+
+bool Conductor::connection_active() const {
+ return peer_connection_.get() != NULL;
+}
+
+void Conductor::Close() {
+ client_->SignOut();
+ DeletePeerConnection();
+}
+
+bool Conductor::InitializePeerConnection() {
+ ASSERT(peer_connection_factory_.get() == NULL);
+ ASSERT(peer_connection_.get() == NULL);
+
+ peer_connection_factory_ = webrtc::CreatePeerConnectionFactory();
+
+ if (!peer_connection_factory_.get()) {
+ main_wnd_->MessageBox("Error",
+ "Failed to initialize PeerConnectionFactory", true);
+ DeletePeerConnection();
+ return false;
+ }
+
+ peer_connection_ = peer_connection_factory_->CreatePeerConnection(
+ GetPeerConnectionString(), this);
+
+ if (!peer_connection_.get()) {
+ main_wnd_->MessageBox("Error",
+ "CreatePeerConnection failed", true);
+ DeletePeerConnection();
+ }
+ return peer_connection_.get() != NULL;
+}
+
+void Conductor::DeletePeerConnection() {
+ peer_connection_ = NULL;
+ active_streams_.clear();
+ peer_connection_factory_ = NULL;
+ peer_id_ = -1;
+}
+
+void Conductor::EnsureStreamingUI() {
+ ASSERT(peer_connection_.get() != NULL);
+ if (main_wnd_->IsWindow()) {
+ if (main_wnd_->current_ui() != MainWindow::STREAMING)
+ main_wnd_->SwitchToStreamingUI();
+ }
+}
+
+//
+// PeerConnectionObserver implementation.
+//
+
+void Conductor::OnError() {
+ LOG(LS_ERROR) << __FUNCTION__;
+ main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_ERROR, NULL);
+}
+
+void Conductor::OnSignalingMessage(const std::string& msg) {
+ LOG(INFO) << __FUNCTION__;
+
+ std::string* msg_copy = new std::string(msg);
+ main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg_copy);
+}
+
+// Called when a remote stream is added
+void Conductor::OnAddStream(webrtc::MediaStreamInterface* stream) {
+ LOG(INFO) << __FUNCTION__ << " " << stream->label();
+
+ stream->AddRef();
+ main_wnd_->QueueUIThreadCallback(NEW_STREAM_ADDED,
+ stream);
+}
+
+void Conductor::OnRemoveStream(webrtc::MediaStreamInterface* stream) {
+ LOG(INFO) << __FUNCTION__ << " " << stream->label();
+ stream->AddRef();
+ main_wnd_->QueueUIThreadCallback(STREAM_REMOVED,
+ stream);
+}
+
+//
+// PeerConnectionClientObserver implementation.
+//
+
+void Conductor::OnSignedIn() {
+ LOG(INFO) << __FUNCTION__;
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnDisconnected() {
+ LOG(INFO) << __FUNCTION__;
+
+ DeletePeerConnection();
+
+ if (main_wnd_->IsWindow())
+ main_wnd_->SwitchToConnectUI();
+}
+
+void Conductor::OnPeerConnected(int id, const std::string& name) {
+ LOG(INFO) << __FUNCTION__;
+ // Refresh the list if we're showing it.
+ if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::OnPeerDisconnected(int id) {
+ LOG(INFO) << __FUNCTION__;
+ if (id == peer_id_) {
+ LOG(INFO) << "Our peer disconnected";
+ main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
+ } else {
+ // Refresh the list if we're showing it.
+ if (main_wnd_->current_ui() == MainWindow::LIST_PEERS)
+ main_wnd_->SwitchToPeerList(client_->peers());
+ }
+}
+
+void Conductor::OnMessageFromPeer(int peer_id, const std::string& message) {
+ ASSERT(peer_id_ == peer_id || peer_id_ == -1);
+ ASSERT(!message.empty());
+
+ if (!peer_connection_.get()) {
+ ASSERT(peer_id_ == -1);
+ peer_id_ = peer_id;
+
+ // Got an offer. Give it to the PeerConnection instance.
+ // Once processed, we will get a callback to OnSignalingMessage with
+ // our 'answer' which we'll send to the peer.
+ LOG(INFO) << "Got an offer from our peer: " << peer_id;
+ if (!InitializePeerConnection()) {
+ LOG(LS_ERROR) << "Failed to initialize our PeerConnection instance";
+ client_->SignOut();
+ return;
+ }
+ } else if (peer_id != peer_id_) {
+ ASSERT(peer_id_ != -1);
+ LOG(WARNING) << "Received an offer from a peer while already in a "
+ "conversation with a different peer.";
+ return;
+ }
+
+ peer_connection_->ProcessSignalingMessage(message);
+}
+
+void Conductor::OnMessageSent(int err) {
+ // Process the next pending message if any.
+ main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, NULL);
+}
+
+//
+// MainWndCallback implementation.
+//
+
+bool Conductor::StartLogin(const std::string& server, int port) {
+ if (client_->is_connected())
+ return false;
+
+ if (!client_->Connect(server, port, GetPeerName())) {
+ main_wnd_->MessageBox("Error", ("Failed to connect to " + server).c_str(),
+ true);
+ return false;
+ }
+
+ return true;
+}
+
+void Conductor::DisconnectFromServer() {
+ if (client_->is_connected())
+ client_->SignOut();
+}
+
+void Conductor::ConnectToPeer(int peer_id) {
+ ASSERT(peer_id_ == -1);
+ ASSERT(peer_id != -1);
+
+ if (peer_connection_.get()) {
+ main_wnd_->MessageBox("Error",
+ "We only support connecting to one peer at a time", true);
+ return;
+ }
+
+ if (InitializePeerConnection()) {
+ peer_id_ = peer_id;
+ AddStreams();
+ } else {
+ main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
+ }
+}
+
+talk_base::scoped_refptr<webrtc::VideoCaptureModule>
+Conductor::OpenVideoCaptureDevice() {
+ webrtc::VideoCaptureModule::DeviceInfo* device_info(
+ webrtc::VideoCaptureFactory::CreateDeviceInfo(0));
+ talk_base::scoped_refptr<webrtc::VideoCaptureModule> video_device;
+
+ const size_t kMaxDeviceNameLength = 128;
+ const size_t kMaxUniqueIdLength = 256;
+ uint8 device_name[kMaxDeviceNameLength];
+ uint8 unique_id[kMaxUniqueIdLength];
+
+ const size_t device_count = device_info->NumberOfDevices();
+ for (size_t i = 0; i < device_count; ++i) {
+ // Get the name of the video capture device.
+ device_info->GetDeviceName(i, device_name, kMaxDeviceNameLength, unique_id,
+ kMaxUniqueIdLength);
+ // Try to open this device.
+ video_device =
+ webrtc::VideoCaptureFactory::Create(0, unique_id);
+ if (video_device.get())
+ break;
+ }
+ delete device_info;
+ return video_device;
+}
+
+void Conductor::AddStreams() {
+ if (active_streams_.find(kStreamLabel) != active_streams_.end())
+ return; // Already added.
+
+ talk_base::scoped_refptr<webrtc::LocalAudioTrackInterface> audio_track(
+ peer_connection_factory_->CreateLocalAudioTrack(kAudioLabel, NULL));
+
+ talk_base::scoped_refptr<webrtc::LocalVideoTrackInterface> video_track(
+ peer_connection_factory_->CreateLocalVideoTrack(
+ kVideoLabel, CreateVideoCapturer(OpenVideoCaptureDevice())));
+
+ video_track->SetRenderer(main_wnd_->local_renderer());
+
+ talk_base::scoped_refptr<webrtc::LocalMediaStreamInterface> stream =
+ peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);
+
+ stream->AddTrack(audio_track);
+ stream->AddTrack(video_track);
+ peer_connection_->AddStream(stream);
+ peer_connection_->CommitStreamChanges();
+ typedef std::pair<std::string,
+ talk_base::scoped_refptr<webrtc::MediaStreamInterface> >
+ MediaStreamPair;
+ active_streams_.insert(MediaStreamPair(stream->label(), stream));
+ main_wnd_->SwitchToStreamingUI();
+}
+
+void Conductor::DisconnectFromCurrentPeer() {
+ LOG(INFO) << __FUNCTION__;
+ if (peer_connection_.get()) {
+ client_->SendHangUp(peer_id_);
+ DeletePeerConnection();
+ }
+
+ if (main_wnd_->IsWindow())
+ main_wnd_->SwitchToPeerList(client_->peers());
+}
+
+void Conductor::UIThreadCallback(int msg_id, void* data) {
+ switch (msg_id) {
+ case PEER_CONNECTION_CLOSED:
+ LOG(INFO) << "PEER_CONNECTION_CLOSED";
+ DeletePeerConnection();
+
+ ASSERT(active_streams_.empty());
+
+ if (main_wnd_->IsWindow()) {
+ if (client_->is_connected()) {
+ main_wnd_->SwitchToPeerList(client_->peers());
+ } else {
+ main_wnd_->SwitchToConnectUI();
+ }
+ } else {
+ DisconnectFromServer();
+ }
+ break;
+
+ case SEND_MESSAGE_TO_PEER: {
+ LOG(INFO) << "SEND_MESSAGE_TO_PEER";
+ std::string* msg = reinterpret_cast<std::string*>(data);
+ if (msg) {
+ // For convenience, we always run the message through the queue.
+ // This way we can be sure that messages are sent to the server
+ // in the same order they were signaled without much hassle.
+ pending_messages_.push_back(msg);
+ }
+
+ if (!pending_messages_.empty() && !client_->IsSendingMessage()) {
+ msg = pending_messages_.front();
+ pending_messages_.pop_front();
+
+ if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
+ LOG(LS_ERROR) << "SendToPeer failed";
+ DisconnectFromServer();
+ }
+ delete msg;
+ }
+
+ if (!peer_connection_.get())
+ peer_id_ = -1;
+
+ break;
+ }
+
+ case PEER_CONNECTION_ADDSTREAMS:
+ AddStreams();
+ break;
+
+ case PEER_CONNECTION_ERROR:
+ main_wnd_->MessageBox("Error", "an unknown error occurred", true);
+ break;
+
+ case NEW_STREAM_ADDED: {
+ webrtc::MediaStreamInterface* stream =
+ reinterpret_cast<webrtc::MediaStreamInterface*>(
+ data);
+ talk_base::scoped_refptr<webrtc::VideoTracks> tracks =
+ stream->video_tracks();
+ for (size_t i = 0; i < tracks->count(); ++i) {
+ webrtc::VideoTrackInterface* track = tracks->at(i);
+ LOG(INFO) << "Setting video renderer for track: " << track->label();
+ track->SetRenderer(main_wnd_->remote_renderer());
+ }
+ // If we haven't shared any streams with this peer (we're the receiver)
+ // then do so now.
+ if (active_streams_.empty())
+ AddStreams();
+ stream->Release();
+ break;
+ }
+
+ case STREAM_REMOVED: {
+ webrtc::MediaStreamInterface* stream =
+ reinterpret_cast<webrtc::MediaStreamInterface*>(
+ data);
+ active_streams_.erase(stream->label());
+ stream->Release();
+ if (active_streams_.empty()) {
+ LOG(INFO) << "All streams have been closed.";
+ main_wnd_->QueueUIThreadCallback(PEER_CONNECTION_CLOSED, NULL);
+ }
+ break;
+ }
+
+ default:
+ ASSERT(false);
+ break;
+ }
+}
diff --git a/talk/examples/peerconnection/client/conductor.h b/talk/examples/peerconnection/client/conductor.h
new file mode 100644
index 0000000..4268d74
--- /dev/null
+++ b/talk/examples/peerconnection/client/conductor.h
@@ -0,0 +1,134 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
+#pragma once
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace webrtc {
+class VideoCaptureModule;
+} // namespace webrtc
+
+namespace cricket {
+class VideoRenderer;
+} // namespace cricket
+
+class Conductor
+ : public webrtc::PeerConnectionObserver,
+ public PeerConnectionClientObserver,
+ public MainWndCallback {
+ public:
+ enum CallbackID {
+ MEDIA_CHANNELS_INITIALIZED = 1,
+ PEER_CONNECTION_CLOSED,
+ SEND_MESSAGE_TO_PEER,
+ PEER_CONNECTION_ADDSTREAMS,
+ PEER_CONNECTION_ERROR,
+ NEW_STREAM_ADDED,
+ STREAM_REMOVED,
+ };
+
+ Conductor(PeerConnectionClient* client, MainWindow* main_wnd);
+ ~Conductor();
+
+ bool connection_active() const;
+
+ virtual void Close();
+
+ protected:
+ bool InitializePeerConnection();
+ void DeletePeerConnection();
+ void EnsureStreamingUI();
+ void AddStreams();
+ talk_base::scoped_refptr<webrtc::VideoCaptureModule> OpenVideoCaptureDevice();
+
+ //
+ // PeerConnectionObserver implementation.
+ //
+ virtual void OnError();
+ virtual void OnMessage(const std::string& msg) {}
+ virtual void OnSignalingMessage(const std::string& msg);
+ virtual void OnStateChange(
+ webrtc::PeerConnectionObserver::StateType state_changed) {}
+ virtual void OnAddStream(webrtc::MediaStreamInterface* stream);
+ virtual void OnRemoveStream(webrtc::MediaStreamInterface* stream);
+
+
+ //
+ // PeerConnectionClientObserver implementation.
+ //
+
+ virtual void OnSignedIn();
+
+ virtual void OnDisconnected();
+
+ virtual void OnPeerConnected(int id, const std::string& name);
+
+ virtual void OnPeerDisconnected(int id);
+
+ virtual void OnMessageFromPeer(int peer_id, const std::string& message);
+
+ virtual void OnMessageSent(int err);
+
+ //
+ // MainWndCallback implementation.
+ //
+
+ virtual bool StartLogin(const std::string& server, int port);
+
+ virtual void DisconnectFromServer();
+
+ virtual void ConnectToPeer(int peer_id);
+
+ virtual void DisconnectFromCurrentPeer();
+
+ virtual void UIThreadCallback(int msg_id, void* data);
+
+ protected:
+ int peer_id_;
+ talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
+ talk_base::scoped_refptr<webrtc::PeerConnectionFactoryInterface>
+ peer_connection_factory_;
+ PeerConnectionClient* client_;
+ MainWindow* main_wnd_;
+ std::deque<std::string*> pending_messages_;
+ std::map<std::string, talk_base::scoped_refptr<webrtc::MediaStreamInterface> >
+ active_streams_;
+};
+
+#endif // PEERCONNECTION_SAMPLES_CLIENT_CONDUCTOR_H_
diff --git a/talk/examples/peerconnection/client/defaults.cc b/talk/examples/peerconnection/client/defaults.cc
new file mode 100644
index 0000000..eb895d0
--- /dev/null
+++ b/talk/examples/peerconnection/client/defaults.cc
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/client/defaults.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "talk/base/common.h"
+
+const char kAudioLabel[] = "audio_label";
+const char kVideoLabel[] = "video_label";
+const char kStreamLabel[] = "stream_label";
+const uint16 kDefaultServerPort = 8888;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+ const char* default_value) {
+ std::string value;
+ const char* env_var = getenv(env_var_name);
+ if (env_var)
+ value = env_var;
+
+ if (value.empty())
+ value = default_value;
+
+ return value;
+}
+
+std::string GetPeerConnectionString() {
+ return GetEnvVarOrDefault("WEBRTC_CONNECT", "STUN stun.l.google.com:19302");
+}
+
+std::string GetDefaultServerName() {
+ return GetEnvVarOrDefault("WEBRTC_SERVER", "localhost");
+}
+
+std::string GetPeerName() {
+ char computer_name[256];
+ if (gethostname(computer_name, ARRAY_SIZE(computer_name)) != 0)
+ strcpy(computer_name, "host");
+ std::string ret(GetEnvVarOrDefault("USERNAME", "user"));
+ ret += '@';
+ ret += computer_name;
+ return ret;
+}
diff --git a/talk/examples/peerconnection/client/defaults.h b/talk/examples/peerconnection/client/defaults.h
new file mode 100644
index 0000000..f646149
--- /dev/null
+++ b/talk/examples/peerconnection/client/defaults.h
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
+#pragma once
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+extern const char kAudioLabel[];
+extern const char kVideoLabel[];
+extern const char kStreamLabel[];
+extern const uint16 kDefaultServerPort;
+
+std::string GetEnvVarOrDefault(const char* env_var_name,
+ const char* default_value);
+std::string GetPeerConnectionString();
+std::string GetDefaultServerName();
+std::string GetPeerName();
+
+#endif // PEERCONNECTION_SAMPLES_CLIENT_DEFAULTS_H_
diff --git a/talk/examples/peerconnection/client/linux/main.cc b/talk/examples/peerconnection/client/linux/main.cc
new file mode 100644
index 0000000..ab912ab
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main.cc
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gtk/gtk.h>
+
+#include "talk/examples/peerconnection/client/conductor.h"
+#include "talk/examples/peerconnection/client/linux/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+
+#include "talk/base/thread.h"
+
+class CustomSocketServer : public talk_base::PhysicalSocketServer {
+ public:
+ CustomSocketServer(talk_base::Thread* thread, GtkMainWnd* wnd)
+ : thread_(thread), wnd_(wnd), conductor_(NULL), client_(NULL) {}
+ virtual ~CustomSocketServer() {}
+
+ void set_client(PeerConnectionClient* client) { client_ = client; }
+ void set_conductor(Conductor* conductor) { conductor_ = conductor; }
+
+ // Override so that we can also pump the GTK message loop.
+ virtual bool Wait(int cms, bool process_io) {
+ // Pump GTK events.
+ // TODO: We really should move either the socket server or UI to a
+ // different thread. Alternatively we could look at merging the two loops
+ // by implementing a dispatcher for the socket server and/or use
+ // g_main_context_set_poll_func.
+ while (gtk_events_pending())
+ gtk_main_iteration();
+
+ if (!wnd_->IsWindow() && !conductor_->connection_active() &&
+ client_ != NULL && !client_->is_connected()) {
+ thread_->Quit();
+ }
+ return talk_base::PhysicalSocketServer::Wait(0/*cms == -1 ? 1 : cms*/,
+ process_io);
+ }
+
+ protected:
+ talk_base::Thread* thread_;
+ GtkMainWnd* wnd_;
+ Conductor* conductor_;
+ PeerConnectionClient* client_;
+};
+
+int main(int argc, char* argv[]) {
+ gtk_init(&argc, &argv);
+ g_type_init();
+ g_thread_init(NULL);
+
+ GtkMainWnd wnd;
+ wnd.Create();
+
+ talk_base::AutoThread auto_thread;
+ talk_base::Thread* thread = talk_base::Thread::Current();
+ CustomSocketServer socket_server(thread, &wnd);
+ thread->set_socketserver(&socket_server);
+
+ // Must be constructed after we set the socketserver.
+ PeerConnectionClient client;
+ Conductor conductor(&client, &wnd);
+ socket_server.set_client(&client);
+ socket_server.set_conductor(&conductor);
+
+ thread->Run();
+
+ // gtk_main();
+ wnd.Destroy();
+
+ thread->set_socketserver(NULL);
+ // TODO: Run the Gtk main loop to tear down the connection.
+ //while (gtk_events_pending()) {
+ // gtk_main_iteration();
+ //}
+
+ return 0;
+}
+
diff --git a/talk/examples/peerconnection/client/linux/main_wnd.cc b/talk/examples/peerconnection/client/linux/main_wnd.cc
new file mode 100644
index 0000000..3335985
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.cc
@@ -0,0 +1,482 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "talk/examples/peerconnection/client/linux/main_wnd.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <stddef.h>
+
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+
+using talk_base::sprintfn;
+
+namespace {
+
+//
+// Simple static functions that simply forward the callback to the
+// GtkMainWnd instance.
+//
+
+gboolean OnDestroyedCallback(GtkWidget* widget, GdkEvent* event,
+ gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnDestroyed(widget, event);
+ return FALSE;
+}
+
+void OnClickedCallback(GtkWidget* widget, gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnClicked(widget);
+}
+
+gboolean OnKeyPressCallback(GtkWidget* widget, GdkEventKey* key,
+ gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnKeyPress(widget, key);
+ return false;
+}
+
+void OnRowActivatedCallback(GtkTreeView* tree_view, GtkTreePath* path,
+ GtkTreeViewColumn* column, gpointer data) {
+ reinterpret_cast<GtkMainWnd*>(data)->OnRowActivated(tree_view, path, column);
+}
+
+// Creates a tree view, that we use to display the list of peers.
+void InitializeList(GtkWidget* list) {
+ GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
+ GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes(
+ "List Items", renderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+ GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+ g_object_unref(store);
+}
+
+// Adds an entry to a tree view.
+void AddToList(GtkWidget* list, const gchar* str, int value) {
+ GtkListStore* store = GTK_LIST_STORE(
+ gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
+
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, str, 1, value, -1);
+}
+
+struct UIThreadCallbackData {
+ explicit UIThreadCallbackData(MainWndCallback* cb, int id, void* d)
+ : callback(cb), msg_id(id), data(d) {}
+ MainWndCallback* callback;
+ int msg_id;
+ void* data;
+};
+
+gboolean HandleUIThreadCallback(gpointer data) {
+ UIThreadCallbackData* cb_data = reinterpret_cast<UIThreadCallbackData*>(data);
+ cb_data->callback->UIThreadCallback(cb_data->msg_id, cb_data->data);
+ delete cb_data;
+ return false;
+}
+
+gboolean Redraw(gpointer data) {
+ GtkMainWnd* wnd = reinterpret_cast<GtkMainWnd*>(data);
+ wnd->OnRedraw();
+ return false;
+}
+} // end anonymous
+
+//
+// GtkMainWnd implementation.
+//
+
+GtkMainWnd::GtkMainWnd()
+ : window_(NULL), draw_area_(NULL), vbox_(NULL), server_edit_(NULL),
+ port_edit_(NULL), peer_list_(NULL), callback_(NULL),
+ server_("localhost") {
+ char buffer[10];
+ sprintfn(buffer, sizeof(buffer), "%i", kDefaultServerPort);
+ port_ = buffer;
+}
+
+GtkMainWnd::~GtkMainWnd() {
+ ASSERT(!IsWindow());
+}
+
+void GtkMainWnd::RegisterObserver(MainWndCallback* callback) {
+ callback_ = callback;
+}
+
+bool GtkMainWnd::IsWindow() {
+ return window_ != NULL && GTK_IS_WINDOW(window_);
+}
+
+void GtkMainWnd::MessageBox(const char* caption, const char* text,
+ bool is_error) {
+ GtkWidget* dialog = gtk_message_dialog_new(GTK_WINDOW(window_),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ is_error ? GTK_MESSAGE_ERROR : GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE, "%s", text);
+ gtk_window_set_title(GTK_WINDOW(dialog), caption);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+MainWindow::UI GtkMainWnd::current_ui() {
+ if (vbox_)
+ return CONNECT_TO_SERVER;
+
+ if (peer_list_)
+ return LIST_PEERS;
+
+ return STREAMING;
+}
+
+webrtc::VideoRendererWrapperInterface* GtkMainWnd::local_renderer() {
+ if (!local_renderer_wrapper_.get())
+ local_renderer_wrapper_ =
+ webrtc::CreateVideoRenderer(new VideoRenderer(this));
+ return local_renderer_wrapper_.get();
+}
+
+webrtc::VideoRendererWrapperInterface* GtkMainWnd::remote_renderer() {
+ if (!remote_renderer_wrapper_.get())
+ remote_renderer_wrapper_ =
+ webrtc::CreateVideoRenderer(new VideoRenderer(this));
+ return remote_renderer_wrapper_.get();
+}
+
+void GtkMainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+ g_idle_add(HandleUIThreadCallback,
+ new UIThreadCallbackData(callback_, msg_id, data));
+}
+
+bool GtkMainWnd::Create() {
+ ASSERT(window_ == NULL);
+
+ window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ if (window_) {
+ gtk_window_set_position(GTK_WINDOW(window_), GTK_WIN_POS_CENTER);
+ gtk_window_set_default_size(GTK_WINDOW(window_), 640, 480);
+ gtk_window_set_title(GTK_WINDOW(window_), "PeerConnection client");
+ g_signal_connect(G_OBJECT(window_), "delete-event",
+ G_CALLBACK(&OnDestroyedCallback), this);
+ g_signal_connect(window_, "key-press-event", G_CALLBACK(OnKeyPressCallback),
+ this);
+
+ SwitchToConnectUI();
+ }
+
+ return window_ != NULL;
+}
+
+bool GtkMainWnd::Destroy() {
+ if (!IsWindow())
+ return false;
+
+ gtk_widget_destroy(window_);
+ window_ = NULL;
+
+ return true;
+}
+
+void GtkMainWnd::SwitchToConnectUI() {
+ LOG(INFO) << __FUNCTION__;
+
+ ASSERT(IsWindow());
+ ASSERT(vbox_ == NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 10);
+
+ if (peer_list_) {
+ gtk_widget_destroy(peer_list_);
+ peer_list_ = NULL;
+ }
+
+ vbox_ = gtk_vbox_new(FALSE, 5);
+ GtkWidget* valign = gtk_alignment_new(0, 1, 0, 0);
+ gtk_container_add(GTK_CONTAINER(vbox_), valign);
+ gtk_container_add(GTK_CONTAINER(window_), vbox_);
+
+ GtkWidget* hbox = gtk_hbox_new(FALSE, 5);
+
+ GtkWidget* label = gtk_label_new("Server");
+ gtk_container_add(GTK_CONTAINER(hbox), label);
+
+ server_edit_ = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(server_edit_), server_.c_str());
+ gtk_widget_set_size_request(server_edit_, 400, 30);
+ gtk_container_add(GTK_CONTAINER(hbox), server_edit_);
+
+ port_edit_ = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(port_edit_), port_.c_str());
+ gtk_widget_set_size_request(port_edit_, 70, 30);
+ gtk_container_add(GTK_CONTAINER(hbox), port_edit_);
+
+ GtkWidget* button = gtk_button_new_with_label("Connect");
+ gtk_widget_set_size_request(button, 70, 30);
+ g_signal_connect(button, "clicked", G_CALLBACK(OnClickedCallback), this);
+ gtk_container_add(GTK_CONTAINER(hbox), button);
+
+ GtkWidget* halign = gtk_alignment_new(1, 0, 0, 0);
+ gtk_container_add(GTK_CONTAINER(halign), hbox);
+ gtk_box_pack_start(GTK_BOX(vbox_), halign, FALSE, FALSE, 0);
+
+ gtk_widget_show_all(window_);
+}
+
+void GtkMainWnd::SwitchToPeerList(const Peers& peers) {
+ LOG(INFO) << __FUNCTION__;
+
+ // Clean up buffers from a potential previous session.
+ local_renderer_wrapper_ = NULL;
+ remote_renderer_wrapper_ = NULL;
+
+ if (!peer_list_) {
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+ if (vbox_) {
+ gtk_widget_destroy(vbox_);
+ vbox_ = NULL;
+ server_edit_ = NULL;
+ port_edit_ = NULL;
+ } else if (draw_area_) {
+ gtk_widget_destroy(draw_area_);
+ draw_area_ = NULL;
+ draw_buffer_.reset();
+ }
+
+ peer_list_ = gtk_tree_view_new();
+ g_signal_connect(peer_list_, "row-activated",
+ G_CALLBACK(OnRowActivatedCallback), this);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(peer_list_), FALSE);
+ InitializeList(peer_list_);
+ gtk_container_add(GTK_CONTAINER(window_), peer_list_);
+ gtk_widget_show_all(window_);
+ } else {
+ GtkListStore* store =
+ GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(peer_list_)));
+ gtk_list_store_clear(store);
+ }
+
+ AddToList(peer_list_, "List of currently connected peers:", -1);
+ for (Peers::const_iterator i = peers.begin(); i != peers.end(); ++i)
+ AddToList(peer_list_, i->second.c_str(), i->first);
+}
+
+void GtkMainWnd::SwitchToStreamingUI() {
+ LOG(INFO) << __FUNCTION__;
+
+ ASSERT(draw_area_ == NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window_), 0);
+ if (peer_list_) {
+ gtk_widget_destroy(peer_list_);
+ peer_list_ = NULL;
+ }
+
+ draw_area_ = gtk_drawing_area_new();
+ gtk_container_add(GTK_CONTAINER(window_), draw_area_);
+
+ gtk_widget_show_all(window_);
+}
+
+void GtkMainWnd::OnDestroyed(GtkWidget* widget, GdkEvent* event) {
+ callback_->Close();
+ window_ = NULL;
+ draw_area_ = NULL;
+ vbox_ = NULL;
+ server_edit_ = NULL;
+ port_edit_ = NULL;
+ peer_list_ = NULL;
+}
+
+void GtkMainWnd::OnClicked(GtkWidget* widget) {
+ server_ = gtk_entry_get_text(GTK_ENTRY(server_edit_));
+ port_ = gtk_entry_get_text(GTK_ENTRY(port_edit_));
+ int port = port_.length() ? atoi(port_.c_str()) : 0;
+ callback_->StartLogin(server_, port);
+}
+
+void GtkMainWnd::OnKeyPress(GtkWidget* widget, GdkEventKey* key) {
+ if (key->type == GDK_KEY_PRESS) {
+ switch (key->keyval) {
+ case GDK_Escape:
+ if (draw_area_) {
+ callback_->DisconnectFromCurrentPeer();
+ } else if (peer_list_) {
+ callback_->DisconnectFromServer();
+ }
+ break;
+
+ case GDK_KP_Enter:
+ case GDK_Return:
+ if (vbox_) {
+ OnClicked(NULL);
+ } else if (peer_list_) {
+ // OnRowActivated will be called automatically when the user
+ // presses enter.
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void GtkMainWnd::OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path,
+ GtkTreeViewColumn* column) {
+ ASSERT(peer_list_ != NULL);
+ GtkTreeIter iter;
+ GtkTreeModel* model;
+ GtkTreeSelection* selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ char* text;
+ int id = -1;
+ gtk_tree_model_get(model, &iter, 0, &text, 1, &id, -1);
+ if (id != -1)
+ callback_->ConnectToPeer(id);
+ g_free(text);
+ }
+}
+
+void GtkMainWnd::OnRedraw() {
+ gdk_threads_enter();
+
+ VideoRenderer* remote_renderer =
+ static_cast<VideoRenderer*>(remote_renderer_wrapper_->renderer());
+ if (remote_renderer && remote_renderer->image() != NULL &&
+ draw_area_ != NULL) {
+ int width = remote_renderer->width();
+ int height = remote_renderer->height();
+
+ if (!draw_buffer_.get()) {
+ draw_buffer_size_ = (width * height * 4) * 4;
+ draw_buffer_.reset(new uint8[draw_buffer_size_]);
+ gtk_widget_set_size_request(draw_area_, width * 2, height * 2);
+ }
+
+ const uint32* image = reinterpret_cast<const uint32*>(
+ remote_renderer->image());
+ uint32* scaled = reinterpret_cast<uint32*>(draw_buffer_.get());
+ for (int r = 0; r < height; ++r) {
+ for (int c = 0; c < width; ++c) {
+ int x = c * 2;
+ scaled[x] = scaled[x + 1] = image[c];
+ }
+
+ uint32* prev_line = scaled;
+ scaled += width * 2;
+ memcpy(scaled, prev_line, (width * 2) * 4);
+
+ image += width;
+ scaled += width * 2;
+ }
+
+ VideoRenderer* local_renderer =
+ static_cast<VideoRenderer*>(local_renderer_wrapper_->renderer());
+ if (local_renderer && local_renderer->image()) {
+ image = reinterpret_cast<const uint32*>(local_renderer->image());
+ scaled = reinterpret_cast<uint32*>(draw_buffer_.get());
+ // Position the local preview on the right side.
+ scaled += (width * 2) - (local_renderer->width() / 2);
+ // right margin...
+ scaled -= 10;
+ // ... towards the bottom.
+ scaled += (height * width * 4) -
+ ((local_renderer->height() / 2) *
+ (local_renderer->width() / 2) * 4);
+ // bottom margin...
+ scaled -= (width * 2) * 5;
+ for (int r = 0; r < local_renderer->height(); r += 2) {
+ for (int c = 0; c < local_renderer->width(); c += 2) {
+ scaled[c / 2] = image[c + r * local_renderer->width()];
+ }
+ scaled += width * 2;
+ }
+ }
+
+ gdk_draw_rgb_32_image(draw_area_->window,
+ draw_area_->style->fg_gc[GTK_STATE_NORMAL],
+ 0,
+ 0,
+ width * 2,
+ height * 2,
+ GDK_RGB_DITHER_MAX,
+ draw_buffer_.get(),
+ (width * 2) * 4);
+ }
+
+ gdk_threads_leave();
+}
+
+GtkMainWnd::VideoRenderer::VideoRenderer(GtkMainWnd* main_wnd)
+ : width_(0), height_(0), main_wnd_(main_wnd) {
+}
+
+GtkMainWnd::VideoRenderer::~VideoRenderer() {
+}
+
+bool GtkMainWnd::VideoRenderer::SetSize(int width, int height, int reserved) {
+ gdk_threads_enter();
+ width_ = width;
+ height_ = height;
+ image_.reset(new uint8[width * height * 4]);
+ gdk_threads_leave();
+ return true;
+}
+
+bool GtkMainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) {
+ gdk_threads_enter();
+
+ int size = width_ * height_ * 4;
+ // TODO: Convert directly to RGBA
+ frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+ image_.get(),
+ size,
+ width_ * 4);
+ // Convert the B,G,R,A frame to R,G,B,A, which is accepted by GTK.
+ // The 'A' is just padding for GTK, so we can use it as temp.
+ uint8* pix = image_.get();
+ uint8* end = image_.get() + size;
+ while (pix < end) {
+ pix[3] = pix[0]; // Save B to A.
+ pix[0] = pix[2]; // Set Red.
+ pix[2] = pix[3]; // Set Blue.
+ pix[3] = 0xFF; // Fixed Alpha.
+ pix += 4;
+ }
+
+ gdk_threads_leave();
+
+ g_idle_add(Redraw, main_wnd_);
+
+ return true;
+}
+
+
diff --git a/talk/examples/peerconnection/client/linux/main_wnd.h b/talk/examples/peerconnection/client/linux/main_wnd.h
new file mode 100644
index 0000000..c0f020a
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.h
@@ -0,0 +1,134 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
+
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+
+// Forward declarations.
+typedef struct _GtkWidget GtkWidget;
+typedef union _GdkEvent GdkEvent;
+typedef struct _GdkEventKey GdkEventKey;
+typedef struct _GtkTreeView GtkTreeView;
+typedef struct _GtkTreePath GtkTreePath;
+typedef struct _GtkTreeViewColumn GtkTreeViewColumn;
+
+// Implements the main UI of the peer connection client.
+// This is functionally equivalent to the MainWnd class in the Windows
+// implementation.
+class GtkMainWnd : public MainWindow {
+ public:
+ GtkMainWnd();
+ ~GtkMainWnd();
+
+ virtual void RegisterObserver(MainWndCallback* callback);
+ virtual bool IsWindow();
+ virtual void SwitchToConnectUI();
+ virtual void SwitchToPeerList(const Peers& peers);
+ virtual void SwitchToStreamingUI();
+ virtual void MessageBox(const char* caption, const char* text,
+ bool is_error);
+ virtual MainWindow::UI current_ui();
+ virtual webrtc::VideoRendererWrapperInterface* local_renderer();
+ virtual webrtc::VideoRendererWrapperInterface* remote_renderer();
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+ // Creates and shows the main window with the |Connect UI| enabled.
+ bool Create();
+
+ // Destroys the window. When the window is destroyed, it ends the
+ // main message loop.
+ bool Destroy();
+
+ // Callback for when the main window is destroyed.
+ void OnDestroyed(GtkWidget* widget, GdkEvent* event);
+
+ // Callback for when the user clicks the "Connect" button.
+ void OnClicked(GtkWidget* widget);
+
+ // Callback for keystrokes. Used to capture Esc and Return.
+ void OnKeyPress(GtkWidget* widget, GdkEventKey* key);
+
+ // Callback when the user double clicks a peer in order to initiate a
+ // connection.
+ void OnRowActivated(GtkTreeView* tree_view, GtkTreePath* path,
+ GtkTreeViewColumn* column);
+
+ void OnRedraw();
+
+ protected:
+ class VideoRenderer : public cricket::VideoRenderer {
+ public:
+ explicit VideoRenderer(GtkMainWnd* main_wnd);
+ virtual ~VideoRenderer();
+
+ virtual bool SetSize(int width, int height, int reserved);
+
+ virtual bool RenderFrame(const cricket::VideoFrame* frame);
+
+ const uint8* image() const {
+ return image_.get();
+ }
+
+ int width() const {
+ return width_;
+ }
+
+ int height() const {
+ return height_;
+ }
+
+ protected:
+ talk_base::scoped_array<uint8> image_;
+ int width_;
+ int height_;
+ GtkMainWnd* main_wnd_;
+ };
+
+ protected:
+ GtkWidget* window_; // Our main window.
+ GtkWidget* draw_area_; // The drawing surface for rendering video streams.
+ GtkWidget* vbox_; // Container for the Connect UI.
+ GtkWidget* server_edit_;
+ GtkWidget* port_edit_;
+ GtkWidget* peer_list_; // The list of peers.
+ MainWndCallback* callback_;
+ std::string server_;
+ std::string port_;
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ local_renderer_wrapper_;
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ remote_renderer_wrapper_;
+ talk_base::scoped_ptr<uint8> draw_buffer_;
+ int draw_buffer_size_;
+};
+
+#endif // PEERCONNECTION_SAMPLES_CLIENT_LINUX_MAIN_WND_H_
diff --git a/talk/examples/peerconnection/client/main.cc b/talk/examples/peerconnection/client/main.cc
new file mode 100644
index 0000000..8d2bdc9
--- /dev/null
+++ b/talk/examples/peerconnection/client/main.cc
@@ -0,0 +1,68 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/client/conductor.h"
+#include "talk/examples/peerconnection/client/main_wnd.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/base/win32socketinit.h"
+
+
+int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
+ wchar_t* cmd_line, int cmd_show) {
+ talk_base::EnsureWinsockInit();
+
+ MainWnd wnd;
+ if (!wnd.Create()) {
+ ASSERT(false);
+ return -1;
+ }
+
+ PeerConnectionClient client;
+ Conductor conductor(&client, &wnd);
+
+ // Main loop.
+ MSG msg;
+ BOOL gm;
+ while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+ if (!wnd.PreTranslateMessage(&msg)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+
+ if (conductor.connection_active() || client.is_connected()) {
+ while ((conductor.connection_active() || client.is_connected()) &&
+ (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
+ if (!wnd.PreTranslateMessage(&msg)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/talk/examples/peerconnection/client/main_wnd.cc b/talk/examples/peerconnection/client/main_wnd.cc
new file mode 100644
index 0000000..367922e
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -0,0 +1,610 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/client/main_wnd.h"
+
+#include <math.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/examples/peerconnection/client/defaults.h"
+
+ATOM MainWnd::wnd_class_ = 0;
+const wchar_t MainWnd::kClassName[] = L"WebRTC_MainWnd";
+
+namespace {
+
+const char kConnecting[] = "Connecting... ";
+const char kNoVideoStreams[] = "(no video streams either way)";
+const char kNoIncomingStream[] = "(no incoming video)";
+
+void CalculateWindowSizeForText(HWND wnd, const wchar_t* text,
+ size_t* width, size_t* height) {
+ HDC dc = ::GetDC(wnd);
+ RECT text_rc = {0};
+ ::DrawText(dc, text, -1, &text_rc, DT_CALCRECT | DT_SINGLELINE);
+ ::ReleaseDC(wnd, dc);
+ RECT client, window;
+ ::GetClientRect(wnd, &client);
+ ::GetWindowRect(wnd, &window);
+
+ *width = text_rc.right - text_rc.left;
+ *width += (window.right - window.left) -
+ (client.right - client.left);
+ *height = text_rc.bottom - text_rc.top;
+ *height += (window.bottom - window.top) -
+ (client.bottom - client.top);
+}
+
+HFONT GetDefaultFont() {
+ static HFONT font = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
+ return font;
+}
+
+std::string GetWindowText(HWND wnd) {
+ char text[MAX_PATH] = {0};
+ ::GetWindowTextA(wnd, &text[0], ARRAYSIZE(text));
+ return text;
+}
+
+void AddListBoxItem(HWND listbox, const std::string& str, LPARAM item_data) {
+ LRESULT index = ::SendMessageA(listbox, LB_ADDSTRING, 0,
+ reinterpret_cast<LPARAM>(str.c_str()));
+ ::SendMessageA(listbox, LB_SETITEMDATA, index, item_data);
+}
+
+} // namespace
+
+MainWnd::MainWnd()
+ : ui_(CONNECT_TO_SERVER), wnd_(NULL), edit1_(NULL), edit2_(NULL),
+ label1_(NULL), label2_(NULL), button_(NULL), listbox_(NULL),
+ destroyed_(false), callback_(NULL), nested_msg_(NULL) {
+}
+
+MainWnd::~MainWnd() {
+ ASSERT(!IsWindow());
+}
+
+bool MainWnd::Create() {
+ ASSERT(wnd_ == NULL);
+ if (!RegisterWindowClass())
+ return false;
+
+ ui_thread_id_ = ::GetCurrentThreadId();
+ wnd_ = ::CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, kClassName, L"WebRTC",
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ NULL, NULL, GetModuleHandle(NULL), this);
+
+ ::SendMessage(wnd_, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+ TRUE);
+
+ CreateChildWindows();
+ SwitchToConnectUI();
+
+ return wnd_ != NULL;
+}
+
+bool MainWnd::Destroy() {
+ BOOL ret = FALSE;
+ if (IsWindow()) {
+ ret = ::DestroyWindow(wnd_);
+ }
+
+ return ret != FALSE;
+}
+
+void MainWnd::RegisterObserver(MainWndCallback* callback) {
+ callback_ = callback;
+}
+
+bool MainWnd::IsWindow() {
+ return wnd_ && ::IsWindow(wnd_) != FALSE;
+}
+
+bool MainWnd::PreTranslateMessage(MSG* msg) {
+ bool ret = false;
+ if (msg->message == WM_CHAR) {
+ if (msg->wParam == VK_TAB) {
+ HandleTabbing();
+ ret = true;
+ } else if (msg->wParam == VK_RETURN) {
+ OnDefaultAction();
+ ret = true;
+ } else if (msg->wParam == VK_ESCAPE) {
+ if (callback_) {
+ if (ui_ == STREAMING) {
+ callback_->DisconnectFromCurrentPeer();
+ } else {
+ callback_->DisconnectFromServer();
+ }
+ }
+ }
+ } else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) {
+ callback_->UIThreadCallback(static_cast<int>(msg->wParam),
+ reinterpret_cast<void*>(msg->lParam));
+ ret = true;
+ }
+ return ret;
+}
+
+void MainWnd::SwitchToConnectUI() {
+ ASSERT(IsWindow());
+ LayoutPeerListUI(false);
+ ui_ = CONNECT_TO_SERVER;
+ LayoutConnectUI(true);
+ ::SetFocus(edit1_);
+}
+
+void MainWnd::SwitchToPeerList(const Peers& peers) {
+ // Clean up buffers from a potential previous session.
+ local_renderer_wrapper_ = NULL;
+ remote_renderer_wrapper_ = NULL;
+
+ LayoutConnectUI(false);
+
+ ::SendMessage(listbox_, LB_RESETCONTENT, 0, 0);
+
+ AddListBoxItem(listbox_, "List of currently connected peers:", -1);
+ Peers::const_iterator i = peers.begin();
+ for (; i != peers.end(); ++i)
+ AddListBoxItem(listbox_, i->second.c_str(), i->first);
+
+ ui_ = LIST_PEERS;
+ LayoutPeerListUI(true);
+ ::SetFocus(listbox_);
+}
+
+void MainWnd::SwitchToStreamingUI() {
+ LayoutConnectUI(false);
+ LayoutPeerListUI(false);
+ ui_ = STREAMING;
+}
+
+void MainWnd::MessageBox(const char* caption, const char* text, bool is_error) {
+ DWORD flags = MB_OK;
+ if (is_error)
+ flags |= MB_ICONERROR;
+
+ ::MessageBoxA(handle(), text, caption, flags);
+}
+
+webrtc::VideoRendererWrapperInterface* MainWnd::local_renderer() {
+ if (!local_renderer_wrapper_.get())
+ local_renderer_wrapper_ =
+ webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+ return local_renderer_wrapper_.get();
+}
+
+webrtc::VideoRendererWrapperInterface* MainWnd::remote_renderer() {
+ if (!remote_renderer_wrapper_.get())
+ remote_renderer_wrapper_ =
+ webrtc::CreateVideoRenderer(new VideoRenderer(handle(), 1, 1));
+ return remote_renderer_wrapper_.get();
+}
+
+void MainWnd::QueueUIThreadCallback(int msg_id, void* data) {
+ ::PostThreadMessage(ui_thread_id_, UI_THREAD_CALLBACK,
+ static_cast<WPARAM>(msg_id), reinterpret_cast<LPARAM>(data));
+}
+
+void MainWnd::OnPaint() {
+ PAINTSTRUCT ps;
+ ::BeginPaint(handle(), &ps);
+
+ RECT rc;
+ ::GetClientRect(handle(), &rc);
+
+ webrtc::VideoRendererWrapperInterface* renderer_wrapper = local_renderer();
+ VideoRenderer* local_renderer = renderer_wrapper ?
+ static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+ renderer_wrapper = remote_renderer();
+ VideoRenderer* remote_renderer = renderer_wrapper ?
+ static_cast<VideoRenderer*>(renderer_wrapper->renderer()) : NULL;
+ if (ui_ == STREAMING && remote_renderer && local_renderer) {
+ AutoLock<VideoRenderer> local_lock(local_renderer);
+ AutoLock<VideoRenderer> remote_lock(remote_renderer);
+
+ const BITMAPINFO& bmi = remote_renderer->bmi();
+ int height = abs(bmi.bmiHeader.biHeight);
+ int width = bmi.bmiHeader.biWidth;
+
+ const uint8* image = remote_renderer->image();
+ if (image != NULL) {
+ HDC dc_mem = ::CreateCompatibleDC(ps.hdc);
+ ::SetStretchBltMode(dc_mem, HALFTONE);
+
+ // Set the map mode so that the ratio will be maintained for us.
+ HDC all_dc[] = { ps.hdc, dc_mem };
+ for (int i = 0; i < ARRAY_SIZE(all_dc); ++i) {
+ SetMapMode(all_dc[i], MM_ISOTROPIC);
+ SetWindowExtEx(all_dc[i], width, height, NULL);
+ SetViewportExtEx(all_dc[i], rc.right, rc.bottom, NULL);
+ }
+
+ HBITMAP bmp_mem = ::CreateCompatibleBitmap(ps.hdc, rc.right, rc.bottom);
+ HGDIOBJ bmp_old = ::SelectObject(dc_mem, bmp_mem);
+
+ POINT logical_area = { rc.right, rc.bottom };
+ DPtoLP(ps.hdc, &logical_area, 1);
+
+ HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+ RECT logical_rect = {0, 0, logical_area.x, logical_area.y };
+ ::FillRect(dc_mem, &logical_rect, brush);
+ ::DeleteObject(brush);
+
+ int max_unit = (std::max)(width, height);
+ int x = (logical_area.x / 2) - (width / 2);
+ int y = (logical_area.y / 2) - (height / 2);
+
+ StretchDIBits(dc_mem, x, y, width, height,
+ 0, 0, width, height, image, &bmi, DIB_RGB_COLORS, SRCCOPY);
+
+ if ((rc.right - rc.left) > 200 && (rc.bottom - rc.top) > 200) {
+ const BITMAPINFO& bmi = local_renderer->bmi();
+ image = local_renderer->image();
+ int thumb_width = bmi.bmiHeader.biWidth / 4;
+ int thumb_height = abs(bmi.bmiHeader.biHeight) / 4;
+ StretchDIBits(dc_mem,
+ logical_area.x - thumb_width - 10,
+ logical_area.y - thumb_height - 10,
+ thumb_width, thumb_height,
+ 0, 0, bmi.bmiHeader.biWidth, -bmi.bmiHeader.biHeight,
+ image, &bmi, DIB_RGB_COLORS, SRCCOPY);
+ }
+
+ BitBlt(ps.hdc, 0, 0, logical_area.x, logical_area.y,
+ dc_mem, 0, 0, SRCCOPY);
+
+ // Cleanup.
+ ::SelectObject(dc_mem, bmp_old);
+ ::DeleteObject(bmp_mem);
+ ::DeleteDC(dc_mem);
+ } else {
+ // We're still waiting for the video stream to be initialized.
+ HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
+ ::FillRect(ps.hdc, &rc, brush);
+ ::DeleteObject(brush);
+
+ HGDIOBJ old_font = ::SelectObject(ps.hdc, GetDefaultFont());
+ ::SetTextColor(ps.hdc, RGB(0xff, 0xff, 0xff));
+ ::SetBkMode(ps.hdc, TRANSPARENT);
+
+ std::string text(kConnecting);
+ if (!local_renderer->image()) {
+ text += kNoVideoStreams;
+ } else {
+ text += kNoIncomingStream;
+ }
+ ::DrawTextA(ps.hdc, text.c_str(), -1, &rc,
+ DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+ ::SelectObject(ps.hdc, old_font);
+ }
+ } else {
+ HBRUSH brush = ::CreateSolidBrush(::GetSysColor(COLOR_WINDOW));
+ ::FillRect(ps.hdc, &rc, brush);
+ ::DeleteObject(brush);
+ }
+
+ ::EndPaint(handle(), &ps);
+}
+
+void MainWnd::OnDestroyed() {
+ PostQuitMessage(0);
+}
+
+void MainWnd::OnDefaultAction() {
+ if (!callback_)
+ return;
+ if (ui_ == CONNECT_TO_SERVER) {
+ std::string server(GetWindowText(edit1_));
+ std::string port_str(GetWindowText(edit2_));
+ int port = port_str.length() ? atoi(port_str.c_str()) : 0;
+ callback_->StartLogin(server, port);
+ } else if (ui_ == LIST_PEERS) {
+ LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0);
+ if (sel != LB_ERR) {
+ LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0);
+ if (peer_id != -1 && callback_) {
+ callback_->ConnectToPeer(peer_id);
+ }
+ }
+ } else {
+ MessageBoxA(wnd_, "OK!", "Yeah", MB_OK);
+ }
+}
+
+bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) {
+ switch (msg) {
+ case WM_ERASEBKGND:
+ *result = TRUE;
+ return true;
+
+ case WM_PAINT:
+ OnPaint();
+ return true;
+
+ case WM_SETFOCUS:
+ if (ui_ == CONNECT_TO_SERVER) {
+ SetFocus(edit1_);
+ } else if (ui_ == LIST_PEERS) {
+ SetFocus(listbox_);
+ }
+ return true;
+
+ case WM_SIZE:
+ if (ui_ == CONNECT_TO_SERVER) {
+ LayoutConnectUI(true);
+ } else if (ui_ == LIST_PEERS) {
+ LayoutPeerListUI(true);
+ }
+ break;
+
+ case WM_CTLCOLORSTATIC:
+ *result = reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_WINDOW));
+ return true;
+
+ case WM_COMMAND:
+ if (button_ == reinterpret_cast<HWND>(lp)) {
+ if (BN_CLICKED == HIWORD(wp))
+ OnDefaultAction();
+ } else if (listbox_ == reinterpret_cast<HWND>(lp)) {
+ if (LBN_DBLCLK == HIWORD(wp)) {
+ OnDefaultAction();
+ }
+ }
+ return true;
+
+ case WM_CLOSE:
+ if (callback_)
+ callback_->Close();
+ break;
+ }
+ return false;
+}
+
+// static
+LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
+ MainWnd* me = reinterpret_cast<MainWnd*>(
+ ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+ if (!me && WM_CREATE == msg) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lp);
+ me = reinterpret_cast<MainWnd*>(cs->lpCreateParams);
+ me->wnd_ = hwnd;
+ ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(me));
+ }
+
+ LRESULT result = 0;
+ if (me) {
+ void* prev_nested_msg = me->nested_msg_;
+ me->nested_msg_ = &msg;
+
+ bool handled = me->OnMessage(msg, wp, lp, &result);
+ if (WM_NCDESTROY == msg) {
+ me->destroyed_ = true;
+ } else if (!handled) {
+ result = ::DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ if (me->destroyed_ && prev_nested_msg == NULL) {
+ me->OnDestroyed();
+ me->wnd_ = NULL;
+ me->destroyed_ = false;
+ }
+
+ me->nested_msg_ = prev_nested_msg;
+ } else {
+ result = ::DefWindowProc(hwnd, msg, wp, lp);
+ }
+
+ return result;
+}
+
+// static
+bool MainWnd::RegisterWindowClass() {
+ if (wnd_class_)
+ return true;
+
+ WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
+ wcex.style = CS_DBLCLKS;
+ wcex.hInstance = GetModuleHandle(NULL);
+ wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
+ wcex.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+ wcex.lpfnWndProc = &WndProc;
+ wcex.lpszClassName = kClassName;
+ wnd_class_ = ::RegisterClassEx(&wcex);
+ ASSERT(wnd_class_ != 0);
+ return wnd_class_ != 0;
+}
+
+void MainWnd::CreateChildWindow(HWND* wnd, MainWnd::ChildWindowID id,
+ const wchar_t* class_name, DWORD control_style,
+ DWORD ex_style) {
+ if (::IsWindow(*wnd))
+ return;
+
+ // Child windows are invisible at first, and shown after being resized.
+ DWORD style = WS_CHILD | control_style;
+ *wnd = ::CreateWindowEx(ex_style, class_name, L"", style,
+ 100, 100, 100, 100, wnd_,
+ reinterpret_cast<HMENU>(id),
+ GetModuleHandle(NULL), NULL);
+ ASSERT(::IsWindow(*wnd) != FALSE);
+ ::SendMessage(*wnd, WM_SETFONT, reinterpret_cast<WPARAM>(GetDefaultFont()),
+ TRUE);
+}
+
+void MainWnd::CreateChildWindows() {
+ // Create the child windows in tab order.
+ CreateChildWindow(&label1_, LABEL1_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+ CreateChildWindow(&edit1_, EDIT_ID, L"Edit",
+ ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+ CreateChildWindow(&label2_, LABEL2_ID, L"Static", ES_CENTER | ES_READONLY, 0);
+ CreateChildWindow(&edit2_, EDIT_ID, L"Edit",
+ ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, WS_EX_CLIENTEDGE);
+ CreateChildWindow(&button_, BUTTON_ID, L"Button", BS_CENTER | WS_TABSTOP, 0);
+
+ CreateChildWindow(&listbox_, LISTBOX_ID, L"ListBox",
+ LBS_HASSTRINGS | LBS_NOTIFY, WS_EX_CLIENTEDGE);
+
+ ::SetWindowTextA(edit1_, GetDefaultServerName().c_str());
+ ::SetWindowTextA(edit2_, "8888");
+}
+
+void MainWnd::LayoutConnectUI(bool show) {
+ struct Windows {
+ HWND wnd;
+ const wchar_t* text;
+ size_t width;
+ size_t height;
+ } windows[] = {
+ { label1_, L"Server" },
+ { edit1_, L"XXXyyyYYYgggXXXyyyYYYggg" },
+ { label2_, L":" },
+ { edit2_, L"XyXyX" },
+ { button_, L"Connect" },
+ };
+
+ if (show) {
+ const size_t kSeparator = 5;
+ size_t total_width = (ARRAYSIZE(windows) - 1) * kSeparator;
+
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ CalculateWindowSizeForText(windows[i].wnd, windows[i].text,
+ &windows[i].width, &windows[i].height);
+ total_width += windows[i].width;
+ }
+
+ RECT rc;
+ ::GetClientRect(wnd_, &rc);
+ size_t x = (rc.right / 2) - (total_width / 2);
+ size_t y = rc.bottom / 2;
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ size_t top = y - (windows[i].height / 2);
+ ::MoveWindow(windows[i].wnd, x, top, windows[i].width, windows[i].height,
+ TRUE);
+ x += kSeparator + windows[i].width;
+ if (windows[i].text[0] != 'X')
+ ::SetWindowText(windows[i].wnd, windows[i].text);
+ ::ShowWindow(windows[i].wnd, SW_SHOWNA);
+ }
+ } else {
+ for (size_t i = 0; i < ARRAYSIZE(windows); ++i) {
+ ::ShowWindow(windows[i].wnd, SW_HIDE);
+ }
+ }
+}
+
+void MainWnd::LayoutPeerListUI(bool show) {
+ if (show) {
+ RECT rc;
+ ::GetClientRect(wnd_, &rc);
+ ::MoveWindow(listbox_, 0, 0, rc.right, rc.bottom, TRUE);
+ ::ShowWindow(listbox_, SW_SHOWNA);
+ } else {
+ ::ShowWindow(listbox_, SW_HIDE);
+ InvalidateRect(wnd_, NULL, TRUE);
+ }
+}
+
+void MainWnd::HandleTabbing() {
+ bool shift = ((::GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
+ UINT next_cmd = shift ? GW_HWNDPREV : GW_HWNDNEXT;
+ UINT loop_around_cmd = shift ? GW_HWNDLAST : GW_HWNDFIRST;
+ HWND focus = GetFocus(), next;
+ do {
+ next = ::GetWindow(focus, next_cmd);
+ if (IsWindowVisible(next) &&
+ (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+ break;
+ }
+
+ if (!next) {
+ next = ::GetWindow(focus, loop_around_cmd);
+ if (IsWindowVisible(next) &&
+ (GetWindowLong(next, GWL_STYLE) & WS_TABSTOP)) {
+ break;
+ }
+ }
+ focus = next;
+ } while (true);
+ ::SetFocus(next);
+}
+
+//
+// MainWnd::VideoRenderer
+//
+
+MainWnd::VideoRenderer::VideoRenderer(HWND wnd, int width, int height)
+ : wnd_(wnd) {
+ ::InitializeCriticalSection(&buffer_lock_);
+ ZeroMemory(&bmi_, sizeof(bmi_));
+ bmi_.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi_.bmiHeader.biPlanes = 1;
+ bmi_.bmiHeader.biBitCount = 32;
+ bmi_.bmiHeader.biCompression = BI_RGB;
+ bmi_.bmiHeader.biWidth = width;
+ bmi_.bmiHeader.biHeight = -height;
+ bmi_.bmiHeader.biSizeImage = width * height *
+ (bmi_.bmiHeader.biBitCount >> 3);
+}
+
+MainWnd::VideoRenderer::~VideoRenderer() {
+ ::DeleteCriticalSection(&buffer_lock_);
+}
+
+bool MainWnd::VideoRenderer::SetSize(int width, int height, int reserved) {
+ AutoLock<VideoRenderer> lock(this);
+
+ bmi_.bmiHeader.biWidth = width;
+ bmi_.bmiHeader.biHeight = -height;
+ bmi_.bmiHeader.biSizeImage = width * height *
+ (bmi_.bmiHeader.biBitCount >> 3);
+ image_.reset(new uint8[bmi_.bmiHeader.biSizeImage]);
+
+ return true;
+}
+
+bool MainWnd::VideoRenderer::RenderFrame(const cricket::VideoFrame* frame) {
+ if (!frame)
+ return false;
+
+ {
+ AutoLock<VideoRenderer> lock(this);
+
+ ASSERT(image_.get() != NULL);
+ frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+ image_.get(),
+ bmi_.bmiHeader.biSizeImage,
+ bmi_.bmiHeader.biWidth *
+ bmi_.bmiHeader.biBitCount / 8);
+ }
+
+ InvalidateRect(wnd_, NULL, TRUE);
+
+ return true;
+}
diff --git a/talk/examples/peerconnection/client/main_wnd.h b/talk/examples/peerconnection/client/main_wnd.h
new file mode 100644
index 0000000..17243b3
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.h
@@ -0,0 +1,210 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "talk/app/webrtc/mediastream.h"
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+#include "talk/base/win32.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videoframe.h"
+#include "talk/session/phone/videorenderer.h"
+
+class MainWndCallback {
+ public:
+ virtual bool StartLogin(const std::string& server, int port) = 0;
+ virtual void DisconnectFromServer() = 0;
+ virtual void ConnectToPeer(int peer_id) = 0;
+ virtual void DisconnectFromCurrentPeer() = 0;
+ virtual void UIThreadCallback(int msg_id, void* data) = 0;
+ virtual void Close() = 0;
+ protected:
+ virtual ~MainWndCallback() {}
+};
+
+// Pure virtual interface for the main window.
+class MainWindow {
+ public:
+ virtual ~MainWindow() {}
+
+ enum UI {
+ CONNECT_TO_SERVER,
+ LIST_PEERS,
+ STREAMING,
+ };
+
+ virtual void RegisterObserver(MainWndCallback* callback) = 0;
+
+ virtual bool IsWindow() = 0;
+ virtual void MessageBox(const char* caption, const char* text,
+ bool is_error) = 0;
+
+ virtual UI current_ui() = 0;
+
+ virtual void SwitchToConnectUI() = 0;
+ virtual void SwitchToPeerList(const Peers& peers) = 0;
+ virtual void SwitchToStreamingUI() = 0;
+
+ virtual webrtc::VideoRendererWrapperInterface* local_renderer() = 0;
+ virtual webrtc::VideoRendererWrapperInterface* remote_renderer() = 0;
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data) = 0;
+};
+
+#ifdef WIN32
+
+class MainWnd : public MainWindow {
+ public:
+ static const wchar_t kClassName[];
+
+ enum WindowMessages {
+ UI_THREAD_CALLBACK = WM_APP + 1,
+ };
+
+ MainWnd();
+ ~MainWnd();
+
+ bool Create();
+ bool Destroy();
+ bool PreTranslateMessage(MSG* msg);
+
+ virtual void RegisterObserver(MainWndCallback* callback);
+ virtual bool IsWindow();
+ virtual void SwitchToConnectUI();
+ virtual void SwitchToPeerList(const Peers& peers);
+ virtual void SwitchToStreamingUI();
+ virtual void MessageBox(const char* caption, const char* text,
+ bool is_error);
+ virtual UI current_ui() { return ui_; }
+
+ virtual webrtc::VideoRendererWrapperInterface* local_renderer();
+ virtual webrtc::VideoRendererWrapperInterface* remote_renderer();
+
+ virtual void QueueUIThreadCallback(int msg_id, void* data);
+
+ HWND handle() const { return wnd_; }
+
+ class VideoRenderer : public cricket::VideoRenderer {
+ public:
+ VideoRenderer(HWND wnd, int width, int height);
+ virtual ~VideoRenderer();
+
+ void Lock() {
+ ::EnterCriticalSection(&buffer_lock_);
+ }
+
+ void Unlock() {
+ ::LeaveCriticalSection(&buffer_lock_);
+ }
+
+ virtual bool SetSize(int width, int height, int reserved);
+
+ // Called when a new frame is available for display.
+ virtual bool RenderFrame(const cricket::VideoFrame* frame);
+
+ const BITMAPINFO& bmi() const { return bmi_; }
+ const uint8* image() const { return image_.get(); }
+
+ protected:
+ enum {
+ SET_SIZE,
+ RENDER_FRAME,
+ };
+
+ HWND wnd_;
+ BITMAPINFO bmi_;
+ talk_base::scoped_array<uint8> image_;
+ CRITICAL_SECTION buffer_lock_;
+ };
+
+ // A little helper class to make sure we always to proper locking and
+ // unlocking when working with VideoRenderer buffers.
+ template <typename T>
+ class AutoLock {
+ public:
+ explicit AutoLock(T* obj) : obj_(obj) { obj_->Lock(); }
+ ~AutoLock() { obj_->Unlock(); }
+ protected:
+ T* obj_;
+ };
+
+ protected:
+ enum ChildWindowID {
+ EDIT_ID = 1,
+ BUTTON_ID,
+ LABEL1_ID,
+ LABEL2_ID,
+ LISTBOX_ID,
+ };
+
+ void OnPaint();
+ void OnDestroyed();
+
+ void OnDefaultAction();
+
+ bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result);
+
+ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
+ static bool RegisterWindowClass();
+
+ void CreateChildWindow(HWND* wnd, ChildWindowID id, const wchar_t* class_name,
+ DWORD control_style, DWORD ex_style);
+ void CreateChildWindows();
+
+ void LayoutConnectUI(bool show);
+ void LayoutPeerListUI(bool show);
+
+ void HandleTabbing();
+
+ private:
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ local_renderer_wrapper_;
+ talk_base::scoped_refptr<webrtc::VideoRendererWrapperInterface>
+ remote_renderer_wrapper_;
+ UI ui_;
+ HWND wnd_;
+ DWORD ui_thread_id_;
+ HWND edit1_;
+ HWND edit2_;
+ HWND label1_;
+ HWND label2_;
+ HWND button_;
+ HWND listbox_;
+ bool destroyed_;
+ void* nested_msg_;
+ MainWndCallback* callback_;
+ static ATOM wnd_class_;
+};
+#endif // WIN32
+
+#endif // PEERCONNECTION_SAMPLES_CLIENT_MAIN_WND_H_
diff --git a/talk/examples/peerconnection/client/peer_connection_client.cc b/talk/examples/peerconnection/client/peer_connection_client.cc
new file mode 100644
index 0000000..64706de
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.cc
@@ -0,0 +1,499 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/client/peer_connection_client.h"
+
+#include "talk/examples/peerconnection/client/defaults.h"
+#include "talk/base/common.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#include "talk/base/win32socketserver.h"
+#endif
+
+using talk_base::sprintfn;
+
+namespace {
+
+// This is our magical hangup signal.
+const char kByeMessage[] = "BYE";
+
+talk_base::AsyncSocket* CreateClientSocket() {
+#ifdef WIN32
+ return new talk_base::Win32Socket();
+#elif defined(POSIX)
+ talk_base::Thread* thread = talk_base::Thread::Current();
+ ASSERT(thread != NULL);
+ return thread->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+#else
+#error Platform not supported.
+#endif
+}
+
+}
+
+PeerConnectionClient::PeerConnectionClient()
+ : callback_(NULL),
+ control_socket_(CreateClientSocket()),
+ hanging_get_(CreateClientSocket()),
+ state_(NOT_CONNECTED),
+ my_id_(-1) {
+ control_socket_->SignalCloseEvent.connect(this,
+ &PeerConnectionClient::OnClose);
+ hanging_get_->SignalCloseEvent.connect(this,
+ &PeerConnectionClient::OnClose);
+ control_socket_->SignalConnectEvent.connect(this,
+ &PeerConnectionClient::OnConnect);
+ hanging_get_->SignalConnectEvent.connect(this,
+ &PeerConnectionClient::OnHangingGetConnect);
+ control_socket_->SignalReadEvent.connect(this,
+ &PeerConnectionClient::OnRead);
+ hanging_get_->SignalReadEvent.connect(this,
+ &PeerConnectionClient::OnHangingGetRead);
+}
+
+PeerConnectionClient::~PeerConnectionClient() {
+}
+
+int PeerConnectionClient::id() const {
+ return my_id_;
+}
+
+bool PeerConnectionClient::is_connected() const {
+ return my_id_ != -1;
+}
+
+const Peers& PeerConnectionClient::peers() const {
+ return peers_;
+}
+
+void PeerConnectionClient::RegisterObserver(
+ PeerConnectionClientObserver* callback) {
+ ASSERT(!callback_);
+ callback_ = callback;
+}
+
+bool PeerConnectionClient::Connect(const std::string& server, int port,
+ const std::string& client_name) {
+ ASSERT(!server.empty());
+ ASSERT(!client_name.empty());
+
+ if (state_ != NOT_CONNECTED) {
+ LOG(WARNING)
+ << "The client must not be connected before you can call Connect()";
+ return false;
+ }
+
+ if (server.empty() || client_name.empty())
+ return false;
+
+ if (port <= 0)
+ port = kDefaultServerPort;
+
+ server_address_.SetIP(server);
+ server_address_.SetPort(port);
+
+ if (server_address_.IsUnresolved()) {
+ int errcode = 0;
+ hostent* h = talk_base::SafeGetHostByName(
+ server_address_.IPAsString().c_str(), &errcode);
+ if (!h) {
+ LOG(LS_ERROR) << "Failed to resolve host name: "
+ << server_address_.IPAsString();
+ return false;
+ } else {
+ server_address_.SetResolvedIP(
+ ntohl(*reinterpret_cast<uint32*>(h->h_addr_list[0])));
+ talk_base::FreeHostEnt(h);
+ }
+ }
+
+ char buffer[1024];
+ sprintfn(buffer, sizeof(buffer),
+ "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name.c_str());
+ onconnect_data_ = buffer;
+
+ bool ret = ConnectControlSocket();
+ if (ret)
+ state_ = SIGNING_IN;
+
+ return ret;
+}
+
+bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
+ if (state_ != CONNECTED)
+ return false;
+
+ ASSERT(is_connected());
+ ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED);
+ if (!is_connected() || peer_id == -1)
+ return false;
+
+ char headers[1024];
+ sprintfn(headers, sizeof(headers),
+ "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
+ "Content-Length: %i\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n",
+ my_id_, peer_id, message.length());
+ onconnect_data_ = headers;
+ onconnect_data_ += message;
+ return ConnectControlSocket();
+}
+
+bool PeerConnectionClient::SendHangUp(int peer_id) {
+ return SendToPeer(peer_id, kByeMessage);
+}
+
+bool PeerConnectionClient::IsSendingMessage() {
+ return state_ == CONNECTED &&
+ control_socket_->GetState() != talk_base::Socket::CS_CLOSED;
+}
+
+bool PeerConnectionClient::SignOut() {
+ if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
+ return true;
+
+ if (hanging_get_->GetState() != talk_base::Socket::CS_CLOSED)
+ hanging_get_->Close();
+
+ if (control_socket_->GetState() == talk_base::Socket::CS_CLOSED) {
+ state_ = SIGNING_OUT;
+
+ if (my_id_ != -1) {
+ char buffer[1024];
+ sprintfn(buffer, sizeof(buffer),
+ "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+ onconnect_data_ = buffer;
+ return ConnectControlSocket();
+ } else {
+ // Can occur if the app is closed before we finish connecting.
+ return true;
+ }
+ } else {
+ state_ = SIGNING_OUT_WAITING;
+ }
+
+ return true;
+}
+
+void PeerConnectionClient::Close() {
+ control_socket_->Close();
+ hanging_get_->Close();
+ onconnect_data_.clear();
+ peers_.clear();
+ my_id_ = -1;
+ state_ = NOT_CONNECTED;
+}
+
+bool PeerConnectionClient::ConnectControlSocket() {
+ ASSERT(control_socket_->GetState() == talk_base::Socket::CS_CLOSED);
+ int err = control_socket_->Connect(server_address_);
+ if (err == SOCKET_ERROR) {
+ Close();
+ return false;
+ }
+ return true;
+}
+
+void PeerConnectionClient::OnConnect(talk_base::AsyncSocket* socket) {
+ ASSERT(!onconnect_data_.empty());
+ size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
+ ASSERT(sent == onconnect_data_.length());
+ UNUSED(sent);
+ onconnect_data_.clear();
+}
+
+void PeerConnectionClient::OnHangingGetConnect(talk_base::AsyncSocket* socket) {
+ char buffer[1024];
+ sprintfn(buffer, sizeof(buffer),
+ "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
+ int len = strlen(buffer);
+ int sent = socket->Send(buffer, len);
+ ASSERT(sent == len);
+ UNUSED2(sent, len);
+}
+
+void PeerConnectionClient::OnMessageFromPeer(int peer_id,
+ const std::string& message) {
+ if (message.length() == (sizeof(kByeMessage) - 1) &&
+ message.compare(kByeMessage) == 0) {
+ callback_->OnPeerDisconnected(peer_id);
+ } else {
+ callback_->OnMessageFromPeer(peer_id, message);
+ }
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data,
+ size_t eoh,
+ const char* header_pattern,
+ size_t* value) {
+ ASSERT(value != NULL);
+ size_t found = data.find(header_pattern);
+ if (found != std::string::npos && found < eoh) {
+ *value = atoi(&data[found + strlen(header_pattern)]);
+ return true;
+ }
+ return false;
+}
+
+bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
+ const char* header_pattern,
+ std::string* value) {
+ ASSERT(value != NULL);
+ size_t found = data.find(header_pattern);
+ if (found != std::string::npos && found < eoh) {
+ size_t begin = found + strlen(header_pattern);
+ size_t end = data.find("\r\n", begin);
+ if (end == std::string::npos)
+ end = eoh;
+ value->assign(data.substr(begin, end - begin));
+ return true;
+ }
+ return false;
+}
+
+bool PeerConnectionClient::ReadIntoBuffer(talk_base::AsyncSocket* socket,
+ std::string* data,
+ size_t* content_length) {
+ LOG(INFO) << __FUNCTION__;
+
+ char buffer[0xffff];
+ do {
+ int bytes = socket->Recv(buffer, sizeof(buffer));
+ if (bytes <= 0)
+ break;
+ data->append(buffer, bytes);
+ } while (true);
+
+ bool ret = false;
+ size_t i = data->find("\r\n\r\n");
+ if (i != std::string::npos) {
+ LOG(INFO) << "Headers received";
+ if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
+ LOG(INFO) << "Expecting " << *content_length << " bytes.";
+ size_t total_response_size = (i + 4) + *content_length;
+ if (data->length() >= total_response_size) {
+ ret = true;
+ std::string should_close;
+ const char kConnection[] = "\r\nConnection: ";
+ if (GetHeaderValue(*data, i, kConnection, &should_close) &&
+ should_close.compare("close") == 0) {
+ socket->Close();
+ // Since we closed the socket, there was no notification delivered
+ // to us. Compensate by letting ourselves know.
+ OnClose(socket, 0);
+ }
+ } else {
+ // We haven't received everything. Just continue to accept data.
+ }
+ } else {
+ LOG(LS_ERROR) << "No content length field specified by the server.";
+ }
+ }
+ return ret;
+}
+
+void PeerConnectionClient::OnRead(talk_base::AsyncSocket* socket) {
+ LOG(INFO) << __FUNCTION__;
+ size_t content_length = 0;
+ if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
+ size_t peer_id = 0, eoh = 0;
+ bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
+ &eoh);
+ if (ok) {
+ if (my_id_ == -1) {
+ // First response. Let's store our server assigned ID.
+ ASSERT(state_ == SIGNING_IN);
+ my_id_ = peer_id;
+ ASSERT(my_id_ != -1);
+
+ // The body of the response will be a list of already connected peers.
+ if (content_length) {
+ size_t pos = eoh + 4;
+ while (pos < control_data_.size()) {
+ size_t eol = control_data_.find('\n', pos);
+ if (eol == std::string::npos)
+ break;
+ int id = 0;
+ std::string name;
+ bool connected;
+ if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
+ &connected) && id != my_id_) {
+ peers_[id] = name;
+ callback_->OnPeerConnected(id, name);
+ }
+ pos = eol + 1;
+ }
+ }
+ ASSERT(is_connected());
+ callback_->OnSignedIn();
+ } else if (state_ == SIGNING_OUT) {
+ Close();
+ callback_->OnDisconnected();
+ } else if (state_ == SIGNING_OUT_WAITING) {
+ SignOut();
+ }
+ }
+
+ control_data_.clear();
+
+ if (state_ == SIGNING_IN) {
+ ASSERT(hanging_get_->GetState() == talk_base::Socket::CS_CLOSED);
+ state_ = CONNECTED;
+ hanging_get_->Connect(server_address_);
+ }
+ }
+}
+
+void PeerConnectionClient::OnHangingGetRead(talk_base::AsyncSocket* socket) {
+ LOG(INFO) << __FUNCTION__;
+ size_t content_length = 0;
+ if (ReadIntoBuffer(socket, ¬ification_data_, &content_length)) {
+ size_t peer_id = 0, eoh = 0;
+ bool ok = ParseServerResponse(notification_data_, content_length,
+ &peer_id, &eoh);
+
+ if (ok) {
+ // Store the position where the body begins.
+ size_t pos = eoh + 4;
+
+ if (my_id_ == static_cast<int>(peer_id)) {
+ // A notification about a new member or a member that just
+ // disconnected.
+ int id = 0;
+ std::string name;
+ bool connected = false;
+ if (ParseEntry(notification_data_.substr(pos), &name, &id,
+ &connected)) {
+ if (connected) {
+ peers_[id] = name;
+ callback_->OnPeerConnected(id, name);
+ } else {
+ peers_.erase(id);
+ callback_->OnPeerDisconnected(id);
+ }
+ }
+ } else {
+ OnMessageFromPeer(peer_id, notification_data_.substr(pos));
+ }
+ }
+
+ notification_data_.clear();
+ }
+
+ if (hanging_get_->GetState() == talk_base::Socket::CS_CLOSED &&
+ state_ == CONNECTED) {
+ hanging_get_->Connect(server_address_);
+ }
+}
+
+bool PeerConnectionClient::ParseEntry(const std::string& entry,
+ std::string* name,
+ int* id,
+ bool* connected) {
+ ASSERT(name != NULL);
+ ASSERT(id != NULL);
+ ASSERT(connected != NULL);
+ ASSERT(!entry.empty());
+
+ *connected = false;
+ size_t separator = entry.find(',');
+ if (separator != std::string::npos) {
+ *id = atoi(&entry[separator + 1]);
+ name->assign(entry.substr(0, separator));
+ separator = entry.find(',', separator + 1);
+ if (separator != std::string::npos) {
+ *connected = atoi(&entry[separator + 1]) ? true : false;
+ }
+ }
+ return !name->empty();
+}
+
+int PeerConnectionClient::GetResponseStatus(const std::string& response) {
+ int status = -1;
+ size_t pos = response.find(' ');
+ if (pos != std::string::npos)
+ status = atoi(&response[pos + 1]);
+ return status;
+}
+
+bool PeerConnectionClient::ParseServerResponse(const std::string& response,
+ size_t content_length,
+ size_t* peer_id,
+ size_t* eoh) {
+ LOG(INFO) << response;
+
+ int status = GetResponseStatus(response.c_str());
+ if (status != 200) {
+ LOG(LS_ERROR) << "Received error from server";
+ Close();
+ callback_->OnDisconnected();
+ return false;
+ }
+
+ *eoh = response.find("\r\n\r\n");
+ ASSERT(*eoh != std::string::npos);
+ if (*eoh == std::string::npos)
+ return false;
+
+ *peer_id = -1;
+
+ // See comment in peer_channel.cc for why we use the Pragma header and
+ // not e.g. "X-Peer-Id".
+ GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
+
+ return true;
+}
+
+void PeerConnectionClient::OnClose(talk_base::AsyncSocket* socket, int err) {
+ LOG(INFO) << __FUNCTION__;
+
+ socket->Close();
+
+#ifdef WIN32
+ if (err != WSAECONNREFUSED) {
+#else
+ if (err != ECONNREFUSED) {
+#endif
+ if (socket == hanging_get_.get()) {
+ if (state_ == CONNECTED) {
+ LOG(INFO) << "Issuing a new hanging get";
+ hanging_get_->Close();
+ hanging_get_->Connect(server_address_);
+ }
+ } else {
+ callback_->OnMessageSent(err);
+ }
+ } else {
+ LOG(WARNING) << "Failed to connect to the server";
+ Close();
+ callback_->OnDisconnected();
+ }
+}
diff --git a/talk/examples/peerconnection/client/peer_connection_client.h b/talk/examples/peerconnection/client/peer_connection_client.h
new file mode 100644
index 0000000..f47a24d
--- /dev/null
+++ b/talk/examples/peerconnection/client/peer_connection_client.h
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#define PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+
+typedef std::map<int, std::string> Peers;
+
+struct PeerConnectionClientObserver {
+ virtual void OnSignedIn() = 0; // Called when we're logged on.
+ virtual void OnDisconnected() = 0;
+ virtual void OnPeerConnected(int id, const std::string& name) = 0;
+ virtual void OnPeerDisconnected(int peer_id) = 0;
+ virtual void OnMessageFromPeer(int peer_id, const std::string& message) = 0;
+ virtual void OnMessageSent(int err) = 0;
+
+ protected:
+ virtual ~PeerConnectionClientObserver() {}
+};
+
+class PeerConnectionClient : public sigslot::has_slots<> {
+ public:
+ enum State {
+ NOT_CONNECTED,
+ SIGNING_IN,
+ CONNECTED,
+ SIGNING_OUT_WAITING,
+ SIGNING_OUT,
+ };
+
+ PeerConnectionClient();
+ ~PeerConnectionClient();
+
+ int id() const;
+ bool is_connected() const;
+ const Peers& peers() const;
+
+ void RegisterObserver(PeerConnectionClientObserver* callback);
+
+ bool Connect(const std::string& server, int port,
+ const std::string& client_name);
+
+ bool SendToPeer(int peer_id, const std::string& message);
+ bool SendHangUp(int peer_id);
+ bool IsSendingMessage();
+
+ bool SignOut();
+
+ protected:
+ void Close();
+ bool ConnectControlSocket();
+ void OnConnect(talk_base::AsyncSocket* socket);
+ void OnHangingGetConnect(talk_base::AsyncSocket* socket);
+ void OnMessageFromPeer(int peer_id, const std::string& message);
+
+ // Quick and dirty support for parsing HTTP header values.
+ bool GetHeaderValue(const std::string& data, size_t eoh,
+ const char* header_pattern, size_t* value);
+
+ bool GetHeaderValue(const std::string& data, size_t eoh,
+ const char* header_pattern, std::string* value);
+
+ // Returns true if the whole response has been read.
+ bool ReadIntoBuffer(talk_base::AsyncSocket* socket, std::string* data,
+ size_t* content_length);
+
+ void OnRead(talk_base::AsyncSocket* socket);
+
+ void OnHangingGetRead(talk_base::AsyncSocket* socket);
+
+ // Parses a single line entry in the form "<name>,<id>,<connected>"
+ bool ParseEntry(const std::string& entry, std::string* name, int* id,
+ bool* connected);
+
+ int GetResponseStatus(const std::string& response);
+
+ bool ParseServerResponse(const std::string& response, size_t content_length,
+ size_t* peer_id, size_t* eoh);
+
+ void OnClose(talk_base::AsyncSocket* socket, int err);
+
+ PeerConnectionClientObserver* callback_;
+ talk_base::SocketAddress server_address_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> control_socket_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> hanging_get_;
+ std::string onconnect_data_;
+ std::string control_data_;
+ std::string notification_data_;
+ Peers peers_;
+ State state_;
+ int my_id_;
+};
+
+#endif // PEERCONNECTION_SAMPLES_CLIENT_PEER_CONNECTION_CLIENT_H_
diff --git a/talk/examples/peerconnection/server/data_socket.cc b/talk/examples/peerconnection/server/data_socket.cc
new file mode 100644
index 0000000..81cba2e
--- /dev/null
+++ b/talk/examples/peerconnection/server/data_socket.cc
@@ -0,0 +1,304 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/server/data_socket.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "talk/examples/peerconnection/server/utils.h"
+
+static const char kHeaderTerminator[] = "\r\n\r\n";
+static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1;
+
+// static
+const char DataSocket::kCrossOriginAllowHeaders[] =
+ "Access-Control-Allow-Origin: *\r\n"
+ "Access-Control-Allow-Credentials: true\r\n"
+ "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n"
+ "Access-Control-Allow-Headers: Content-Type, "
+ "Content-Length, Connection, Cache-Control\r\n"
+ "Access-Control-Expose-Headers: Content-Length, X-Peer-Id\r\n";
+
+#if defined(WIN32)
+class WinsockInitializer {
+ static WinsockInitializer singleton;
+
+ WinsockInitializer() {
+ WSADATA data;
+ WSAStartup(MAKEWORD(1, 0), &data);
+ }
+
+ public:
+ ~WinsockInitializer() { WSACleanup(); }
+};
+WinsockInitializer WinsockInitializer::singleton;
+#endif
+
+//
+// SocketBase
+//
+
+bool SocketBase::Create() {
+ assert(!valid());
+ socket_ = ::socket(AF_INET, SOCK_STREAM, 0);
+ return valid();
+}
+
+void SocketBase::Close() {
+ if (socket_ != INVALID_SOCKET) {
+ closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+ }
+}
+
+//
+// DataSocket
+//
+
+std::string DataSocket::request_arguments() const {
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(args + 1);
+ return "";
+}
+
+bool DataSocket::PathEquals(const char* path) const {
+ assert(path);
+ size_t args = request_path_.find('?');
+ if (args != std::string::npos)
+ return request_path_.substr(0, args).compare(path) == 0;
+ return request_path_.compare(path) == 0;
+}
+
+bool DataSocket::OnDataAvailable(bool* close_socket) {
+ assert(valid());
+ char buffer[0xfff] = {0};
+ int bytes = recv(socket_, buffer, sizeof(buffer), 0);
+ if (bytes == SOCKET_ERROR || bytes == 0) {
+ *close_socket = true;
+ return false;
+ }
+
+ *close_socket = false;
+
+ bool ret = true;
+ if (headers_received()) {
+ if (method_ != POST) {
+ // unexpectedly received data.
+ ret = false;
+ } else {
+ data_.append(buffer, bytes);
+ }
+ } else {
+ request_headers_.append(buffer, bytes);
+ size_t found = request_headers_.find(kHeaderTerminator);
+ if (found != std::string::npos) {
+ data_ = request_headers_.substr(found + kHeaderTerminatorLength);
+ request_headers_.resize(found + kHeaderTerminatorLength);
+ ret = ParseHeaders();
+ }
+ }
+ return ret;
+}
+
+bool DataSocket::Send(const std::string& data) const {
+ return send(socket_, data.data(), data.length(), 0) != SOCKET_ERROR;
+}
+
+bool DataSocket::Send(const std::string& status, bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) const {
+ assert(valid());
+ assert(!status.empty());
+ std::string buffer("HTTP/1.1 " + status + "\r\n");
+
+ buffer += "Server: PeerConnectionTestServer/0.1\r\n"
+ "Cache-Control: no-cache\r\n";
+
+ if (connection_close)
+ buffer += "Connection: close\r\n";
+
+ if (!content_type.empty())
+ buffer += "Content-Type: " + content_type + "\r\n";
+
+ buffer += "Content-Length: " + int2str(data.size()) + "\r\n";
+
+ if (!extra_headers.empty()) {
+ buffer += extra_headers;
+ // Extra headers are assumed to have a separator per header.
+ }
+
+ buffer += kCrossOriginAllowHeaders;
+
+ buffer += "\r\n";
+ buffer += data;
+
+ return Send(buffer);
+}
+
+void DataSocket::Clear() {
+ method_ = INVALID;
+ content_length_ = 0;
+ content_type_.clear();
+ request_path_.clear();
+ request_headers_.clear();
+ data_.clear();
+}
+
+bool DataSocket::ParseHeaders() {
+ assert(!request_headers_.empty());
+ assert(method_ == INVALID);
+ size_t i = request_headers_.find("\r\n");
+ if (i == std::string::npos)
+ return false;
+
+ if (!ParseMethodAndPath(request_headers_.data(), i))
+ return false;
+
+ assert(method_ != INVALID);
+ assert(!request_path_.empty());
+
+ if (method_ == POST) {
+ const char* headers = request_headers_.data() + i + 2;
+ size_t len = request_headers_.length() - i - 2;
+ if (!ParseContentLengthAndType(headers, len))
+ return false;
+ }
+
+ return true;
+}
+
+bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) {
+ struct {
+ const char* method_name;
+ size_t method_name_len;
+ RequestMethod id;
+ } supported_methods[] = {
+ { "GET", 3, GET },
+ { "POST", 4, POST },
+ { "OPTIONS", 7, OPTIONS },
+ };
+
+ const char* path = NULL;
+ for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) {
+ if (len > supported_methods[i].method_name_len &&
+ isspace(begin[supported_methods[i].method_name_len]) &&
+ strncmp(begin, supported_methods[i].method_name,
+ supported_methods[i].method_name_len) == 0) {
+ method_ = supported_methods[i].id;
+ path = begin + supported_methods[i].method_name_len;
+ break;
+ }
+ }
+
+ const char* end = begin + len;
+ if (!path || path >= end)
+ return false;
+
+ ++path;
+ begin = path;
+ while (!isspace(*path) && path < end)
+ ++path;
+
+ request_path_.assign(begin, path - begin);
+
+ return true;
+}
+
+bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) {
+ assert(content_length_ == 0);
+ assert(content_type_.empty());
+
+ const char* end = headers + length;
+ while (headers && headers < end) {
+ if (!isspace(headers[0])) {
+ static const char kContentLength[] = "Content-Length:";
+ static const char kContentType[] = "Content-Type:";
+ if ((headers + ARRAYSIZE(kContentLength)) < end &&
+ strncmp(headers, kContentLength,
+ ARRAYSIZE(kContentLength) - 1) == 0) {
+ headers += ARRAYSIZE(kContentLength) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ content_length_ = atoi(headers);
+ } else if ((headers + ARRAYSIZE(kContentType)) < end &&
+ strncmp(headers, kContentType,
+ ARRAYSIZE(kContentType) - 1) == 0) {
+ headers += ARRAYSIZE(kContentType) - 1;
+ while (headers[0] == ' ')
+ ++headers;
+ const char* type_end = strstr(headers, "\r\n");
+ if (type_end == NULL)
+ type_end = end;
+ content_type_.assign(headers, type_end);
+ }
+ } else {
+ ++headers;
+ }
+ headers = strstr(headers, "\r\n");
+ if (headers)
+ headers += 2;
+ }
+
+ return !content_type_.empty() && content_length_ != 0;
+}
+
+//
+// ListeningSocket
+//
+
+bool ListeningSocket::Listen(unsigned short port) {
+ assert(valid());
+ int enabled = 1;
+ setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const char*>(&enabled), sizeof(enabled));
+ struct sockaddr_in addr = {0};
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+ if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr),
+ sizeof(addr)) == SOCKET_ERROR) {
+ printf("bind failed\n");
+ return false;
+ }
+ return listen(socket_, 5) != SOCKET_ERROR;
+}
+
+DataSocket* ListeningSocket::Accept() const {
+ assert(valid());
+ struct sockaddr_in addr = {0};
+ socklen_t size = sizeof(addr);
+ int client = accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size);
+ if (client == INVALID_SOCKET)
+ return NULL;
+
+ return new DataSocket(client);
+}
+
diff --git a/talk/examples/peerconnection/server/data_socket.h b/talk/examples/peerconnection/server/data_socket.h
new file mode 100644
index 0000000..b2ba28d
--- /dev/null
+++ b/talk/examples/peerconnection/server/data_socket.h
@@ -0,0 +1,168 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
+#pragma once
+
+#ifdef WIN32
+#include <winsock2.h>
+typedef int socklen_t;
+#else
+#include <netinet/in.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#define closesocket close
+#endif
+
+#include <string>
+
+#ifndef SOCKET_ERROR
+#define SOCKET_ERROR (-1)
+#endif
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET static_cast<int>(~0)
+#endif
+
+class SocketBase {
+ public:
+ SocketBase() : socket_(INVALID_SOCKET) { }
+ explicit SocketBase(int socket) : socket_(socket) { }
+ ~SocketBase() { Close(); }
+
+ int socket() const { return socket_; }
+ bool valid() const { return socket_ != INVALID_SOCKET; }
+
+ bool Create();
+ void Close();
+
+ protected:
+ int socket_;
+};
+
+// Represents an HTTP server socket.
+class DataSocket : public SocketBase {
+ public:
+ enum RequestMethod {
+ INVALID,
+ GET,
+ POST,
+ OPTIONS,
+ };
+
+ explicit DataSocket(int socket)
+ : SocketBase(socket),
+ method_(INVALID),
+ content_length_(0) {
+ }
+
+ ~DataSocket() {
+ }
+
+ static const char kCrossOriginAllowHeaders[];
+
+ bool headers_received() const { return method_ != INVALID; }
+
+ RequestMethod method() const { return method_; }
+
+ const std::string& request_path() const { return request_path_; }
+ std::string request_arguments() const;
+
+ const std::string& data() const { return data_; }
+
+ const std::string& content_type() const { return content_type_; }
+
+ size_t content_length() const { return content_length_; }
+
+ bool request_received() const {
+ return headers_received() && (method_ != POST || data_received());
+ }
+
+ bool data_received() const {
+ return method_ != POST || data_.length() >= content_length_;
+ }
+
+ // Checks if the request path (minus arguments) matches a given path.
+ bool PathEquals(const char* path) const;
+
+ // Called when we have received some data from clients.
+ // Returns false if an error occurred.
+ bool OnDataAvailable(bool* close_socket);
+
+ // Send a raw buffer of bytes.
+ bool Send(const std::string& data) const;
+
+ // Send an HTTP response. The |status| should start with a valid HTTP
+ // response code, followed by a string. E.g. "200 OK".
+ // If |connection_close| is set to true, an extra "Connection: close" HTTP
+ // header will be included. |content_type| is the mime content type, not
+ // including the "Content-Type: " string.
+ // |extra_headers| should be either empty or a list of headers where each
+ // header terminates with "\r\n".
+ // |data| is the body of the message. It's length will be specified via
+ // a "Content-Length" header.
+ bool Send(const std::string& status, bool connection_close,
+ const std::string& content_type,
+ const std::string& extra_headers, const std::string& data) const;
+
+ // Clears all held state and prepares the socket for receiving a new request.
+ void Clear();
+
+ protected:
+ // A fairly relaxed HTTP header parser. Parses the method, path and
+ // content length (POST only) of a request.
+ // Returns true if a valid request was received and no errors occurred.
+ bool ParseHeaders();
+
+ // Figures out whether the request is a GET or POST and what path is
+ // being requested.
+ bool ParseMethodAndPath(const char* begin, size_t len);
+
+ // Determines the length of the body and it's mime type.
+ bool ParseContentLengthAndType(const char* headers, size_t length);
+
+ protected:
+ RequestMethod method_;
+ size_t content_length_;
+ std::string content_type_;
+ std::string request_path_;
+ std::string request_headers_;
+ std::string data_;
+};
+
+// The server socket. Accepts connections and generates DataSocket instances
+// for each new connection.
+class ListeningSocket : public SocketBase {
+ public:
+ ListeningSocket() {}
+
+ bool Listen(unsigned short port);
+ DataSocket* Accept() const;
+};
+
+#endif // TALK_EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_
diff --git a/talk/examples/peerconnection/server/main.cc b/talk/examples/peerconnection/server/main.cc
new file mode 100644
index 0000000..63a573b
--- /dev/null
+++ b/talk/examples/peerconnection/server/main.cc
@@ -0,0 +1,176 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "talk/examples/peerconnection/server/data_socket.h"
+#include "talk/examples/peerconnection/server/peer_channel.h"
+#include "talk/examples/peerconnection/server/utils.h"
+
+static const size_t kMaxConnections = (FD_SETSIZE - 2);
+
+void HandleBrowserRequest(DataSocket* ds, bool* quit) {
+ assert(ds && ds->valid());
+ assert(quit);
+
+ const std::string& path = ds->request_path();
+
+ *quit = (path.compare("/quit") == 0);
+
+ if (*quit) {
+ ds->Send("200 OK", true, "text/html", "",
+ "<html><body>Quitting...</body></html>");
+ } else if (ds->method() == DataSocket::OPTIONS) {
+ // We'll get this when a browsers do cross-resource-sharing requests.
+ // The headers to allow cross-origin script support will be set inside
+ // Send.
+ ds->Send("200 OK", true, "", "", "");
+ } else {
+ // Here we could write some useful output back to the browser depending on
+ // the path.
+ printf("Received an invalid request: %s\n", ds->request_path().c_str());
+ ds->Send("500 Sorry", true, "text/html", "",
+ "<html><body>Sorry, not yet implemented</body></html>");
+ }
+}
+
+int main(int argc, char** argv) {
+ // TODO: make configurable.
+ static const unsigned short port = 8888;
+
+ ListeningSocket listener;
+ if (!listener.Create()) {
+ printf("Failed to create server socket\n");
+ return -1;
+ } else if (!listener.Listen(port)) {
+ printf("Failed to listen on server socket\n");
+ return -1;
+ }
+
+ printf("Server listening on port %i\n", port);
+
+ PeerChannel clients;
+ typedef std::vector<DataSocket*> SocketArray;
+ SocketArray sockets;
+ bool quit = false;
+ while (!quit) {
+ fd_set socket_set;
+ FD_ZERO(&socket_set);
+ if (listener.valid())
+ FD_SET(listener.socket(), &socket_set);
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ FD_SET((*i)->socket(), &socket_set);
+
+ struct timeval timeout = { 10, 0 };
+ if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) {
+ printf("select failed\n");
+ break;
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
+ DataSocket* s = *i;
+ bool socket_done = true;
+ if (FD_ISSET(s->socket(), &socket_set)) {
+ if (s->OnDataAvailable(&socket_done) && s->request_received()) {
+ ChannelMember* member = clients.Lookup(s);
+ if (member || PeerChannel::IsPeerConnection(s)) {
+ if (!member) {
+ if (s->PathEquals("/sign_in")) {
+ clients.AddMember(s);
+ } else {
+ printf("No member found for: %s\n",
+ s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ } else if (member->is_wait_request(s)) {
+ // no need to do anything.
+ socket_done = false;
+ } else {
+ ChannelMember* target = clients.IsTargetedRequest(s);
+ if (target) {
+ member->ForwardRequestToPeer(s, target);
+ } else if (s->PathEquals("/sign_out")) {
+ s->Send("200 OK", true, "text/plain", "", "");
+ } else {
+ printf("Couldn't find target for request: %s\n",
+ s->request_path().c_str());
+ s->Send("500 Error", true, "text/plain", "",
+ "Peer most likely gone.");
+ }
+ }
+ } else {
+ HandleBrowserRequest(s, &quit);
+ if (quit) {
+ printf("Quitting...\n");
+ FD_CLR(listener.socket(), &socket_set);
+ listener.Close();
+ clients.CloseAll();
+ }
+ }
+ }
+ } else {
+ socket_done = false;
+ }
+
+ if (socket_done) {
+ printf("Disconnecting socket\n");
+ clients.OnClosing(s);
+ assert(s->valid()); // Close must not have been called yet.
+ FD_CLR(s->socket(), &socket_set);
+ delete (*i);
+ i = sockets.erase(i);
+ if (i == sockets.end())
+ break;
+ }
+ }
+
+ clients.CheckForTimeout();
+
+ if (FD_ISSET(listener.socket(), &socket_set)) {
+ DataSocket* s = listener.Accept();
+ if (sockets.size() >= kMaxConnections) {
+ delete s; // sorry, that's all we can take.
+ printf("Connection limit reached\n");
+ } else {
+ sockets.push_back(s);
+ printf("New connection...\n");
+ }
+ }
+ }
+
+ for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
+ delete (*i);
+ sockets.clear();
+
+ return 0;
+}
diff --git a/talk/examples/peerconnection/server/peer_channel.cc b/talk/examples/peerconnection/server/peer_channel.cc
new file mode 100644
index 0000000..5409941
--- /dev/null
+++ b/talk/examples/peerconnection/server/peer_channel.cc
@@ -0,0 +1,369 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/server/peer_channel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "talk/examples/peerconnection/server/data_socket.h"
+#include "talk/examples/peerconnection/server/utils.h"
+
+// Set to the peer id of the originator when messages are being
+// exchanged between peers, but set to the id of the receiving peer
+// itself when notifications are sent from the server about the state
+// of other peers.
+//
+// WORKAROUND: Since support for CORS varies greatly from one browser to the
+// next, we don't use a custom name for our peer-id header (originally it was
+// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should
+// always be exposed to CORS requests. There is a special CORS header devoted
+// to exposing proprietary headers (Access-Control-Expose-Headers), however
+// at this point it is not working correctly in some popular browsers.
+static const char kPeerIdHeader[] = "Pragma: ";
+
+static const char* kRequestPaths[] = {
+ "/wait", "/sign_out", "/message",
+};
+
+enum RequestPathIndex {
+ kWait,
+ kSignOut,
+ kMessage,
+};
+
+//
+// ChannelMember
+//
+
+int ChannelMember::s_member_id_ = 0;
+
+ChannelMember::ChannelMember(DataSocket* socket)
+ : waiting_socket_(NULL), id_(++s_member_id_),
+ connected_(true), timestamp_(time(NULL)) {
+ assert(socket);
+ assert(socket->method() == DataSocket::GET);
+ assert(socket->PathEquals("/sign_in"));
+ name_ = socket->request_arguments(); // TODO: urldecode
+ if (!name_.length())
+ name_ = "peer_" + int2str(id_);
+ std::replace(name_.begin(), name_.end(), ',', '_');
+}
+
+ChannelMember::~ChannelMember() {
+}
+
+bool ChannelMember::is_wait_request(DataSocket* ds) const {
+ return ds && ds->PathEquals(kRequestPaths[kWait]);
+}
+
+bool ChannelMember::TimedOut() {
+ return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30;
+}
+
+std::string ChannelMember::GetPeerIdHeader() const {
+ std::string ret(kPeerIdHeader + int2str(id_) + "\r\n");
+ return ret;
+}
+
+bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) {
+ assert(&other != this);
+ QueueResponse("200 OK", "text/plain", GetPeerIdHeader(),
+ other.GetEntry());
+ return true;
+}
+
+// Returns a string in the form "name,id\n".
+std::string ChannelMember::GetEntry() const {
+ char entry[1024] = {0};
+ sprintf(entry, "%s,%i,%i\n", name_.c_str(), id_, connected_); // NOLINT
+ return entry;
+}
+
+void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) {
+ assert(peer);
+ assert(ds);
+
+ std::string extra_headers(GetPeerIdHeader());
+
+ if (peer == this) {
+ ds->Send("200 OK", true, ds->content_type(), extra_headers,
+ ds->data());
+ } else {
+ printf("Client %s sending to %s\n",
+ name_.c_str(), peer->name().c_str());
+ peer->QueueResponse("200 OK", ds->content_type(), extra_headers,
+ ds->data());
+ ds->Send("200 OK", true, "text/plain", "", "");
+ }
+}
+
+void ChannelMember::OnClosing(DataSocket* ds) {
+ if (ds == waiting_socket_) {
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ }
+}
+
+void ChannelMember::QueueResponse(const std::string& status,
+ const std::string& content_type,
+ const std::string& extra_headers,
+ const std::string& data) {
+ if (waiting_socket_) {
+ assert(queue_.size() == 0);
+ assert(waiting_socket_->method() == DataSocket::GET);
+ bool ok = waiting_socket_->Send(status, true, content_type, extra_headers,
+ data);
+ if (!ok) {
+ printf("Failed to deliver data to waiting socket\n");
+ }
+ waiting_socket_ = NULL;
+ timestamp_ = time(NULL);
+ } else {
+ QueuedResponse qr;
+ qr.status = status;
+ qr.content_type = content_type;
+ qr.extra_headers = extra_headers;
+ qr.data = data;
+ queue_.push(qr);
+ }
+}
+
+void ChannelMember::SetWaitingSocket(DataSocket* ds) {
+ assert(ds->method() == DataSocket::GET);
+ if (ds && !queue_.empty()) {
+ assert(waiting_socket_ == NULL);
+ const QueuedResponse& response = queue_.back();
+ ds->Send(response.status, true, response.content_type,
+ response.extra_headers, response.data);
+ queue_.pop();
+ } else {
+ waiting_socket_ = ds;
+ }
+}
+
+
+//
+// PeerChannel
+//
+
+// static
+bool PeerChannel::IsPeerConnection(const DataSocket* ds) {
+ assert(ds);
+ return (ds->method() == DataSocket::POST && ds->content_length() > 0) ||
+ (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in"));
+}
+
+ChannelMember* PeerChannel::Lookup(DataSocket* ds) const {
+ assert(ds);
+
+ if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST)
+ return NULL;
+
+ size_t i = 0;
+ for (; i < ARRAYSIZE(kRequestPaths); ++i) {
+ if (ds->PathEquals(kRequestPaths[i]))
+ break;
+ }
+
+ if (i == ARRAYSIZE(kRequestPaths))
+ return NULL;
+
+ std::string args(ds->request_arguments());
+ static const char kPeerId[] = "peer_id=";
+ size_t found = args.find(kPeerId);
+ if (found == std::string::npos)
+ return NULL;
+
+ int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]);
+ Members::const_iterator iter = members_.begin();
+ for (; iter != members_.end(); ++iter) {
+ if (id == (*iter)->id()) {
+ if (i == kWait)
+ (*iter)->SetWaitingSocket(ds);
+ if (i == kSignOut)
+ (*iter)->set_disconnected();
+ return *iter;
+ }
+ }
+
+ return NULL;
+}
+
+ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const {
+ assert(ds);
+ // Regardless of GET or POST, we look for the peer_id parameter
+ // only in the request_path.
+ const std::string& path = ds->request_path();
+ size_t args = path.find('?');
+ if (args == std::string::npos)
+ return NULL;
+ size_t found;
+ const char kTargetPeerIdParam[] = "to=";
+ do {
+ found = path.find(kTargetPeerIdParam, args);
+ if (found == std::string::npos)
+ return NULL;
+ if (found == (args + 1) || path[found - 1] == '&') {
+ found += ARRAYSIZE(kTargetPeerIdParam) - 1;
+ break;
+ }
+ args = found + ARRAYSIZE(kTargetPeerIdParam) - 1;
+ } while (true);
+ int id = atoi(&path[found]);
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if ((*i)->id() == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+bool PeerChannel::AddMember(DataSocket* ds) {
+ assert(IsPeerConnection(ds));
+ ChannelMember* new_guy = new ChannelMember(ds);
+ Members failures;
+ BroadcastChangedState(*new_guy, &failures);
+ HandleDeliveryFailures(&failures);
+ members_.push_back(new_guy);
+
+ printf("New member added (total=%s): %s\n",
+ size_t2str(members_.size()).c_str(), new_guy->name().c_str());
+
+ // Let the newly connected peer know about other members of the channel.
+ std::string content_type;
+ std::string response = BuildResponseForNewMember(*new_guy, &content_type);
+ ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(),
+ response);
+ return true;
+}
+
+void PeerChannel::CloseAll() {
+ Members::const_iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down");
+ }
+ DeleteAll();
+}
+
+void PeerChannel::OnClosing(DataSocket* ds) {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ m->OnClosing(ds);
+ if (!m->connected()) {
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+ printf("Total connected: %s\n", size_t2str(members_.size()).c_str());
+}
+
+void PeerChannel::CheckForTimeout() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ ChannelMember* m = (*i);
+ if (m->TimedOut()) {
+ printf("Timeout: %s\n", m->name().c_str());
+ m->set_disconnected();
+ i = members_.erase(i);
+ Members failures;
+ BroadcastChangedState(*m, &failures);
+ HandleDeliveryFailures(&failures);
+ delete m;
+ if (i == members_.end())
+ break;
+ }
+ }
+}
+
+void PeerChannel::DeleteAll() {
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i)
+ delete (*i);
+ members_.clear();
+}
+
+void PeerChannel::BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures) {
+ // This function should be called prior to DataSocket::Close().
+ assert(delivery_failures);
+
+ if (!member.connected()) {
+ printf("Member disconnected: %s\n", member.name().c_str());
+ }
+
+ Members::iterator i = members_.begin();
+ for (; i != members_.end(); ++i) {
+ if (&member != (*i)) {
+ if (!(*i)->NotifyOfOtherMember(member)) {
+ (*i)->set_disconnected();
+ delivery_failures->push_back(*i);
+ i = members_.erase(i);
+ if (i == members_.end())
+ break;
+ }
+ }
+ }
+}
+
+void PeerChannel::HandleDeliveryFailures(Members* failures) {
+ assert(failures);
+
+ while (!failures->empty()) {
+ Members::iterator i = failures->begin();
+ ChannelMember* member = *i;
+ assert(!member->connected());
+ failures->erase(i);
+ BroadcastChangedState(*member, failures);
+ delete member;
+ }
+}
+
+// Builds a simple list of "name,id\n" entries for each member.
+std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type) {
+ assert(content_type);
+
+ *content_type = "text/plain";
+ // The peer itself will always be the first entry.
+ std::string response(member.GetEntry());
+ for (Members::iterator i = members_.begin(); i != members_.end(); ++i) {
+ if (member.id() != (*i)->id()) {
+ assert((*i)->connected());
+ response += (*i)->GetEntry();
+ }
+ }
+
+ return response;
+}
diff --git a/talk/examples/peerconnection/server/peer_channel.h b/talk/examples/peerconnection/server/peer_channel.h
new file mode 100644
index 0000000..2ecc789
--- /dev/null
+++ b/talk/examples/peerconnection/server/peer_channel.h
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
+#pragma once
+
+#include <time.h>
+
+#include <queue>
+#include <string>
+#include <vector>
+
+class DataSocket;
+
+// Represents a single peer connected to the server.
+class ChannelMember {
+ public:
+ explicit ChannelMember(DataSocket* socket);
+ ~ChannelMember();
+
+ bool connected() const { return connected_; }
+ int id() const { return id_; }
+ void set_disconnected() { connected_ = false; }
+ bool is_wait_request(DataSocket* ds) const;
+ const std::string& name() const { return name_; }
+
+ bool TimedOut();
+
+ std::string GetPeerIdHeader() const;
+
+ bool NotifyOfOtherMember(const ChannelMember& other);
+
+ // Returns a string in the form "name,id\n".
+ std::string GetEntry() const;
+
+ void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer);
+
+ void OnClosing(DataSocket* ds);
+
+ void QueueResponse(const std::string& status, const std::string& content_type,
+ const std::string& extra_headers, const std::string& data);
+
+ void SetWaitingSocket(DataSocket* ds);
+
+ protected:
+ struct QueuedResponse {
+ std::string status, content_type, extra_headers, data;
+ };
+
+ DataSocket* waiting_socket_;
+ int id_;
+ bool connected_;
+ time_t timestamp_;
+ std::string name_;
+ std::queue<QueuedResponse> queue_;
+ static int s_member_id_;
+};
+
+// Manages all currently connected peers.
+class PeerChannel {
+ public:
+ typedef std::vector<ChannelMember*> Members;
+
+ PeerChannel() {
+ }
+
+ ~PeerChannel() {
+ DeleteAll();
+ }
+
+ const Members& members() const { return members_; }
+
+ // Returns true if the request should be treated as a new ChannelMember
+ // request. Otherwise the request is not peerconnection related.
+ static bool IsPeerConnection(const DataSocket* ds);
+
+ // Finds a connected peer that's associated with the |ds| socket.
+ ChannelMember* Lookup(DataSocket* ds) const;
+
+ // Checks if the request has a "peer_id" parameter and if so, looks up the
+ // peer for which the request is targeted at.
+ ChannelMember* IsTargetedRequest(const DataSocket* ds) const;
+
+ // Adds a new ChannelMember instance to the list of connected peers and
+ // associates it with the socket.
+ bool AddMember(DataSocket* ds);
+
+ // Closes all connections and sends a "shutting down" message to all
+ // connected peers.
+ void CloseAll();
+
+ // Called when a socket was determined to be closing by the peer (or if the
+ // connection went dead).
+ void OnClosing(DataSocket* ds);
+
+ void CheckForTimeout();
+
+ protected:
+ void DeleteAll();
+ void BroadcastChangedState(const ChannelMember& member,
+ Members* delivery_failures);
+ void HandleDeliveryFailures(Members* failures);
+
+ // Builds a simple list of "name,id\n" entries for each member.
+ std::string BuildResponseForNewMember(const ChannelMember& member,
+ std::string* content_type);
+
+ protected:
+ Members members_;
+};
+
+#endif // TALK_EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_
diff --git a/talk/examples/peerconnection/server/server_test.html b/talk/examples/peerconnection/server/server_test.html
new file mode 100644
index 0000000..d7c2266
--- /dev/null
+++ b/talk/examples/peerconnection/server/server_test.html
@@ -0,0 +1,263 @@
+<html>
+<head>
+<title>PeerConnection server test page</title>
+
+<script>
+var request = null;
+var hangingGet = null;
+var localName;
+var server;
+var my_id = -1;
+var other_peers = {};
+var message_counter = 0;
+
+function trace(txt) {
+ var elem = document.getElementById("debug");
+ elem.innerHTML += txt + "<br>";
+}
+
+function handleServerNotification(data) {
+ trace("Server notification: " + data);
+ var parsed = data.split(',');
+ if (parseInt(parsed[2]) != 0)
+ other_peers[parseInt(parsed[1])] = parsed[0];
+}
+
+function handlePeerMessage(peer_id, data) {
+ ++message_counter;
+ var str = "Message from '" + other_peers[peer_id] + "' ";
+ str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' ";
+ str += "style='cursor: pointer'>+</span><br>";
+ str += "<blockquote id='msg_" + message_counter + "' style='display:none'>";
+ str += data + "</blockquote>";
+ trace(str);
+ if (document.getElementById("loopback").checked) {
+ if (data.search("OFFER") != -1) {
+ // In loopback mode, replace the ROAP OFFER with ROAP ANSWER.
+ data = data.replace("OFFER", "ANSWER");
+ // Keep only the first crypto line for each m line in the answer.
+ var mlineBegin = data.indexOf("m=", 0);
+ while (mlineBegin != -1) {
+ var cryptoBegin = data.indexOf("a=crypto:", mlineBegin);
+ if (cryptoBegin == -1) {
+ // No more crypto lines.
+ break;
+ }
+ // Skip the first crypto line.
+ cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+ // Update the mlineBegin to the the next m line.
+ mlineBegin = data.indexOf("m=", mlineBegin + 1);
+ while (cryptoBegin != -1 && cryptoBegin < mlineBegin) {
+ var cryptoEnd = data.indexOf("\\n", cryptoBegin);
+ var crypto = data.substring(cryptoBegin, cryptoEnd + 2);
+ data = data.replace(crypto, "");
+ // Search for the the next crypto line.
+ cryptoBegin = data.indexOf("a=crypto:", cryptoBegin + 1);
+ }
+ }
+ var lines = data.split("\n");
+ for (var i = 1; i < lines.length; ++i) {
+ // Look for the offererSessionId and use it as answererSessionId
+ if (lines[i].length > 0 && lines[i].search("offererSessionId") != -1) {
+ var answer_session_id =
+ lines[i].replace("offererSessionId", "answererSessionId");
+ answer_session_id += "\n" + lines[i];
+ data = data.replace(lines[i], answer_session_id);
+ }
+ }
+ }
+ sendToPeer(peer_id, data);
+ }
+}
+
+function GetIntHeader(r, name) {
+ var val = r.getResponseHeader(name);
+ return val != null && val.length ? parseInt(val) : -1;
+}
+
+function hangingGetCallback() {
+ try {
+ if (hangingGet.readyState != 4)
+ return;
+ if (hangingGet.status != 200) {
+ trace("server error: " + hangingGet.statusText);
+ disconnect();
+ } else {
+ var peer_id = GetIntHeader(hangingGet, "Pragma");
+ if (peer_id == my_id) {
+ handleServerNotification(hangingGet.responseText);
+ } else {
+ handlePeerMessage(peer_id, hangingGet.responseText);
+ }
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+ } catch (e) {
+ trace("Hanging get error: " + e.description);
+ }
+}
+
+function startHangingGet() {
+ try {
+ hangingGet = new XMLHttpRequest();
+ hangingGet.onreadystatechange = hangingGetCallback;
+ hangingGet.ontimeout = onHangingGetTimeout;
+ hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true);
+ hangingGet.send();
+ } catch (e) {
+ trace("error" + e.description);
+ }
+}
+
+function onHangingGetTimeout() {
+ trace("hanging get timeout. issuing again.");
+ hangingGet.abort();
+ hangingGet = null;
+ if (my_id != -1)
+ window.setTimeout(startHangingGet, 0);
+}
+
+function signInCallback() {
+ try {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ var peers = request.responseText.split("\n");
+ my_id = parseInt(peers[0].split(',')[1]);
+ trace("My id: " + my_id);
+ for (var i = 1; i < peers.length; ++i) {
+ if (peers[i].length > 0) {
+ trace("Peer " + i + ": " + peers[i]);
+ var parsed = peers[i].split(',');
+ other_peers[parseInt(parsed[1])] = parsed[0];
+ }
+ }
+ startHangingGet();
+ request = null;
+ }
+ }
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function signIn() {
+ try {
+ request = new XMLHttpRequest();
+ request.onreadystatechange = signInCallback;
+ request.open("GET", server + "/sign_in?" + localName, true);
+ request.send();
+ } catch (e) {
+ trace("error: " + e.description);
+ }
+}
+
+function sendToPeer(peer_id, data) {
+ if (my_id == -1) {
+ alert("Not connected");
+ return;
+ }
+ if (peer_id == my_id) {
+ alert("Can't send a message to oneself :)");
+ return;
+ }
+ var r = new XMLHttpRequest();
+ r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id,
+ false);
+ r.setRequestHeader("Content-Type", "text/plain");
+ r.send(data);
+ r = null;
+}
+
+function connect() {
+ localName = document.getElementById("local").value.toLowerCase();
+ server = document.getElementById("server").value.toLowerCase();
+ if (localName.length == 0) {
+ alert("I need a name please.");
+ document.getElementById("local").focus();
+ } else {
+ document.getElementById("connect").disabled = true;
+ document.getElementById("disconnect").disabled = false;
+ document.getElementById("send").disabled = false;
+ signIn();
+ }
+}
+
+function disconnect() {
+ if (request) {
+ request.abort();
+ request = null;
+ }
+
+ if (hangingGet) {
+ hangingGet.abort();
+ hangingGet = null;
+ }
+
+ if (my_id != -1) {
+ request = new XMLHttpRequest();
+ request.open("GET", server + "/sign_out?peer_id=" + my_id, false);
+ request.send();
+ request = null;
+ my_id = -1;
+ }
+
+ document.getElementById("connect").disabled = false;
+ document.getElementById("disconnect").disabled = true;
+ document.getElementById("send").disabled = true;
+}
+
+window.onbeforeunload = disconnect;
+
+function send() {
+ var text = document.getElementById("message").value;
+ var peer_id = parseInt(document.getElementById("peer_id").value);
+ if (!text.length || peer_id == 0) {
+ alert("No text supplied or invalid peer id");
+ } else {
+ sendToPeer(peer_id, text);
+ }
+}
+
+function toggleMe(obj) {
+ var id = obj.id.replace("toggle", "msg");
+ var t = document.getElementById(id);
+ if (obj.innerText == "+") {
+ obj.innerText = "-";
+ t.style.display = "block";
+ } else {
+ obj.innerText = "+";
+ t.style.display = "none";
+ }
+}
+
+</script>
+
+</head>
+<body>
+Server: <input type="text" id="server" value="http://localhost:8888" /><br>
+<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send
+received messages right back)<br>
+Your name: <input type="text" id="local" value="my_name"/>
+<button id="connect" onclick="connect();">Connect</button>
+<button disabled="true" id="disconnect"
+ onclick="disconnect();">Disconnect</button>
+<br>
+<table><tr><td>
+Target peer id: <input type="text" id="peer_id" size="3"/></td><td>
+Message: <input type="text" id="message"/></td><td>
+<button disabled="true" id="send" onclick="send();">Send</button>
+</td></tr></table>
+<button onclick="document.getElementById('debug').innerHTML='';">
+Clear log</button>
+
+<pre id="debug">
+</pre>
+<br><hr>
+</body>
+</html>
diff --git a/talk/examples/peerconnection/server/utils.cc b/talk/examples/peerconnection/server/utils.cc
new file mode 100644
index 0000000..25ec10c
--- /dev/null
+++ b/talk/examples/peerconnection/server/utils.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/examples/peerconnection/server/utils.h"
+
+#include <stdio.h>
+
+std::string int2str(int i) {
+ char buffer[11] = {0};
+ sprintf(buffer, "%d", i); // NOLINT
+ return buffer;
+}
+
+std::string size_t2str(size_t i) {
+ char buffer[32] = {0};
+#ifdef WIN32
+ // %zu isn't supported on Windows.
+ sprintf(buffer, "%Iu", i); // NOLINT
+#else
+ sprintf(buffer, "%zu", i); // NOLINT
+#endif
+ return buffer;
+}
diff --git a/talk/examples/peerconnection/server/utils.h b/talk/examples/peerconnection/server/utils.h
new file mode 100644
index 0000000..d05a2c3
--- /dev/null
+++ b/talk/examples/peerconnection/server/utils.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#define TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
+#pragma once
+
+#ifndef assert
+#ifndef WIN32
+#include <assert.h>
+#else
+#ifndef NDEBUG
+#define assert(expr) ((void)((expr) ? true : __debugbreak()))
+#else
+#define assert(expr) ((void)0)
+#endif // NDEBUG
+#endif // WIN32
+#endif // assert
+
+#include <string>
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
+std::string int2str(int i);
+std::string size_t2str(size_t i);
+
+#endif // TALK_EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_
diff --git a/talk/examples/plus/libjingleplus.cc b/talk/examples/plus/libjingleplus.cc
new file mode 100644
index 0000000..4d27dfd
--- /dev/null
+++ b/talk/examples/plus/libjingleplus.cc
@@ -0,0 +1,736 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include "libjingleplus.h"
+#ifdef WIN32
+#include "talk/base/win32socketserver.h"
+#endif
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/logging.h"
+#include "talk/examples/login/xmppauth.h"
+#include "talk/examples/login/xmppsocket.h"
+#include "talk/examples/login/xmpppump.h"
+#include "presencepushtask.h"
+#include "talk/app/status.h"
+#include "talk/app/message.h"
+#include "rostertask.h"
+#include "talk/app/iqtask.h"
+#include "talk/app/presenceouttask.h"
+#include "talk/app/receivemessagetask.h"
+#include "talk/app/rostersettask.h"
+#include "talk/app/sendmessagetask.h"
+
+enum {
+ MSG_START,
+
+ // main thread to worker
+ MSG_LOGIN,
+ MSG_DISCONNECT,
+ MSG_SEND_PRESENCE,
+ MSG_SEND_DIRECTED_PRESENCE,
+ MSG_SEND_DIRECTED_MUC_PRESENCE,
+ MSG_SEND_XMPP_MESSAGE,
+ MSG_SEND_XMPP_IQ,
+ MSG_UPDATE_ROSTER_ITEM,
+ MSG_REMOVE_ROSTER_ITEM,
+
+ // worker thread to main thread
+ MSG_STATE_CHANGE,
+ MSG_STATUS_UPDATE,
+ MSG_STATUS_ERROR,
+ MSG_ROSTER_REFRESH_STARTED,
+ MSG_ROSTER_REFRESH_FINISHED,
+ MSG_ROSTER_ITEM_UPDATED,
+ MSG_ROSTER_ITEM_REMOVED,
+ MSG_ROSTER_SUBSCRIBE,
+ MSG_ROSTER_UNSUBSCRIBE,
+ MSG_ROSTER_SUBSCRIBED,
+ MSG_ROSTER_UNSUBSCRIBED,
+ MSG_INCOMING_MESSAGE,
+ MSG_IQ_COMPLETE,
+ MSG_XMPP_INPUT,
+ MSG_XMPP_OUTPUT
+};
+
+class LibjinglePlusWorker : public talk_base::MessageHandler,
+ public XmppPumpNotify,
+ public sigslot::has_slots<> {
+ public:
+ LibjinglePlusWorker(LibjinglePlus *ljp, LibjinglePlusNotify *notify) :
+ worker_thread_(NULL), ljp_(ljp), notify_(notify),
+ ppt_(NULL), rmt_(NULL), rt_(NULL), is_test_login_(false) {
+
+ main_thread_.reset(new talk_base::AutoThread());
+#ifdef WIN32
+ ss_.reset(new talk_base::Win32SocketServer(main_thread_.get()));
+ main_thread_->set_socketserver(ss_.get());
+#endif
+
+ pump_.reset(new XmppPump(this));
+
+ pump_->client()->SignalLogInput.connect(this, &LibjinglePlusWorker::OnInputDebug);
+ pump_->client()->SignalLogOutput.connect(this, &LibjinglePlusWorker::OnOutputDebug);
+ //pump_->client()->SignalStateChange.connect(this, &LibjinglePlusWorker::OnStateChange);
+ }
+
+ ~LibjinglePlusWorker() {
+ if (worker_thread_) {
+ worker_thread_->Send(this, MSG_DISCONNECT);
+ delete worker_thread_;
+ }
+ }
+
+ virtual void OnMessage(talk_base::Message *msg) {
+ switch (msg->message_id) {
+ case MSG_START:
+ LoginW();
+ break;
+ case MSG_DISCONNECT:
+ DisconnectW();
+ break;
+ case MSG_SEND_XMPP_MESSAGE:
+ SendXmppMessageW(static_cast<SendMessageData*>(msg->pdata)->m_);
+ delete msg->pdata;
+ break;
+ case MSG_SEND_XMPP_IQ:
+ SendXmppIqW(static_cast<SendIqData*>(msg->pdata)->to_jid_,
+ static_cast<SendIqData*>(msg->pdata)->is_get_,
+ static_cast<SendIqData*>(msg->pdata)->xml_element_);
+ delete msg->pdata;
+ break;
+ case MSG_SEND_PRESENCE:
+ SendPresenceW(static_cast<SendPresenceData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ case MSG_SEND_DIRECTED_PRESENCE:
+ SendDirectedPresenceW(static_cast<SendDirectedPresenceData*>(msg->pdata)->j_,
+ static_cast<SendDirectedPresenceData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ case MSG_SEND_DIRECTED_MUC_PRESENCE:
+ SendDirectedMUCPresenceW(static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->j_,
+ static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->s_,
+ static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->un_,
+ static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->ac_,
+ static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->am_,
+ static_cast<SendDirectedMUCPresenceData*>(msg->pdata)->role_);
+ delete msg->pdata;
+ break;
+ case MSG_UPDATE_ROSTER_ITEM:
+ UpdateRosterItemW(static_cast<UpdateRosterItemData*>(msg->pdata)->jid_,
+ static_cast<UpdateRosterItemData*>(msg->pdata)->n_,
+ static_cast<UpdateRosterItemData*>(msg->pdata)->g_,
+ static_cast<UpdateRosterItemData*>(msg->pdata)->grt_);
+ delete msg->pdata;
+ break;
+ case MSG_REMOVE_ROSTER_ITEM:
+ RemoveRosterItemW(static_cast<JidData*>(msg->pdata)->jid_);
+ delete msg->pdata;
+ break;
+
+
+
+
+ case MSG_STATUS_UPDATE:
+ OnStatusUpdateW(static_cast<SendPresenceData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ case MSG_STATUS_ERROR:
+ OnStatusErrorW(static_cast<StatusErrorData*>(msg->pdata)->stanza_);
+ delete msg->pdata;
+ break;
+ case MSG_STATE_CHANGE:
+ OnStateChangeW(static_cast<StateChangeData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_REFRESH_STARTED:
+ OnRosterRefreshStartedW();
+ break;
+ case MSG_ROSTER_REFRESH_FINISHED:
+ OnRosterRefreshFinishedW();
+ break;
+ case MSG_ROSTER_ITEM_UPDATED:
+ OnRosterItemUpdatedW(static_cast<RosterItemData*>(msg->pdata)->ri_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_ITEM_REMOVED:
+ OnRosterItemRemovedW(static_cast<RosterItemData*>(msg->pdata)->ri_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_SUBSCRIBE:
+ OnRosterSubscribeW(static_cast<JidData*>(msg->pdata)->jid_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_UNSUBSCRIBE:
+ OnRosterUnsubscribeW(static_cast<JidData*>(msg->pdata)->jid_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_SUBSCRIBED:
+ OnRosterSubscribedW(static_cast<JidData*>(msg->pdata)->jid_);
+ delete msg->pdata;
+ break;
+ case MSG_ROSTER_UNSUBSCRIBED:
+ OnRosterUnsubscribedW(static_cast<JidData*>(msg->pdata)->jid_);
+ delete msg->pdata;
+ break;
+ case MSG_INCOMING_MESSAGE:
+ OnIncomingMessageW(static_cast<XmppMessageData*>(msg->pdata)->m_);
+ delete msg->pdata;
+ break;
+ case MSG_IQ_COMPLETE:
+ OnIqCompleteW(static_cast<IqCompleteData*>(msg->pdata)->success_,
+ static_cast<IqCompleteData*>(msg->pdata)->stanza_);
+ delete msg->pdata;
+ break;
+ case MSG_XMPP_OUTPUT:
+ OnOutputDebugW(static_cast<StringData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ case MSG_XMPP_INPUT:
+ OnInputDebugW(static_cast<StringData*>(msg->pdata)->s_);
+ delete msg->pdata;
+ break;
+ }
+ }
+
+ void Login(const std::string &jid, const std::string &password,
+ const std::string &machine_address, bool is_test, bool cookie_auth) {
+ is_test_login_ = is_test;
+
+ xcs_.set_user(jid);
+ if (cookie_auth) {
+ xcs_.set_auth_cookie(password);
+ } else {
+ talk_base::InsecureCryptStringImpl pass;
+ pass.password() = password;
+ xcs_.set_pass(talk_base::CryptString(pass));
+ }
+ xcs_.set_host(is_test ? "google.com" : "gmail.com");
+ xcs_.set_resource("libjingleplus");
+ xcs_.set_server(talk_base::SocketAddress(machine_address, 5222));
+ xcs_.set_use_tls(!is_test);
+ if (is_test) {
+ xcs_.set_allow_plain(true);
+ }
+
+ worker_thread_ = new talk_base::Thread(&pss_);
+ worker_thread_->Start();
+ worker_thread_->Send(this, MSG_START);
+ }
+
+ void SendXmppMessage(const buzz::XmppMessage &m) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_SEND_XMPP_MESSAGE, new SendMessageData(m));
+ }
+
+ void SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+ const buzz::XmlElement *xml_element) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_SEND_XMPP_IQ,
+ new SendIqData(to_jid, is_get, xml_element));
+ }
+
+ void SendPresence(const buzz::Status & s) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_SEND_PRESENCE, new SendPresenceData(s));
+ }
+
+ void SendDirectedPresence (const buzz::Jid &j, const buzz::Status &s) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_SEND_DIRECTED_PRESENCE, new SendDirectedPresenceData(j,s));
+ }
+
+ void SendDirectedMUCPresence(const buzz::Jid &j, const buzz::Status &s,
+ const std::string &un, const std::string &ac,
+ const std::string &am, const std::string &role) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_SEND_DIRECTED_MUC_PRESENCE, new SendDirectedMUCPresenceData(j,s,un,ac,am, role));
+ }
+
+ void UpdateRosterItem(const buzz::Jid & jid, const std::string & name,
+ const std::vector<std::string> & groups, buzz::GrType grt) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_UPDATE_ROSTER_ITEM, new UpdateRosterItemData(jid,name,groups,grt));
+ }
+
+ void RemoveRosterItemW(const buzz::Jid &jid) {
+ buzz::RosterSetTask *rst = new buzz::RosterSetTask(pump_.get()->client());
+ rst->Remove(jid);
+ rst->Start();
+ }
+
+ void RemoveRosterItem(const buzz::Jid &jid) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ worker_thread_->Post(this, MSG_REMOVE_ROSTER_ITEM, new JidData(jid));
+ }
+
+ void DoCallbacks() {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ talk_base::Message m;
+ while (main_thread_->Get(&m, 0)) {
+ main_thread_->Dispatch(&m);
+ }
+ }
+
+ private:
+
+ struct UpdateRosterItemData : public talk_base::MessageData {
+ UpdateRosterItemData(const buzz::Jid &jid, const std::string &name,
+ const std::vector<std::string> &groups, buzz::GrType grt) :
+ jid_(jid), n_(name), g_(groups), grt_(grt) {}
+ buzz::Jid jid_;
+ std::string n_;
+ std::vector<std::string> g_;
+ buzz::GrType grt_;
+ };
+
+ void UpdateRosterItemW(const buzz::Jid &jid, const std::string &name,
+ const std::vector<std::string> &groups, buzz::GrType grt) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::RosterSetTask *rst = new buzz::RosterSetTask(pump_.get()->client());
+ rst->Update(jid, name, groups, grt);
+ rst->Start();
+ }
+
+ struct StringData : public talk_base::MessageData {
+ StringData(std::string s) : s_(s) {}
+ std::string s_;
+ };
+
+ void OnInputDebugW(const std::string &data) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnXmppInput(data);
+ }
+
+ void OnInputDebug(const char *data, int len) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_XMPP_INPUT, new StringData(std::string(data,len)));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnOutputDebugW(const std::string &data) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnXmppOutput(data);
+ }
+
+ void OnOutputDebug(const char *data, int len) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_XMPP_OUTPUT, new StringData(std::string(data,len)));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ struct StateChangeData : public talk_base::MessageData {
+ StateChangeData(buzz::XmppEngine::State state) : s_(state) {}
+ buzz::XmppEngine::State s_;
+ };
+
+ void OnStateChange(buzz::XmppEngine::State state) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ switch (state) {
+ case buzz::XmppEngine::STATE_OPEN:
+ ppt_ = new buzz::PresencePushTask(pump_.get()->client());
+ ppt_->SignalStatusUpdate.connect(this,
+ &LibjinglePlusWorker::OnStatusUpdate);
+ ppt_->SignalStatusError.connect(this,
+ &LibjinglePlusWorker::OnStatusError);
+ ppt_->Start();
+
+ rmt_ = new buzz::ReceiveMessageTask(pump_.get()->client(), buzz::XmppEngine::HL_ALL);
+ rmt_->SignalIncomingMessage.connect(this, &LibjinglePlusWorker::OnIncomingMessage);
+ rmt_->Start();
+
+ rt_ = new buzz::RosterTask(pump_.get()->client());
+ rt_->SignalRosterItemUpdated.connect(this, &LibjinglePlusWorker::OnRosterItemUpdated);
+ rt_->SignalRosterItemRemoved.connect(this, &LibjinglePlusWorker::OnRosterItemRemoved);
+ rt_->SignalSubscribe.connect(this, &LibjinglePlusWorker::OnRosterSubscribe);
+ rt_->SignalUnsubscribe.connect(this, &LibjinglePlusWorker::OnRosterUnsubscribe);
+ rt_->SignalSubscribed.connect(this, &LibjinglePlusWorker::OnRosterSubscribed);
+ rt_->SignalUnsubscribed.connect(this, &LibjinglePlusWorker::OnRosterUnsubscribed);
+ rt_->SignalRosterRefreshStarted.connect(this, &LibjinglePlusWorker::OnRosterRefreshStarted);
+ rt_->SignalRosterRefreshFinished.connect(this, &LibjinglePlusWorker::OnRosterRefreshFinished);
+ rt_->Start();
+ rt_->RefreshRosterNow();
+
+ break;
+ }
+ main_thread_->Post(this, MSG_STATE_CHANGE, new StateChangeData(state));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnStateChangeW(buzz::XmppEngine::State state) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnStateChange(state);
+ }
+
+ struct RosterItemData : public talk_base::MessageData {
+ RosterItemData(const buzz::RosterItem &ri) : ri_(ri) {}
+ buzz::RosterItem ri_;
+ };
+
+ void OnRosterItemUpdatedW(const buzz::RosterItem &ri) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterItemUpdated(ri);
+ }
+
+ void OnRosterItemUpdated(const buzz::RosterItem &ri, bool huh) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_ITEM_UPDATED, new RosterItemData(ri));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterItemRemovedW(const buzz::RosterItem &ri) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterItemRemoved(ri);
+ }
+
+ void OnRosterItemRemoved(const buzz::RosterItem &ri) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_ITEM_REMOVED, new RosterItemData(ri));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ struct JidData : public talk_base::MessageData {
+ JidData(const buzz::Jid& jid) : jid_(jid) {}
+ const buzz::Jid jid_;
+ };
+
+ void OnRosterSubscribeW(const buzz::Jid& jid) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterSubscribe(jid);
+ }
+
+ void OnRosterSubscribe(const buzz::Jid& jid) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_SUBSCRIBE, new JidData(jid));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterUnsubscribeW(const buzz::Jid &jid) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterUnsubscribe(jid);
+ }
+
+ void OnRosterUnsubscribe(const buzz::Jid &jid) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_UNSUBSCRIBE, new JidData(jid));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterSubscribedW(const buzz::Jid &jid) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterSubscribed(jid);
+ }
+
+ void OnRosterSubscribed(const buzz::Jid &jid) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_SUBSCRIBED, new JidData(jid));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterUnsubscribedW(const buzz::Jid &jid) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterUnsubscribed(jid);
+ }
+
+ void OnRosterUnsubscribed(const buzz::Jid &jid) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_UNSUBSCRIBED, new JidData(jid));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterRefreshStartedW() {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterRefreshStarted();
+ }
+
+ void OnRosterRefreshStarted() {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_REFRESH_STARTED);
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnRosterRefreshFinishedW() {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnRosterRefreshFinished();
+ }
+
+ void OnRosterRefreshFinished() {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_ROSTER_REFRESH_FINISHED);
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ struct XmppMessageData : talk_base::MessageData {
+ XmppMessageData(const buzz::XmppMessage &m) : m_(m) {}
+ buzz::XmppMessage m_;
+ };
+
+ void OnIncomingMessageW(const buzz::XmppMessage &msg) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnMessage(msg);
+ }
+
+ void OnIncomingMessage(const buzz::XmppMessage &msg) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_INCOMING_MESSAGE, new XmppMessageData(msg));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void OnStatusUpdateW (const buzz::Status &status) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnStatusUpdate(status);
+ }
+
+ void OnStatusUpdate (const buzz::Status &status) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_STATUS_UPDATE, new SendPresenceData(status));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ struct StatusErrorData : talk_base::MessageData {
+ StatusErrorData(const buzz::XmlElement &stanza) : stanza_(stanza) {}
+ buzz::XmlElement stanza_;
+ };
+
+ void OnStatusErrorW (const buzz::XmlElement &stanza) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnStatusError(stanza);
+ }
+
+ void OnStatusError (const buzz::XmlElement &stanza) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_STATUS_ERROR, new StatusErrorData(stanza));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void LoginW() {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ XmppSocket* socket = new XmppSocket(true);
+ pump_->DoLogin(xcs_, socket, is_test_login_ ? NULL : new XmppAuth());
+ socket->SignalCloseEvent.connect(this,
+ &LibjinglePlusWorker::OnXmppSocketClose);
+ }
+
+ void DisconnectW() {
+ assert(talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ pump_->DoDisconnect();
+ }
+
+ void SendXmppMessageW(const buzz::XmppMessage &m) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::SendMessageTask * smt = new buzz::SendMessageTask(pump_.get()->client());
+ smt->Send(m);
+ smt->Start();
+ }
+
+ void SendXmppIqW(const buzz::Jid &to_jid, bool is_get,
+ const buzz::XmlElement *xml_element) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::IqTask *iq_task = new buzz::IqTask(pump_.get()->client(),
+ is_get, to_jid, const_cast<buzz::XmlElement *>(xml_element));
+ iq_task->SignalDone.connect(this, &LibjinglePlusWorker::OnIqComplete);
+ iq_task->Start();
+ }
+
+ struct IqCompleteData : public talk_base::MessageData {
+ IqCompleteData(bool success, const buzz::XmlElement *stanza) :
+ success_(success), stanza_(*stanza) {}
+ bool success_;
+ buzz::XmlElement stanza_;
+ };
+
+ void OnIqCompleteW(bool success, const buzz::XmlElement& stanza) {
+ assert(talk_base::ThreadManager::CurrentThread() != worker_thread_);
+ if (notify_)
+ notify_->OnIqDone(success, stanza);
+ }
+
+ void OnIqComplete(bool success, const buzz::XmlElement *stanza) {
+ assert(talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ main_thread_->Post(this, MSG_IQ_COMPLETE,
+ new IqCompleteData(success, stanza));
+ if (notify_)
+ notify_->WakeupMainThread();
+ }
+
+ void SendPresenceW(const buzz::Status & s) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+ pot->Send(s);
+ pot->Start();
+ }
+
+
+ void SendDirectedMUCPresenceW(const buzz::Jid & j, const buzz::Status & s,
+ const std::string &user_nick, const std::string &api_capability,
+ const std::string &api_message, const std::string &role) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+ pot->SendDirectedMUC(j,s,user_nick,api_capability,api_message, role);
+ pot->Start();
+ }
+
+ void SendDirectedPresenceW(const buzz::Jid & j, const buzz::Status & s) {
+ assert (talk_base::ThreadManager::CurrentThread() == worker_thread_);
+ buzz::PresenceOutTask *pot = new buzz::PresenceOutTask(pump_.get()->client());
+ pot->SendDirected(j,s);
+ pot->Start();
+ }
+
+ void OnXmppSocketClose(int error) {
+ notify_->OnSocketClose(error);
+ }
+
+ struct SendMessageData : public talk_base::MessageData {
+ SendMessageData(const buzz::XmppMessage &m) : m_(m) {}
+ buzz::XmppMessage m_;
+ };
+
+ struct SendIqData : public talk_base::MessageData {
+ SendIqData(const buzz::Jid &jid, bool is_get, const buzz::XmlElement *m)
+ : to_jid_(jid), is_get_(is_get), xml_element_(m) {}
+ buzz::Jid to_jid_;
+ bool is_get_;
+ const buzz::XmlElement *xml_element_;
+ };
+
+ struct SendPresenceData : public talk_base::MessageData {
+ SendPresenceData(const buzz::Status &s) : s_(s) {}
+ buzz::Status s_;
+ };
+
+ struct SendDirectedPresenceData : public talk_base::MessageData {
+ SendDirectedPresenceData(const buzz::Jid &j, const buzz::Status &s) : j_(j), s_(s) {}
+ buzz::Jid j_;
+ buzz::Status s_;
+ };
+
+ struct SendDirectedMUCPresenceData : public talk_base::MessageData {
+ SendDirectedMUCPresenceData(const buzz::Jid &j, const buzz::Status &s,
+ const std::string &un, const std::string &ac,
+ const std::string &am, const std::string &role)
+ : j_(j), s_(s), un_(un), ac_(ac), am_(am), role_(role) {}
+ buzz::Jid j_;
+ buzz::Status s_;
+ std::string un_;
+ std::string ac_;
+ std::string am_;
+ std::string role_;
+ };
+
+ talk_base::scoped_ptr<talk_base::Win32SocketServer> ss_;
+ talk_base::scoped_ptr<talk_base::Thread> main_thread_;
+ talk_base::Thread *worker_thread_;
+
+ LibjinglePlus *ljp_;
+ LibjinglePlusNotify *notify_;
+ buzz::XmppClientSettings xcs_;
+ talk_base::PhysicalSocketServer pss_;
+
+ talk_base::scoped_ptr<XmppPump> pump_;
+ buzz::PresencePushTask * ppt_;
+ buzz::ReceiveMessageTask * rmt_;
+ buzz::RosterTask * rt_;
+
+ bool is_test_login_;
+};
+
+LibjinglePlus::LibjinglePlus(LibjinglePlusNotify *notify)
+{
+ worker_ = new LibjinglePlusWorker(this, notify);
+}
+
+LibjinglePlus::~LibjinglePlus()
+{
+ delete worker_;
+ worker_ = NULL;
+}
+
+void LibjinglePlus::Login(const std::string &jid,
+ const std::string &password,
+ const std::string &machine_address,
+ bool is_test, bool cookie_auth) {
+ worker_->Login(jid, password, machine_address, is_test, cookie_auth);
+}
+
+void LibjinglePlus::SendPresence(const buzz::Status & s) {
+ worker_->SendPresence(s);
+}
+
+void LibjinglePlus::SendDirectedPresence(const buzz::Jid & j, const buzz::Status & s) {
+ worker_->SendDirectedPresence(j,s);
+}
+
+void LibjinglePlus::SendDirectedMUCPresence(const buzz::Jid & j,
+ const buzz::Status & s, const std::string &user_nick,
+ const std::string &api_capability, const std::string &api_message,
+ const std::string &role) {
+ worker_->SendDirectedMUCPresence(j,s,user_nick,api_capability,api_message,
+ role);
+}
+
+void LibjinglePlus::SendXmppMessage(const buzz::XmppMessage & m) {
+ worker_->SendXmppMessage(m);
+}
+
+void LibjinglePlus::SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+ const buzz::XmlElement *iq_element) {
+ worker_->SendXmppIq(to_jid, is_get, iq_element);
+}
+
+void LibjinglePlus::DoCallbacks() {
+ worker_->DoCallbacks();
+}
diff --git a/talk/examples/plus/libjingleplus.h b/talk/examples/plus/libjingleplus.h
new file mode 100644
index 0000000..a2898f5
--- /dev/null
+++ b/talk/examples/plus/libjingleplus.h
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// LibjinglePlus is a class that connects to Google Talk, creates
+// some common tasks, and emits signals when things change
+
+#ifndef LIBJINGLEPLUS_H__
+#define LIBJINGLEPLUS_H__
+
+#include "talk/base/basicdefs.h"
+#include "talk/app/rosteritem.h"
+#include "talk/app/message.h"
+#include "talk/app/status.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/base/scoped_ptr.h"
+
+
+class LibjinglePlusWorker;
+
+class LibjinglePlusNotify {
+ public:
+ virtual ~LibjinglePlusNotify() {}
+
+ /* Libjingle+ works on its own thread. It will call WakeupMainThread
+ * when it has something to report. The main thread should then wake up,
+ * and call DoCallbacks on the LibjinglePlus object.
+ *
+ * This function gets called from libjingle+'s worker thread. All other
+ * methods in LibjinglePlusNotify get called from the thread you call
+ * DoCallbacks() on.
+ *
+ * If running on Windows, libjingle+ will use Windows messages to generate
+ * callbacks from the main thread, and you don't need to do anything here.
+ */
+ virtual void WakeupMainThread() = 0;
+
+ /* Connection */
+ /* Called when the connection state changes */
+ virtual void OnStateChange(buzz::XmppEngine::State) = 0;
+
+ /* Called when the socket closes */
+ virtual void OnSocketClose(int error_code) = 0;
+
+ /* Called when XMPP is being sent or received. Used for debugging */
+ virtual void OnXmppOutput(const std::string &output) = 0;
+ virtual void OnXmppInput(const std::string &input) = 0;
+
+ /* Presence */
+ /* Called when someone's Status is updated */
+ virtual void OnStatusUpdate(const buzz::Status &status) = 0;
+
+ /* Called when a status update results in an error */
+ virtual void OnStatusError(const buzz::XmlElement &stanza) = 0;
+
+ /* Called with an IQ return code */
+ virtual void OnIqDone(bool success, const buzz::XmlElement &stanza) = 0;
+
+ /* Message */
+ /* Called when a message comes in. */
+ virtual void OnMessage(const buzz::XmppMessage &message) = 0;
+
+ /* Roster */
+
+ /* Called when we start refreshing the roster */
+ virtual void OnRosterRefreshStarted() = 0;
+ /* Called when we have the entire roster */
+ virtual void OnRosterRefreshFinished() = 0;
+ /* Called when an item on the roster is created or updated */
+ virtual void OnRosterItemUpdated(const buzz::RosterItem &ri) = 0;
+ /* Called when an item on the roster is removed */
+ virtual void OnRosterItemRemoved(const buzz::RosterItem &ri) = 0;
+
+ /* Subscriptions */
+ virtual void OnRosterSubscribe(const buzz::Jid &jid) = 0;
+ virtual void OnRosterUnsubscribe(const buzz::Jid &jid) = 0;
+ virtual void OnRosterSubscribed(const buzz::Jid &jid) = 0;
+ virtual void OnRosterUnsubscribed(const buzz::Jid &jid) = 0;
+
+};
+
+class LibjinglePlus
+{
+ public:
+ /* Provide the constructor with your interface. */
+ LibjinglePlus(LibjinglePlusNotify *notify);
+ ~LibjinglePlus();
+
+ /* Logs in and starts doing stuff
+ *
+ * If cookie_auth is true, password must be a Gaia SID. Otherwise,
+ * it should be the user's password
+ */
+ void Login(const std::string &username, const std::string &password,
+ const std::string &machine_address, bool is_test, bool cookie_auth);
+
+ /* Set Presence */
+ void SendPresence(const buzz::Status & s);
+ void SendDirectedPresence(const buzz::Jid & j, const buzz::Status & s);
+ void SendDirectedMUCPresence(const buzz::Jid & j, const buzz::Status & s,
+ const std::string &user_nick, const std::string &api_capability,
+ const std::string &api_message, const std::string &role);
+
+ /* Send Message */
+ void SendXmppMessage(const buzz::XmppMessage & m);
+
+ /* Send IQ */
+ void SendXmppIq(const buzz::Jid &to_jid, bool is_get,
+ const buzz::XmlElement *iq_element);
+
+ /* Set Roster */
+ void UpdateRosterItem(const buzz::Jid & jid, const std::string & name,
+ const std::vector<std::string> & groups, buzz::GrType grt);
+ void RemoveRosterItem(const buzz::Jid &jid);
+
+ /* Call this from the thread you want to receive callbacks on. Typically, this will be called
+ * after your WakeupMainThread() notify function is called.
+ *
+ * On Windows, libjingle+ will trigger its callback from the Windows message loop, and
+ * you needn't call this yourself.
+ */
+ void DoCallbacks();
+
+ private:
+ void LoginInternal(const std::string &jid, const std::string &password,
+ const std::string &machine_address, bool is_test);
+
+ LibjinglePlusWorker *worker_;
+};
+
+#endif // LIBJINGLE_PLUS_H__
diff --git a/talk/examples/plus/presencepushtask.cc b/talk/examples/plus/presencepushtask.cc
new file mode 100644
index 0000000..9d5ed28
--- /dev/null
+++ b/talk/examples/plus/presencepushtask.cc
@@ -0,0 +1,201 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/stringencode.h"
+#include "presencepushtask.h"
+#include "talk/xmpp/constants.h"
+#include <sstream>
+
+
+namespace buzz {
+
+// string helper functions -----------------------------------------------------
+
+static bool
+IsXmlSpace(int ch) {
+ return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+}
+
+static bool
+ListContainsToken(const std::string & list, const std::string & token) {
+ size_t i = list.find(token);
+ if (i == std::string::npos || token.empty())
+ return false;
+ bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1]));
+ bool boundary_after = (i == list.length() - token.length() || IsXmlSpace(list[i + token.length()]));
+ return boundary_before && boundary_after;
+}
+
+
+bool
+PresencePushTask::HandleStanza(const XmlElement * stanza) {
+ if (stanza->Name() != QN_PRESENCE)
+ return false;
+ if (stanza->HasAttr(QN_TYPE) && stanza->Attr(QN_TYPE) != STR_UNAVAILABLE) {
+ if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+ // Pass on the error.
+ const XmlElement* error_xml_elem = stanza->FirstNamed(QN_ERROR);
+ if (!error_xml_elem) {
+ return false;
+ }
+ SignalStatusError(*error_xml_elem);
+ return true;
+ }
+ }
+ QueueStanza(stanza);
+ return true;
+}
+
+static bool IsUtf8FirstByte(int c) {
+ return (((c)&0x80)==0) || // is single byte
+ ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+int
+PresencePushTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+ Status s;
+
+ s.set_jid(Jid(stanza->Attr(QN_FROM)));
+
+ if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+ s.set_available(false);
+ SignalStatusUpdate(s);
+ }
+ else {
+ s.set_available(true);
+ const XmlElement * status = stanza->FirstNamed(QN_STATUS);
+ if (status != NULL) {
+ s.set_status(status->BodyText());
+
+ // Truncate status messages longer than 300 bytes
+ if (s.status().length() > 300) {
+ size_t len = 300;
+
+ // Be careful not to split legal utf-8 chars in half
+ while (!IsUtf8FirstByte(s.status()[len]) && len > 0) {
+ len -= 1;
+ }
+ std::string truncated(s.status(), 0, len);
+ s.set_status(truncated);
+ }
+ }
+
+ const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+ if (priority != NULL) {
+ int pri;
+ if (talk_base::FromString(priority->BodyText(), &pri)) {
+ s.set_priority(pri);
+ }
+ }
+
+ const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+ if (show == NULL || show->FirstChild() == NULL) {
+ s.set_show(Status::SHOW_ONLINE);
+ }
+ else {
+ if (show->BodyText() == "away") {
+ s.set_show(Status::SHOW_AWAY);
+ }
+ else if (show->BodyText() == "xa") {
+ s.set_show(Status::SHOW_XA);
+ }
+ else if (show->BodyText() == "dnd") {
+ s.set_show(Status::SHOW_DND);
+ }
+ else if (show->BodyText() == "chat") {
+ s.set_show(Status::SHOW_CHAT);
+ }
+ else {
+ s.set_show(Status::SHOW_ONLINE);
+ }
+ }
+
+ const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+ if (caps != NULL) {
+ std::string node = caps->Attr(QN_NODE);
+ std::string ver = caps->Attr(QN_VER);
+ std::string exts = caps->Attr(QN_EXT);
+
+ s.set_know_capabilities(true);
+ std::string capability;
+ std::stringstream ss(exts);
+ while (ss >> capability) {
+ s.AddCapability(capability);
+ }
+
+ s->set_caps_node(node);
+ s->set_version(ver);
+ }
+
+ const XmlElement* delay = stanza->FirstNamed(kQnDelayX);
+ if (delay != NULL) {
+ // Ideally we would parse this according to the Psuedo ISO-8601 rules
+ // that are laid out in JEP-0082:
+ // http://www.jabber.org/jeps/jep-0082.html
+ std::string stamp = delay->Attr(kQnStamp);
+ s.set_sent_time(stamp);
+ }
+
+ const XmlElement *nick = stanza->FirstNamed(kQnNickname);
+ if (nick) {
+ std::string user_nick = nick->BodyText();
+ s.set_user_nick(user_nick);
+ }
+
+ const XmlElement *plugin = stanza->FirstNamed(QN_PLUGIN);
+ if (plugin) {
+ const XmlElement *api_cap = plugin->FirstNamed(QN_CAPABILITY);
+ if (api_cap) {
+ const std::string &api_capability = api_cap->BodyText();
+ s.set_api_capability(api_capability);
+ }
+ const XmlElement *api_msg = plugin->FirstNamed(QN_DATA);
+ if (api_msg) {
+ const std::string &api_message = api_msg->BodyText();
+ s.set_api_message(api_message);
+ }
+ }
+
+ const XmlElement* data_x = stanza->FirstNamed(QN_MUC_USER_X);
+ if (data_x != NULL) {
+ const XmlElement* item = data_x->FirstNamed(QN_MUC_USER_ITEM);
+ if (item != NULL) {
+ s.set_muc_role(item->Attr(QN_ROLE));
+ }
+ }
+
+ SignalStatusUpdate(s);
+ }
+
+ return STATE_START;
+}
+
+
+}
diff --git a/talk/examples/plus/presencepushtask.h b/talk/examples/plus/presencepushtask.h
new file mode 100644
index 0000000..b9c8038
--- /dev/null
+++ b/talk/examples/plus/presencepushtask.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 _PRESENCEPUSHTASK_H_
+#define _PRESENCEPUSHTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+#include "talk/app/status.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+
+public:
+ PresencePushTask(Task * parent) : XmppTask(parent, XmppEngine::HL_TYPE) {}
+ virtual int ProcessStart();
+ sigslot::signal1<const Status &>SignalStatusUpdate;
+ sigslot::signal1<const XmlElement &> SignalStatusError;
+
+protected:
+ virtual bool HandleStanza(const XmlElement * stanza);
+};
+
+
+}
+
+#endif
diff --git a/talk/examples/plus/rostertask.cc b/talk/examples/plus/rostertask.cc
new file mode 100644
index 0000000..1344d08
--- /dev/null
+++ b/talk/examples/plus/rostertask.cc
@@ -0,0 +1,218 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 "rostertask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/base/stream.h"
+
+#undef WIN32
+#ifdef WIN32
+#include "talk/app/win32/offlineroster.h"
+#endif
+
+namespace buzz {
+
+class RosterTask::RosterGetTask : public XmppTask {
+public:
+ RosterGetTask(Task * parent) : XmppTask(parent, XmppEngine::HL_SINGLE),
+ done_(false) {}
+
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+
+protected:
+ virtual bool HandleStanza(const XmlElement * stanza);
+
+ bool done_;
+};
+
+//==============================================================================
+// RosterTask
+//==============================================================================
+void RosterTask::RefreshRosterNow() {
+ RosterGetTask* get_task = new RosterGetTask(this);
+ ResumeTimeout();
+ get_task->Start();
+}
+
+void RosterTask::TranslateItems(const XmlElement * rosterQueryResult) {
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+ // We build up a list of contacts which have had information persisted offline.
+ // we'll remove items from this list if we get a buzz::SUBSCRIBE_REMOVE
+ // subscription. After updating all the items from the server, we'll then
+ // update (and merge) any roster items left in our map of offline items
+ XmlElement *el_local = OfflineRoster::RetrieveOfflineRoster(GetClient()->jid());
+ std::map<buzz::Jid, RosterItem> jid_to_item;
+ if (el_local) {
+ for (XmlElement *el_item = el_local->FirstNamed(QN_ROSTER_ITEM);
+ el_item != NULL;
+ el_item = el_item->NextNamed(QN_ROSTER_ITEM)) {
+ RosterItem roster_item;
+ roster_item.FromXml(el_item);
+
+ jid_to_item[roster_item.jid()] = roster_item;
+ }
+ }
+#endif // WIN32
+#endif // FEATURE_ENABLE_PSTN
+
+ const XmlElement * xml_item;
+ for (xml_item = rosterQueryResult->FirstNamed(QN_ROSTER_ITEM);
+ xml_item != NULL; xml_item = xml_item->NextNamed(QN_ROSTER_ITEM)) {
+ RosterItem roster_item;
+ roster_item.FromXml(xml_item);
+
+ if (roster_item.subscription() == buzz::SUBSCRIBE_REMOVE) {
+ SignalRosterItemRemoved(roster_item);
+
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+ std::map<buzz::Jid, RosterItem>::iterator it =
+ jid_to_item.find(roster_item.jid());
+
+ if (it != jid_to_item.end())
+ jid_to_item.erase(it);
+#endif
+#endif
+ } else {
+ SignalRosterItemUpdated(roster_item, false);
+ }
+ }
+
+#if defined(FEATURE_ENABLE_PSTN)
+#ifdef WIN32
+ for (std::map<buzz::Jid, RosterItem>::iterator it = jid_to_item.begin();
+ it != jid_to_item.end(); ++it) {
+ SignalRosterItemUpdated(it->second, true);
+ }
+#endif
+#endif
+}
+
+int RosterTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ if (stanza->Name() == QN_IQ) {
+ SuspendTimeout();
+ bool result = (stanza->Attr(QN_TYPE) == STR_RESULT);
+ if (result)
+ SignalRosterRefreshStarted();
+
+ TranslateItems(stanza->FirstNamed(QN_ROSTER_QUERY));
+
+ if (result)
+ SignalRosterRefreshFinished();
+ } else if (stanza->Name() == QN_PRESENCE) {
+ Jid jid(stanza->Attr(QN_FROM));
+ std::string type = stanza->Attr(QN_TYPE);
+ if (type == "subscribe")
+ SignalSubscribe(jid);
+ else if (type == "unsubscribe")
+ SignalUnsubscribe(jid);
+ else if (type == "subscribed")
+ SignalSubscribed(jid);
+ else if (type == "unsubscribed")
+ SignalUnsubscribed(jid);
+ }
+
+ return STATE_START;
+}
+
+bool RosterTask::HandleStanza(const XmlElement * stanza) {
+ if (!MatchRequestIq(stanza, STR_SET, QN_ROSTER_QUERY)) {
+ // Not a roster IQ. Look for a presence instead
+ if (stanza->Name() != QN_PRESENCE)
+ return false;
+ if (!stanza->HasAttr(QN_TYPE))
+ return false;
+ std::string type = stanza->Attr(QN_TYPE);
+ if (type == "subscribe" || type == "unsubscribe" ||
+ type == "subscribed" || type == "unsubscribed") {
+ QueueStanza(stanza);
+ return true;
+ }
+ return false;
+ }
+
+ // only respect roster push from the server
+ Jid from(stanza->Attr(QN_FROM));
+ if (from != JID_EMPTY &&
+ !from.BareEquals(GetClient()->jid()) &&
+ from != Jid(GetClient()->jid().domain()))
+ return false;
+
+ XmlElement * result = MakeIqResult(stanza);
+ result->AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ SendStanza(result);
+
+ QueueStanza(stanza);
+ return true;
+}
+
+
+//==============================================================================
+// RosterTask::RosterGetTask
+//==============================================================================
+int RosterTask::RosterGetTask::ProcessStart() {
+ talk_base::scoped_ptr<XmlElement> get(MakeIq(STR_GET, JID_EMPTY, task_id()));
+ get->AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ get->AddAttr(QN_XMLNS_GR, NS_GR, 1);
+ get->AddAttr(QN_GR_EXT, "2", 1);
+ get->AddAttr(QN_GR_INCLUDE, "all", 1);
+ if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+ return STATE_ERROR;
+ }
+ return STATE_RESPONSE;
+}
+
+int RosterTask::RosterGetTask::ProcessResponse() {
+ if (done_)
+ return STATE_DONE;
+ return STATE_BLOCKED;
+}
+
+bool RosterTask::RosterGetTask::HandleStanza(const XmlElement * stanza) {
+ if (!MatchResponseIq(stanza, JID_EMPTY, task_id()))
+ return false;
+
+ if (stanza->Attr(QN_TYPE) != STR_RESULT)
+ return false;
+
+ // Queue the stanza with the parent so these don't get handled out of order
+ RosterTask* parent = static_cast<RosterTask*>(GetParent());
+ parent->QueueStanza(stanza);
+
+ // Wake ourselves so we can go into the done state
+ done_ = true;
+ Wake();
+ return true;
+}
+
+}
diff --git a/talk/examples/plus/rostertask.h b/talk/examples/plus/rostertask.h
new file mode 100644
index 0000000..beab1f9
--- /dev/null
+++ b/talk/examples/plus/rostertask.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 _PHONE_CLIENT_ROSTERTASK_H_
+#define _PHONE_CLIENT_ROSTERTASK_H_
+
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/app/rosteritem.h"
+#include "talk/base/sigslot.h"
+
+namespace buzz {
+
+class RosterTask : public XmppTask {
+public:
+ RosterTask(Task * parent) :
+ XmppTask(parent, XmppEngine::HL_TYPE) {}
+
+ // Roster items removed or updated. This can come from a push or a get
+ sigslot::signal2<const RosterItem &, bool> SignalRosterItemUpdated;
+ sigslot::signal1<const RosterItem &> SignalRosterItemRemoved;
+
+ // Subscription messages
+ sigslot::signal1<const Jid &> SignalSubscribe;
+ sigslot::signal1<const Jid &> SignalUnsubscribe;
+ sigslot::signal1<const Jid &> SignalSubscribed;
+ sigslot::signal1<const Jid &> SignalUnsubscribed;
+
+ // Roster get
+ void RefreshRosterNow();
+ sigslot::signal0<> SignalRosterRefreshStarted;
+ sigslot::signal0<> SignalRosterRefreshFinished;
+
+ virtual int ProcessStart();
+
+protected:
+ void TranslateItems(const XmlElement *rosterQueryResult);
+
+ virtual bool HandleStanza(const XmlElement * stanza);
+
+ // Inner class for doing the roster get
+ class RosterGetTask;
+ friend class RosterGetTask;
+};
+
+}
+
+#endif // _PHONE_CLIENT_ROSTERTASK_H_
diff --git a/talk/examples/plus/testutil/libjingleplus_main.cc b/talk/examples/plus/testutil/libjingleplus_main.cc
new file mode 100644
index 0000000..b5a0686
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_main.cc
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <string>
+
+#include "talk/base/thread.h"
+#include "talk/libjingle-plus/libjingleplus.h"
+#include "talk/libjingle-plus/testutil/libjingleplus_test_notifier.h"
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler =0;
+#endif
+
+
+void SetConsoleEcho(bool on) {
+#ifdef WIN32
+ HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+ if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+ return;
+
+ DWORD mode;
+ if (!GetConsoleMode(hIn, &mode))
+ return;
+
+ if (on) {
+ mode = mode | ENABLE_ECHO_INPUT;
+ } else {
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ }
+
+ SetConsoleMode(hIn, mode);
+#else
+ if (on)
+ system("stty echo");
+ else
+ system("stty -echo");
+#endif
+}
+
+int main (int argc, char **argv)
+{
+ std::string username;
+ std::string password;
+
+ bool gaia = false;
+
+ for (int i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-gaia"))
+ gaia = true;
+ }
+
+ std::cout << "Username: ";
+ std::cin >> username;
+ std::cout << (gaia ? "Gaia cookie: " : "Password: ");
+ SetConsoleEcho(false);
+ std::cin >> password;
+ SetConsoleEcho(true);
+
+ // Create a LibjinglePlus object and give it the notifier interface
+ LibjinglePlus ljp(new Notifier);
+
+ // Login
+ ljp.Login(username, password, "talk.google.com", false, gaia);
+
+ buzz::Status s;
+ s.set_available(true);
+ s.set_show(buzz::Status::SHOW_ONLINE);
+ s.set_status("I'm online.");
+
+ buzz::XmppMessage m;
+ m.set_to(buzz::Jid(username + "@gmail.com"));
+ m.set_body("What's up?");
+
+ // Typically, you would wait for WakeupMainThread to be called, and then call
+ // DoCallbacks. Because I have nothing else to do on the main thread, I'm just going
+ // to do a few things after 10 seconds and then poll every 2ms.
+ Sleep(10000);
+ // ljp.DoCallbacks();
+ ljp.SendPresence(s);
+ ljp.SendXmppMessage(m);
+
+#ifdef WIN32
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ DispatchMessage(&msg);
+ }
+#else
+ for (;;) {
+ ljp.DoCallbacks();
+ Sleep(2);
+ }
+#endif
+}
diff --git a/talk/examples/plus/testutil/libjingleplus_test_notifier.h b/talk/examples/plus/testutil/libjingleplus_test_notifier.h
new file mode 100644
index 0000000..3a1af55
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_test_notifier.h
@@ -0,0 +1,101 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <string>
+
+#include "talk/libjingle-plus/libjingleplus.h"
+
+class Notifier : virtual public LibjinglePlusNotify {
+ virtual void OnStateChange(buzz::XmppEngine::State state) {
+ std::cout << "State change: " << state << std::endl;
+ }
+
+ virtual void OnSocketClose(int error_code) {
+ std::cout << "Socket close: " << error_code << std::endl;
+ }
+
+ virtual void OnXmppOutput(const std::string &output) {
+ std::cout << ">>>>>>>>" << std::endl << output << std::endl << ">>>>>>>>" << std::endl;
+ }
+
+ virtual void OnXmppInput(const std::string &input) {
+ std::cout << "<<<<<<<<" << std::endl << input << std::endl << "<<<<<<<<" << std::endl;
+ }
+
+
+ virtual void OnStatusUpdate(const buzz::Status &status) {
+ std::string from = status.jid().Str();
+ std::cout << from << " - " << status.status() << std::endl;
+ }
+
+ virtual void OnStatusError(const buzz::XmlElement &stanza) {
+ }
+
+ virtual void OnIqDone(bool success, const buzz::XmlElement &stanza) {
+ }
+
+ virtual void OnMessage(const buzz::XmppMessage &m) {
+ if (m.body() != "")
+ std::cout << m.from().Str() << ": " << m.body() << std::endl;
+ }
+
+ void OnRosterItemUpdated(const buzz::RosterItem &ri) {
+ std::cout << "Roster item: " << ri.jid().Str() << std::endl;
+ }
+
+ virtual void OnRosterItemRemoved(const buzz::RosterItem &ri) {
+ std::cout << "Roster item removed: " << ri.jid().Str() << std::endl;
+ }
+
+ virtual void OnRosterSubscribe(const buzz::Jid& jid) {
+ std::cout << "Subscribing: " << jid.Str() << std::endl;
+ }
+
+ virtual void OnRosterUnsubscribe(const buzz::Jid &jid) {
+ std::cout << "Unsubscribing: " <<jid.Str() << std::endl;
+ }
+
+ virtual void OnRosterSubscribed(const buzz::Jid &jid) {
+ std::cout << "Subscribed: " << jid.Str() << std::endl;
+ }
+
+ virtual void OnRosterUnsubscribed(const buzz::Jid &jid) {
+ std::cout << "Unsubscribed: " << jid.Str() << std::endl;
+ }
+
+ virtual void OnRosterRefreshStarted() {
+ std::cout << "Refreshing roster." << std::endl;
+ }
+
+ virtual void OnRosterRefreshFinished() {
+ std::cout << "Roster refreshed." << std::endl;
+ }
+
+ virtual void WakeupMainThread() {
+ }
+};
diff --git a/talk/examples/plus/testutil/libjingleplus_unittest.cc b/talk/examples/plus/testutil/libjingleplus_unittest.cc
new file mode 100644
index 0000000..d7f6325
--- /dev/null
+++ b/talk/examples/plus/testutil/libjingleplus_unittest.cc
@@ -0,0 +1,52 @@
+/*
+ * libjingle
+ * Copyright 2006, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "testing/base/gunit.h"
+#include "talk/libjingle-plus/libjingleplus.h"
+#include "talk/libjingle-plus/testutil/libjingleplus_test_notifier.h"
+
+#if defined(_MSC_VER) && (_MSC_VER < 1400)
+void __cdecl std::_Throw(const std::exception &) {}
+std::_Prhand std::_Raise_handler =0;
+#endif
+
+namespace talk_base {
+
+TEST(LibjingleTest, ConstructDestruct) {
+ for (int i = 0; i < 5; ++i) {
+ LibjinglePlus *libjingleplus = new LibjinglePlus(new Notifier);
+ libjingleplus->Login("eaterleaver0", "Buzzt3st", "talk.google.com", false, false);
+
+ delete libjingleplus;
+ }
+}
+}
+
+int main(int argc, char** argv) {
+ testing::ParseGUnitFlags(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index bfd82b0..ff073a0 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -26,10 +26,14 @@
],
includedirs = [
"third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
"third_party/gtest",
],
cppdefines = [
"LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
],
)
talk.Library(env, name = "srtp",
@@ -70,19 +74,16 @@
lin_srcs = [
"base/latebindingsymboltable.cc",
"base/linux.cc",
+ "base/linuxfdwalk.c",
+ "base/libdbusglibsymboltable.cc",
"session/phone/libudevsymboltable.cc",
+ "session/phone/linuxdevicemanager.cc",
"session/phone/v4llookup.cc",
"sound/alsasoundsystem.cc",
"sound/alsasymboltable.cc",
"sound/linuxsoundsystem.cc",
- "sound/nullsoundsystem.cc",
- "sound/nullsoundsystemfactory.cc",
"sound/pulseaudiosoundsystem.cc",
"sound/pulseaudiosymboltable.cc",
- "sound/platformsoundsystem.cc",
- "sound/platformsoundsystemfactory.cc",
- "sound/soundsysteminterface.cc",
- "sound/soundsystemproxy.cc",
],
dependent_target_settings = {
'lin_libs': [
@@ -100,13 +101,17 @@
"base/macconversion.cc",
"base/macsocketserver.cc",
"base/macutils.cc",
+ "base/macwindowpicker.cc",
+ "base/scoped_autorelease_pool.mm",
"session/phone/carbonvideorenderer.cc",
- "session/phone/devicemanager_mac.mm",
+ "session/phone/macdevicemanager.cc",
+ "session/phone/macdevicemanagermm.mm",
],
posix_srcs = [
"base/unixfilesystem.cc",
"base/opensslidentity.cc",
"base/opensslstreamadapter.cc",
+ "base/posix.cc",
"base/sslidentity.cc",
"base/sslstreamadapter.cc",
],
@@ -127,13 +132,17 @@
"base/asynctcpsocket.cc",
"base/asyncudpsocket.cc",
"base/autodetectproxy.cc",
+ "base/bandwidthsmoother.cc",
"base/base64.cc",
"base/basicpacketsocketfactory.cc",
"base/bytebuffer.cc",
"base/checks.cc",
"base/common.cc",
+ "base/cpuid.cc",
+ "base/cpumonitor.cc",
"base/diskcache.cc",
"base/event.cc",
+ "base/filelock.cc",
"base/fileutils.cc",
"base/firewallsocketserver.cc",
"base/flags.cc",
@@ -144,18 +153,26 @@
"base/httpcommon.cc",
"base/httprequest.cc",
"base/httpserver.cc",
+ "base/ipaddress.cc",
"base/logging.cc",
"base/md5c.c",
"base/messagehandler.cc",
"base/messagequeue.cc",
+ "base/multipart.cc",
+ "base/natserver.cc",
+ "base/natsocketfactory.cc",
+ "base/nattypes.cc",
"base/nethelpers.cc",
"base/network.cc",
"base/openssladapter.cc",
+ "base/optionsfile.cc",
"base/pathutils.cc",
"base/physicalsocketserver.cc",
"base/proxydetect.cc",
"base/proxyinfo.cc",
+ "base/proxyserver.cc",
"base/ratetracker.cc",
+ "base/sharedexclusivelock.cc",
"base/signalthread.cc",
"base/socketadapters.cc",
"base/socketaddress.cc",
@@ -168,18 +185,27 @@
"base/stringdigest.cc",
"base/stringencode.cc",
"base/stringutils.cc",
+ "base/systeminfo.cc",
"base/task.cc",
"base/taskparent.cc",
"base/taskrunner.cc",
+ "base/testclient.cc",
"base/thread.cc",
- "base/time.cc",
+ "base/timeutils.cc",
+ "base/timing.cc",
+ "base/transformadapter.cc",
"base/urlencode.cc",
+ "base/versionparsing.cc",
+ "base/virtualsocketserver.cc",
"base/worker.cc",
"p2p/base/constants.cc",
"p2p/base/p2ptransport.cc",
"p2p/base/p2ptransportchannel.cc",
"p2p/base/parsing.cc",
"p2p/base/port.cc",
+ "p2p/base/portallocator.cc",
+ "p2p/base/portallocatorsessionproxy.cc",
+ "p2p/base/portproxy.cc",
"p2p/base/pseudotcp.cc",
"p2p/base/relayport.cc",
"p2p/base/relayserver.cc",
@@ -199,6 +225,7 @@
"p2p/base/transportchannelproxy.cc",
"p2p/base/udpport.cc",
"p2p/client/basicportallocator.cc",
+ "p2p/client/connectivitychecker.cc",
"p2p/client/httpportallocator.cc",
"p2p/client/socketmonitor.cc",
"session/tunnel/pseudotcpchannel.cc",
@@ -211,11 +238,14 @@
"session/phone/codec.cc",
"session/phone/currentspeakermonitor.cc",
"session/phone/devicemanager.cc",
+ "session/phone/dummydevicemanager.cc",
"session/phone/filemediaengine.cc",
"session/phone/gipsmediaengine.cc",
+ "session/phone/filevideocapturer.cc",
"session/phone/mediaengine.cc",
"session/phone/mediamessages.cc",
"session/phone/mediamonitor.cc",
+ "session/phone/mediarecorder.cc",
"session/phone/mediasession.cc",
"session/phone/mediasessionclient.cc",
"session/phone/rtpdump.cc",
@@ -223,6 +253,18 @@
"session/phone/rtcpmuxfilter.cc",
"session/phone/soundclip.cc",
"session/phone/srtpfilter.cc",
+ "session/phone/ssrcmuxfilter.cc",
+ "session/phone/streamparams.cc",
+ "session/phone/videoadapter.cc",
+ "session/phone/videocapturer.cc",
+ "session/phone/videocommon.cc",
+ "session/phone/videoframe.cc",
+ "sound/nullsoundsystem.cc",
+ "sound/nullsoundsystemfactory.cc",
+ "sound/platformsoundsystem.cc",
+ "sound/platformsoundsystemfactory.cc",
+ "sound/soundsysteminterface.cc",
+ "sound/soundsystemproxy.cc",
"xmllite/qname.cc",
"xmllite/xmlbuilder.cc",
"xmllite/xmlconstants.cc",
@@ -234,11 +276,13 @@
"xmpp/hangoutpubsubclient.cc",
"xmpp/iqtask.cc",
"xmpp/jid.cc",
+ "xmpp/moduleimpl.cc",
"xmpp/mucroomconfigtask.cc",
+ "xmpp/mucroomhistorytask.cc",
"xmpp/mucroomlookuptask.cc",
"xmpp/pubsubclient.cc",
+ "xmpp/pubsub_task.cc",
"xmpp/pubsubtasks.cc",
- "xmpp/ratelimitmanager.cc",
"xmpp/receivetask.cc",
"xmpp/saslmechanism.cc",
"xmpp/xmppclient.cc",
@@ -253,24 +297,36 @@
"third_party/expat-2.0.1/lib",
"third_party/srtp/include",
"third_party/srtp/crypto/include",
+ "third_party/openssl/include",
],
win_srcs = [
+ "base/diskcache_win32.cc",
"base/schanneladapter.cc",
"base/win32.cc",
+ "base/win32regkey.cc",
"base/win32filesystem.cc",
"base/win32securityerrors.cc",
"base/win32socketserver.cc",
"base/win32socketinit.cc",
"base/win32window.cc",
+ "base/win32windowpicker.cc",
"base/winfirewall.cc",
"base/winping.cc",
+ "session/phone/win32devicemanager.cc",
"session/phone/gdivideorenderer.cc",
],
mac_ccflags = [
"-Wno-deprecated-declarations",
],
extra_srcs = [
+ "base/dbus.cc",
"base/json.cc",
+ "base/linuxwindowpicker.cc",
+ "base/natserver_main.cc",
+ "base/maccocoasocketserver.mm",
+ "base/maccocoathreadhelper.mm",
+ "xmpp/chatroommoduleimpl.cc",
+ "xmpp/rostermoduleimpl.cc",
],
)
talk.Library(env, name = "xmpphelp",
@@ -278,6 +334,7 @@
"jingle",
],
srcs = [
+ "examples/login/jingleinfotask.cc",
"examples/login/xmppauth.cc",
"examples/login/xmpppump.cc",
"examples/login/xmppsocket.cc",
@@ -293,10 +350,14 @@
],
includedirs = [
"third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
"third_party/gtest",
],
cppdefines = [
"LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
],
)
talk.App(env, name = "login",
@@ -340,9 +401,6 @@
"crypto",
"ssl",
],
- cppdefines = [
- "FEATURE_ENABLE_VOICEMAIL",
- ],
lin_libs = [
# :TODO: (kedong) add nexus videorenderer later
# "videorenderer",
@@ -351,13 +409,12 @@
"examples/call/call_main.cc",
"examples/call/callclient.cc",
"examples/call/console.cc",
- "examples/call/discoitemsquerytask.cc",
"examples/call/friendinvitesendtask.cc",
+ "examples/call/mediaenginefactory.cc",
"examples/call/mucinviterecvtask.cc",
"examples/call/mucinvitesendtask.cc",
"examples/call/presenceouttask.cc",
"examples/call/presencepushtask.cc",
- "examples/call/voicemailjidrequester.cc",
],
libs = [
"jingle",
@@ -386,39 +443,267 @@
],
)
talk.Unittest(env, name = "base",
- libs = [
- "jingle",
+ lin_srcs = [
+ "base/latebindingsymboltable_unittest.cc",
+ "base/linux_unittest.cc",
+ "base/linuxfdwalk_unittest.cc",
+ ],
+ mac_srcs = [
+ "base/macsocketserver_unittest.cc",
+ "base/macutils_unittest.cc",
+ "base/macwindowpicker_unittest.cc",
+ ],
+ posix_srcs = [
+ "base/sslidentity_unittest.cc",
+ "base/sslstreamadapter_unittest.cc",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
],
srcs = [
"base/asynchttprequest_unittest.cc",
+ "base/atomicops_unittest.cc",
"base/autodetectproxy_unittest.cc",
+ "base/bandwidthsmoother_unittest.cc",
+ "base/base64_unittest.cc",
+ "base/buffer_unittest.cc",
"base/bytebuffer_unittest.cc",
+ "base/cpuid_unittest.cc",
+ "base/cpumonitor_unittest.cc",
"base/event_unittest.cc",
+ "base/filelock_unittest.cc",
"base/fileutils_unittest.cc",
"base/helpers_unittest.cc",
"base/host_unittest.cc",
"base/httpbase_unittest.cc",
"base/httpcommon_unittest.cc",
"base/httpserver_unittest.cc",
+ "base/ipaddress_unittest.cc",
"base/logging_unittest.cc",
"base/messagequeue_unittest.cc",
+ "base/multipart_unittest.cc",
+ "base/nat_unittest.cc",
"base/network_unittest.cc",
+ "base/optionsfile_unittest.cc",
+ "base/pathutils_unittest.cc",
+ "base/physicalsocketserver_unittest.cc",
+ "base/proxy_unittest.cc",
+ "base/proxydetect_unittest.cc",
+ "base/ratetracker_unittest.cc",
"base/referencecountedsingletonfactory_unittest.cc",
+ "base/rollingaccumulator_unittest.cc",
+ "base/sharedexclusivelock_unittest.cc",
"base/signalthread_unittest.cc",
+ "base/socket_unittest.cc",
"base/socketaddress_unittest.cc",
"base/stream_unittest.cc",
"base/stringencode_unittest.cc",
"base/stringutils_unittest.cc",
+ "base/systeminfo_unittest.cc",
"base/task_unittest.cc",
+ "base/testclient_unittest.cc",
"base/thread_unittest.cc",
- "base/time_unittest.cc",
+ "base/timeutils_unittest.cc",
"base/urlencode_unittest.cc",
+ "base/versionparsing_unittest.cc",
+ "base/virtualsocket_unittest.cc",
],
includedirs = [
"third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
+ "third_party/gtest",
+ ],
+ win_srcs = [
+ "base/win32_unittest.cc",
+ "base/win32regkey_unittest.cc",
+ "base/win32socketserver_unittest.cc",
+ "base/win32toolhelp_unittest.cc",
+ "base/win32window_unittest.cc",
+ "base/win32windowpicker_unittest.cc",
+ "base/winfirewall_unittest.cc",
+ ],
+ libs = [
+ "jingle",
+ ],
+ extra_srcs = [
+ "base/dbus_unittest.cc",
+ "base/json_unittest.cc",
+ "base/linuxwindowpicker_unittest.cc",
+ ],
+)
+talk.Unittest(env, name = "p2p",
+ mac_FRAMEWORKS = [
+ "Foundation",
+ "IOKit",
+ "QTKit",
+ ],
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ ],
+ srcs = [
+ "p2p/base/p2ptransportchannel_unittest.cc",
+ "p2p/base/port_unittest.cc",
+ "p2p/base/pseudotcp_unittest.cc",
+ "p2p/base/relayport_unittest.cc",
+ "p2p/base/relayserver_unittest.cc",
+ "p2p/base/session_unittest.cc",
+ "p2p/base/stun_unittest.cc",
+ "p2p/base/stunport_unittest.cc",
+ "p2p/base/stunrequest_unittest.cc",
+ "p2p/base/stunserver_unittest.cc",
+ "p2p/base/transport_unittest.cc",
+ "p2p/client/connectivitychecker_unittest.cc",
+ "p2p/client/portallocator_unittest.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
+ "third_party/gtest",
+ ],
+ libs = [
+ "jingle",
+ "expat",
+ ],
+)
+talk.Unittest(env, name = "media",
+ libs = [
+ "jingle",
+ "expat",
+ "srtp",
+ ],
+ srcs = [
+ "session/phone/channel_unittest.cc",
+ "session/phone/channelmanager_unittest.cc",
+ "session/phone/codec_unittest.cc",
+ "session/phone/currentspeakermonitor_unittest.cc",
+ "session/phone/devicemanager_unittest.cc",
+ "session/phone/dummydevicemanager_unittest.cc",
+ "session/phone/filemediaengine_unittest.cc",
+ "session/phone/filevideocapturer_unittest.cc",
+ "session/phone/mediarecorder_unittest.cc",
+ "session/phone/mediamessages_unittest.cc",
+ "session/phone/mediasession_unittest.cc",
+ "session/phone/mediasessionclient_unittest.cc",
+ "session/phone/rtcpmuxfilter_unittest.cc",
+ "session/phone/rtpdump_unittest.cc",
+ "session/phone/rtputils_unittest.cc",
+ "session/phone/srtpfilter_unittest.cc",
+ "session/phone/ssrcmuxfilter_unittest.cc",
+ "session/phone/testutils.cc",
+ "session/phone/videocapturer_unittest.cc",
+ "session/phone/videocommon_unittest.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
"third_party/gtest",
],
cppdefines = [
"LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ ],
+ win_libs = [
+ "winmm.lib",
+ "strmiids",
+ ],
+)
+talk.Unittest(env, name = "sound",
+ libs = [
+ "jingle",
+ ],
+ srcs = [
+ "sound/automaticallychosensoundsystem_unittest.cc",
+ ],
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
+ "third_party/gtest",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ ],
+)
+talk.Unittest(env, name = "xmllite",
+ libs = [
+ "jingle",
+ "expat",
+ ],
+ srcs = [
+ "xmllite/qname_unittest.cc",
+ "xmllite/xmlbuilder_unittest.cc",
+ "xmllite/xmlelement_unittest.cc",
+ "xmllite/xmlnsstack_unittest.cc",
+ "xmllite/xmlparser_unittest.cc",
+ "xmllite/xmlprinter_unittest.cc",
+ ],
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
+ "third_party/gtest",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ ],
+)
+talk.Unittest(env, name = "xmpp",
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ ],
+ srcs = [
+ "xmpp/hangoutpubsubclient_unittest.cc",
+ "xmpp/jid_unittest.cc",
+ "xmpp/mucroomconfigtask_unittest.cc",
+ "xmpp/mucroomlookuptask_unittest.cc",
+ "xmpp/pubsubclient_unittest.cc",
+ "xmpp/pubsubtasks_unittest.cc",
+ "xmpp/util_unittest.cc",
+ "xmpp/xmppengine_unittest.cc",
+ "xmpp/xmpplogintask_unittest.cc",
+ "xmpp/xmppstanzaparser_unittest.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/expat-2.0.1/lib",
+ "third_party/srtp",
+ "third_party/gtest",
+ ],
+ libs = [
+ "jingle",
+ "expat",
+ ],
+ extra_srcs = [
+ "xmpp/chatroommodule_unittest.cc",
],
)
diff --git a/talk/main.scons b/talk/main.scons
index 5ad5065..6b6b891 100644
--- a/talk/main.scons
+++ b/talk/main.scons
@@ -69,6 +69,8 @@
'HAVE_SRTP',
'HAVE_GIPS',
],
+ # Ensure the os environment is captured for any scripts we call out to
+ ENV = os.environ,
)
# This is where we set common environments
@@ -78,18 +80,15 @@
if platform.architecture()[0] == "64bit":
root_env.SetBits('build_platform_64bit')
-# This bit denotes that an env is for pure 64-bit builds. When set, all build
-# artifacts will be 64-bit. When unset, we will still produce some 64-bit
-# artifacts on 64-bit Linux via the legacy also64bit setting on our targets.
-# When the pure 64-bit build is considered stable, the also64bit setting will go
-# away.
+# This bit denotes that an env is for 64-bit builds. When set, all build
+# artifacts will be 64-bit. When unset, all build artifacts will be 32-bit.
DeclareBit('host_platform_64bit', 'Platform of the host machine (where artifacts will execute) is 64-bit')
DeclareBit('host_platform_mipsel', 'Platform of the host machine (where artifacts will execute) is mipsel')
-def same_arch(env):
- """Whether the host machine and build machine architectures match."""
- return env.Bit('host_platform_64bit') == env.Bit('build_platform_64bit')
+def CrossBuilding(env):
+ return env.Bit('host_platform_64bit') != env.Bit('build_platform_64bit')
+root_env.AddMethod(CrossBuilding)
DeclareBit('use_static_openssl', 'Build OpenSSL as a static library')
@@ -107,6 +106,8 @@
'no', 'or', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv',
'ta', 'te', 'th', 'tl', 'tr', 'uk', 'ur', 'vi', 'zh-CN', 'zh-TW'])
+AddTargetGroup('all_breakpads', 'breakpad files can be built')
+
#-------------------------------------------------------------------------------
# W I N D O W S
#
@@ -293,6 +294,13 @@
# Needed for a clean ABI and for link-time dead-code removal to work
# properly.
'-fvisibility=hidden',
+ # Generate debugging info in the DWARF2 format.
+ '-gdwarf-2',
+ # Generate maximal debugging information. (It is stripped from what we ship
+ # to users, so we want it for both dbg and opt.)
+ # Note that hammer automatically supplies "-g" for mac/linux dbg, so that
+ # flag must be filtered out of linux_dbg and mac_dbg envs below.
+ '-g3',
],
CXXFLAGS = [
'-Wno-non-virtual-dtor',
@@ -311,6 +319,9 @@
#'fill_plist',
],
)
+# Use static OpenSSL on mac so that we can use the latest APIs on all
+# supported mac platforms (10.5+).
+mac_env.SetBits('use_static_openssl')
mac_env.Append(
CPPDEFINES = [
'OSX',
@@ -320,8 +331,6 @@
'-arch', 'i386',
'-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
'-fasm-blocks',
- # Generate debugging info in the DWARF2 format.
- '-gdwarf-2',
],
LINKFLAGS = [
'-Wl,-search_paths_first',
@@ -334,8 +343,7 @@
'-mmacosx-version-min=10.5',
'-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
'-m32',
- '-dead-strip'
- '-gdwarf-2',
+ '-dead_strip',
],
FRAMEWORKS = [
'CoreServices',
@@ -360,8 +368,13 @@
mac_dbg_env.Append(
CCFLAGS = [
'-O0',
- ]
+ ],
+ CPPDEFINES = [
+ 'DEBUG=1',
+ ],
)
+# Remove -g set by hammer, which is not what we want (we have set -g3 above).
+mac_dbg_env.FilterOut(CCFLAGS = ['-g'])
envs.append(mac_dbg_env)
mac_opt_env = mac_env.Clone(
@@ -377,6 +390,7 @@
# out. Maybe it's a different version of gcc?
'-Wno-unused-variable',
],
+ # Hammer automatically specifies -Os for mac opt.
)
envs.append(mac_opt_env)
@@ -399,11 +413,6 @@
# Needed for link-time dead-code removal to work properly.
'-ffunction-sections',
'-fdata-sections',
- # Generate debugging info in the DWARF2 format.
- '-gdwarf-2',
- # Generate maximal debugging information. (It is stripped from what we ship
- # to users, so we want it for both dbg and opt.)
- '-g3',
],
LINKFLAGS = [
# Enable dead-code removal.
@@ -467,11 +476,26 @@
# product would end up being broken on any computer with a different version
# installed. So instead we build it ourself and statically link to it.
linux_env.SetBits('use_static_openssl')
+
linux_env.SetBits('host_platform_mipsel')
- dbg_groups = ['default', 'all']
+
+ groups = ['all']
+ if not linux_env.CrossBuilding():
+ groups = groups + ['all-native']
+ # The native-arch dbg build is the default.
+ dbg_groups = groups + ['default']
+ native_desc = ', native '
+ # No suffix for native modes.
+ type_suffix = ''
+ else:
+ groups = groups + ['all-cross']
+ dbg_groups = groups
+ native_desc = ', cross-built for '
+
linux_dbg_env = linux_env.Clone(
BUILD_TYPE = 'dbg' + type_suffix,
- BUILD_TYPE_DESCRIPTION = 'Linux debug build' + desc_suffix,
+ BUILD_TYPE_DESCRIPTION = 'Linux debug build%s%s' % (native_desc,
+ desc_suffix),
BUILD_GROUPS = dbg_groups,
tools = ['target_debug'],
)
@@ -481,8 +505,9 @@
linux_opt_env = linux_env.Clone(
BUILD_TYPE = 'opt' + type_suffix,
- BUILD_TYPE_DESCRIPTION = 'Linux optimized build' + desc_suffix,
- BUILD_GROUPS = ['all'],
+ BUILD_TYPE_DESCRIPTION = 'Linux optimized build%s%s' % (native_desc,
+ desc_suffix),
+ BUILD_GROUPS = groups,
tools = ['target_optimized'],
)
# Remove -O2 set by hammer, which is not what we want.
@@ -492,6 +517,90 @@
gen_linux_mips(linux_mipsel_env, '', '')
+#-------------------------------------------------------------------------------
+# L I N U X -- C R O S S -- B U I L D
+
+# Cross build requires the following tool names be provided by the environment:
+linux_cross_common_env = linux_common_env.Clone(
+ AR = os.environ.get("AR"),
+ AS = os.environ.get("AS"),
+ LD = os.environ.get("LD"),
+ NM = os.environ.get("NM"),
+ RANLIB = os.environ.get("RANLIB"),
+ CC = str(os.environ.get("CC")) +
+ ' --sysroot=' + str(os.environ.get("SYSROOT")),
+ CXX = str(os.environ.get("CXX")) +
+ ' --sysroot=' + str(os.environ.get("SYSROOT")),
+)
+
+# The rest of these paths and flags are optional:
+if os.environ.get("CPPPATH"):
+ linux_cross_common_env.Append(
+ CPPPATH = os.environ.get("CPPPATH").split(':'),
+ )
+if os.environ.get("LIBPATH"):
+ linux_cross_common_env.Append(
+ LIBPATH = os.environ.get("LIBPATH").split(':'),
+ )
+if os.environ.get("CFLAGS"):
+ linux_cross_common_env.Append(
+ CFLAGS = os.environ.get("CFLAGS").split(' '),
+ )
+if os.environ.get("CCFLAGS"):
+ linux_cross_common_env.Append(
+ CCFLAGS = os.environ.get("CCFLAGS").split(' '),
+ )
+if os.environ.get("CXXFLAGS"):
+ linux_cross_common_env.Append(
+ CXXFLAGS = os.environ.get("CXXFLAGS").split(' '),
+ )
+if os.environ.get("LIBFLAGS"):
+ linux_cross_common_env.Append(
+ _LIBFLAGS = os.environ.get("LIBFLAGS").split(' '),
+ )
+
+#-------------------------------------------------------------------------------
+# L I N U X -- C R O S S -- B U I L D -- A R M
+
+linux_cross_arm_env = linux_cross_common_env.Clone()
+linux_cross_arm_env.Append(
+ CPPDEFINES = [
+ 'NACL_BUILD_ARCH=arm',
+ 'DISABLE_EFFECTS=1',
+ ],
+ CCFLAGS = [
+ '-fPIC',
+ ],
+)
+DeclareBit('arm', 'ARM build')
+linux_cross_arm_env.SetBits('arm')
+
+# Detect NEON support from the -mfpu build flag.
+DeclareBit('arm_neon', 'ARM supporting neon')
+if '-mfpu=neon' in linux_cross_arm_env['CFLAGS'] or \
+ '-mfpu=neon' in linux_cross_arm_env['CCFLAGS'] or \
+ '-mfpu=neon' in linux_cross_arm_env['CXXFLAGS']:
+ print "Building with ARM NEON support."
+ linux_cross_arm_env.SetBits('arm_neon')
+
+
+linux_cross_arm_dbg_env = linux_cross_arm_env.Clone(
+ BUILD_TYPE = 'arm-dbg',
+ BUILD_TYPE_DESCRIPTION = 'Cross-compiled ARM debug build',
+ BUILD_GROUPS = ['arm'],
+ tools = ['target_debug'],
+)
+envs.append(linux_cross_arm_dbg_env)
+
+linux_cross_arm_opt_env = linux_cross_arm_env.Clone(
+ BUILD_TYPE = 'arm-opt',
+ BUILD_TYPE_DESCRIPTION = 'Cross-compiled ARM optimized build',
+ BUILD_GROUPS = ['arm'],
+ tools = ['target_optimized'],
+)
+envs.append(linux_cross_arm_opt_env)
+
+
# TODO(): Clone linux envs for 64bit. See 'variant' documentation.
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
index 178bd59..fe89aa7 100644
--- a/talk/p2p/base/constants.cc
+++ b/talk/p2p/base/constants.cc
@@ -25,207 +25,206 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/p2p/base/constants.h"
+
#include <string>
-#include "talk/p2p/base/constants.h"
#include "talk/xmllite/qname.h"
namespace cricket {
-const std::string NS_EMPTY("");
-const std::string NS_JINGLE("urn:xmpp:jingle:1");
-const std::string NS_JINGLE_DRAFT("google:jingle");
-const std::string NS_GINGLE("http://www.google.com/session");
+const char NS_EMPTY[] = "";
+const char NS_JINGLE[] = "urn:xmpp:jingle:1";
+const char NS_JINGLE_DRAFT[] = "google:jingle";
+const char NS_GINGLE[] = "http://www.google.com/session";
// actions (aka <session> or <jingle>)
-const buzz::QName QN_ACTION(true, NS_EMPTY, "action");
-const std::string LN_INITIATOR("initiator");
-const buzz::QName QN_INITIATOR(true, NS_EMPTY, LN_INITIATOR);
-const buzz::QName QN_CREATOR(true, NS_EMPTY, "creator");
+const buzz::StaticQName QN_ACTION = { NS_EMPTY, "action" };
+const char LN_INITIATOR[] = "initiator";
+const buzz::StaticQName QN_INITIATOR = { NS_EMPTY, LN_INITIATOR };
+const buzz::StaticQName QN_CREATOR = { NS_EMPTY, "creator" };
-const buzz::QName QN_JINGLE(true, NS_JINGLE, "jingle");
-const buzz::QName QN_JINGLE_CONTENT(true, NS_JINGLE, "content");
-const buzz::QName QN_JINGLE_CONTENT_NAME(true, NS_EMPTY, "name");
-const buzz::QName QN_JINGLE_CONTENT_MEDIA(true, NS_EMPTY, "media");
-const buzz::QName QN_JINGLE_REASON(true, NS_JINGLE, "reason");
-const std::string JINGLE_CONTENT_MEDIA_AUDIO("audio");
-const std::string JINGLE_CONTENT_MEDIA_VIDEO("video");
-const std::string JINGLE_ACTION_SESSION_INITIATE("session-initiate");
-const std::string JINGLE_ACTION_SESSION_INFO("session-info");
-const std::string JINGLE_ACTION_SESSION_ACCEPT("session-accept");
-const std::string JINGLE_ACTION_SESSION_TERMINATE("session-terminate");
-const std::string JINGLE_ACTION_TRANSPORT_INFO("transport-info");
-const std::string JINGLE_ACTION_TRANSPORT_ACCEPT("transport-accept");
-const std::string JINGLE_ACTION_DESCRIPTION_INFO("description-info");
+const buzz::StaticQName QN_JINGLE = { NS_JINGLE, "jingle" };
+const buzz::StaticQName QN_JINGLE_CONTENT = { NS_JINGLE, "content" };
+const buzz::StaticQName QN_JINGLE_CONTENT_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA = { NS_EMPTY, "media" };
+const buzz::StaticQName QN_JINGLE_REASON = { NS_JINGLE, "reason" };
+const char JINGLE_CONTENT_MEDIA_AUDIO[] = "audio";
+const char JINGLE_CONTENT_MEDIA_VIDEO[] = "video";
+const char JINGLE_ACTION_SESSION_INITIATE[] = "session-initiate";
+const char JINGLE_ACTION_SESSION_INFO[] = "session-info";
+const char JINGLE_ACTION_SESSION_ACCEPT[] = "session-accept";
+const char JINGLE_ACTION_SESSION_TERMINATE[] = "session-terminate";
+const char JINGLE_ACTION_TRANSPORT_INFO[] = "transport-info";
+const char JINGLE_ACTION_TRANSPORT_ACCEPT[] = "transport-accept";
+const char JINGLE_ACTION_DESCRIPTION_INFO[] = "description-info";
-const buzz::QName QN_GINGLE_SESSION(true, NS_GINGLE, "session");
-const std::string GINGLE_ACTION_INITIATE("initiate");
-const std::string GINGLE_ACTION_INFO("info");
-const std::string GINGLE_ACTION_ACCEPT("accept");
-const std::string GINGLE_ACTION_REJECT("reject");
-const std::string GINGLE_ACTION_TERMINATE("terminate");
-const std::string GINGLE_ACTION_CANDIDATES("candidates");
-const std::string GINGLE_ACTION_UPDATE("update");
+const buzz::StaticQName QN_GINGLE_SESSION = { NS_GINGLE, "session" };
+const char GINGLE_ACTION_INITIATE[] = "initiate";
+const char GINGLE_ACTION_INFO[] = "info";
+const char GINGLE_ACTION_ACCEPT[] = "accept";
+const char GINGLE_ACTION_REJECT[] = "reject";
+const char GINGLE_ACTION_TERMINATE[] = "terminate";
+const char GINGLE_ACTION_CANDIDATES[] = "candidates";
+const char GINGLE_ACTION_UPDATE[] = "update";
-const std::string LN_ERROR("error");
-const buzz::QName QN_GINGLE_REDIRECT(true, NS_GINGLE, "redirect");
-const std::string STR_REDIRECT_PREFIX("xmpp:");
+const char LN_ERROR[] = "error";
+const buzz::StaticQName QN_GINGLE_REDIRECT = { NS_GINGLE, "redirect" };
+const char STR_REDIRECT_PREFIX[] = "xmpp:";
// Session Contents (aka Gingle <session><description>
// or Jingle <content><description>)
-const std::string LN_DESCRIPTION("description");
-const std::string LN_PAYLOADTYPE("payload-type");
-const buzz::QName QN_ID(true, NS_EMPTY, "id");
-const buzz::QName QN_SID(true, NS_EMPTY, "sid");
-const buzz::QName QN_NAME(true, NS_EMPTY, "name");
-const buzz::QName QN_CLOCKRATE(true, NS_EMPTY, "clockrate");
-const buzz::QName QN_BITRATE(true, NS_EMPTY, "bitrate");
-const buzz::QName QN_CHANNELS(true, NS_EMPTY, "channels");
-const buzz::QName QN_WIDTH(true, NS_EMPTY, "width");
-const buzz::QName QN_HEIGHT(true, NS_EMPTY, "height");
-const buzz::QName QN_FRAMERATE(true, NS_EMPTY, "framerate");
-const std::string LN_NAME("name");
-const std::string LN_VALUE("value");
-const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME(true, NS_EMPTY, LN_NAME);
-const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE(true, NS_EMPTY, LN_VALUE);
-const std::string PAYLOADTYPE_PARAMETER_BITRATE("bitrate");
-const std::string PAYLOADTYPE_PARAMETER_HEIGHT("height");
-const std::string PAYLOADTYPE_PARAMETER_WIDTH("width");
-const std::string PAYLOADTYPE_PARAMETER_FRAMERATE("framerate");
-const std::string LN_BANDWIDTH("bandwidth");
+const char LN_DESCRIPTION[] = "description";
+const char LN_PAYLOADTYPE[] = "payload-type";
+const buzz::StaticQName QN_ID = { NS_EMPTY, "id" };
+const buzz::StaticQName QN_SID = { NS_EMPTY, "sid" };
+const buzz::StaticQName QN_NAME = { NS_EMPTY, "name" };
+const buzz::StaticQName QN_CLOCKRATE = { NS_EMPTY, "clockrate" };
+const buzz::StaticQName QN_BITRATE = { NS_EMPTY, "bitrate" };
+const buzz::StaticQName QN_CHANNELS = { NS_EMPTY, "channels" };
+const buzz::StaticQName QN_WIDTH = { NS_EMPTY, "width" };
+const buzz::StaticQName QN_HEIGHT = { NS_EMPTY, "height" };
+const buzz::StaticQName QN_FRAMERATE = { NS_EMPTY, "framerate" };
+const char LN_NAME[] = "name";
+const char LN_VALUE[] = "value";
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME = { NS_EMPTY, LN_NAME };
+const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE = { NS_EMPTY, LN_VALUE };
+const char PAYLOADTYPE_PARAMETER_BITRATE[] = "bitrate";
+const char PAYLOADTYPE_PARAMETER_HEIGHT[] = "height";
+const char PAYLOADTYPE_PARAMETER_WIDTH[] = "width";
+const char PAYLOADTYPE_PARAMETER_FRAMERATE[] = "framerate";
+const char LN_BANDWIDTH[] = "bandwidth";
-const std::string CN_AUDIO("audio");
-const std::string CN_VIDEO("video");
-const std::string CN_OTHER("main");
+const char CN_AUDIO[] = "audio";
+const char CN_VIDEO[] = "video";
+const char CN_OTHER[] = "main";
+// other SDP related strings
+const char GN_BUNDLE[] = "BUNDLE";
-const std::string NS_JINGLE_RTP("urn:xmpp:jingle:apps:rtp:1");
-const buzz::QName QN_JINGLE_RTP_CONTENT(
- true, NS_JINGLE_RTP, LN_DESCRIPTION);
-const buzz::QName QN_JINGLE_SSRC(true, NS_EMPTY, "ssrc");
-const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE(
- true, NS_JINGLE_RTP, LN_PAYLOADTYPE);
-const buzz::QName QN_JINGLE_RTP_BANDWIDTH(
- true, NS_JINGLE_RTP, LN_BANDWIDTH);
-const buzz::QName QN_JINGLE_RTCP_MUX(true, NS_JINGLE_RTP, "rtcp-mux");
-const buzz::QName QN_PARAMETER(true, NS_JINGLE_RTP, "parameter");
+const char NS_JINGLE_RTP[] = "urn:xmpp:jingle:apps:rtp:1";
+const buzz::StaticQName QN_JINGLE_RTP_CONTENT =
+ { NS_JINGLE_RTP, LN_DESCRIPTION };
+const buzz::StaticQName QN_SSRC = { NS_EMPTY, "ssrc" };
+const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE =
+ { NS_JINGLE_RTP, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH =
+ { NS_JINGLE_RTP, LN_BANDWIDTH };
+const buzz::StaticQName QN_JINGLE_RTCP_MUX = { NS_JINGLE_RTP, "rtcp-mux" };
+const buzz::StaticQName QN_PARAMETER = { NS_JINGLE_RTP, "parameter" };
-const std::string NS_GINGLE_AUDIO("http://www.google.com/session/phone");
-const buzz::QName QN_GINGLE_AUDIO_CONTENT(
- true, NS_GINGLE_AUDIO, LN_DESCRIPTION);
-const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE(
- true, NS_GINGLE_AUDIO, LN_PAYLOADTYPE);
-const buzz::QName QN_GINGLE_AUDIO_SRCID(true, NS_GINGLE_AUDIO, "src-id");
-const std::string NS_GINGLE_VIDEO("http://www.google.com/session/video");
-const buzz::QName QN_GINGLE_VIDEO_CONTENT(
- true, NS_GINGLE_VIDEO, LN_DESCRIPTION);
-const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE(
- true, NS_GINGLE_VIDEO, LN_PAYLOADTYPE);
-const buzz::QName QN_GINGLE_VIDEO_SRCID(true, NS_GINGLE_VIDEO, "src-id");
-const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH(
- true, NS_GINGLE_VIDEO, LN_BANDWIDTH);
+const char NS_GINGLE_AUDIO[] = "http://www.google.com/session/phone";
+const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT =
+ { NS_GINGLE_AUDIO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE =
+ { NS_GINGLE_AUDIO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_AUDIO_SRCID = { NS_GINGLE_AUDIO, "src-id" };
+const char NS_GINGLE_VIDEO[] = "http://www.google.com/session/video";
+const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT =
+ { NS_GINGLE_VIDEO, LN_DESCRIPTION };
+const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE =
+ { NS_GINGLE_VIDEO, LN_PAYLOADTYPE };
+const buzz::StaticQName QN_GINGLE_VIDEO_SRCID = { NS_GINGLE_VIDEO, "src-id" };
+const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH =
+ { NS_GINGLE_VIDEO, LN_BANDWIDTH };
// Crypto support.
-const buzz::QName QN_ENCRYPTION(true, NS_JINGLE_RTP, "encryption");
-const buzz::QName QN_ENCRYPTION_REQUIRED(true, NS_EMPTY, "required");
-const buzz::QName QN_CRYPTO(true, NS_JINGLE_RTP, "crypto");
-const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE(true, NS_GINGLE_AUDIO, "usage");
-const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE(true, NS_GINGLE_VIDEO, "usage");
-const buzz::QName QN_CRYPTO_SUITE(true, NS_EMPTY, "crypto-suite");
-const buzz::QName QN_CRYPTO_KEY_PARAMS(true, NS_EMPTY, "key-params");
-const buzz::QName QN_CRYPTO_TAG(true, NS_EMPTY, "tag");
-const buzz::QName QN_CRYPTO_SESSION_PARAMS(true, NS_EMPTY, "session-params");
+const buzz::StaticQName QN_ENCRYPTION = { NS_JINGLE_RTP, "encryption" };
+const buzz::StaticQName QN_ENCRYPTION_REQUIRED = { NS_EMPTY, "required" };
+const buzz::StaticQName QN_CRYPTO = { NS_JINGLE_RTP, "crypto" };
+const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE =
+ { NS_GINGLE_AUDIO, "usage" };
+const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE =
+ { NS_GINGLE_VIDEO, "usage" };
+const buzz::StaticQName QN_CRYPTO_SUITE = { NS_EMPTY, "crypto-suite" };
+const buzz::StaticQName QN_CRYPTO_KEY_PARAMS = { NS_EMPTY, "key-params" };
+const buzz::StaticQName QN_CRYPTO_TAG = { NS_EMPTY, "tag" };
+const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS =
+ { NS_EMPTY, "session-params" };
// transports and candidates
-const std::string LN_TRANSPORT("transport");
-const std::string LN_CANDIDATE("candidate");
-const buzz::QName QN_UFRAG(true, cricket::NS_EMPTY, "ufrag");
-const buzz::QName QN_PWD(true, cricket::NS_EMPTY, "pwd");
-const buzz::QName QN_COMPONENT(true, cricket::NS_EMPTY, "component");
-const buzz::QName QN_IP(true, cricket::NS_EMPTY, "ip");
-const buzz::QName QN_PORT(true, cricket::NS_EMPTY, "port");
-const buzz::QName QN_NETWORK(true, cricket::NS_EMPTY, "network");
-const buzz::QName QN_GENERATION(true, cricket::NS_EMPTY, "generation");
-const buzz::QName QN_PRIORITY(true, cricket::NS_EMPTY, "priority");
-const buzz::QName QN_PROTOCOL(true, cricket::NS_EMPTY, "protocol");
-const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN("prflx");
-const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN("srflx");
-const std::string JINGLE_CANDIDATE_NAME_RTP("1");
-const std::string JINGLE_CANDIDATE_NAME_RTCP("2");
+const char LN_TRANSPORT[] = "transport";
+const char LN_CANDIDATE[] = "candidate";
+const buzz::StaticQName QN_UFRAG = { cricket::NS_EMPTY, "ufrag" };
+const buzz::StaticQName QN_PWD = { cricket::NS_EMPTY, "pwd" };
+const buzz::StaticQName QN_COMPONENT = { cricket::NS_EMPTY, "component" };
+const buzz::StaticQName QN_IP = { cricket::NS_EMPTY, "ip" };
+const buzz::StaticQName QN_PORT = { cricket::NS_EMPTY, "port" };
+const buzz::StaticQName QN_NETWORK = { cricket::NS_EMPTY, "network" };
+const buzz::StaticQName QN_GENERATION = { cricket::NS_EMPTY, "generation" };
+const buzz::StaticQName QN_PRIORITY = { cricket::NS_EMPTY, "priority" };
+const buzz::StaticQName QN_PROTOCOL = { cricket::NS_EMPTY, "protocol" };
+const char JINGLE_CANDIDATE_TYPE_PEER_STUN[] = "prflx";
+const char JINGLE_CANDIDATE_TYPE_SERVER_STUN[] = "srflx";
+const char JINGLE_CANDIDATE_NAME_RTP[] = "1";
+const char JINGLE_CANDIDATE_NAME_RTCP[] = "2";
// TODO Once we are full ICE-UDP compliant, use this namespace.
// For now, just use the same as NS_GINGLE_P2P.
-// const std::string NS_JINGLE_ICE_UDP("urn:xmpp:jingle:transports:ice-udp:1");
-const std::string NS_GINGLE_P2P("http://www.google.com/transport/p2p");
-const buzz::QName QN_GINGLE_P2P_TRANSPORT(true, NS_GINGLE_P2P, LN_TRANSPORT);
-const buzz::QName QN_GINGLE_P2P_CANDIDATE(true, NS_GINGLE_P2P, LN_CANDIDATE);
-const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME(
- true, NS_GINGLE_P2P, "unknown-channel-name");
-const buzz::QName QN_GINGLE_CANDIDATE(true, NS_GINGLE, LN_CANDIDATE);
-const buzz::QName QN_ADDRESS(true, cricket::NS_EMPTY, "address");
-const buzz::QName QN_USERNAME(true, cricket::NS_EMPTY, "username");
-const buzz::QName QN_PASSWORD(true, cricket::NS_EMPTY, "password");
-const buzz::QName QN_PREFERENCE(true, cricket::NS_EMPTY, "preference");
-const std::string GINGLE_CANDIDATE_TYPE_STUN("stun");
-const std::string GINGLE_CANDIDATE_NAME_RTP("rtp");
-const std::string GINGLE_CANDIDATE_NAME_RTCP("rtcp");
-const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP("video_rtp");
-const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP("video_rtcp");
+// const char NS_JINGLE_ICE_UDP[] = "urn:xmpp:jingle:transports:ice-udp:1";
+const char NS_GINGLE_P2P[] = "http://www.google.com/transport/p2p";
+const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT =
+ { NS_GINGLE_P2P, LN_TRANSPORT };
+const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE =
+ { NS_GINGLE_P2P, LN_CANDIDATE };
+const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME =
+ { NS_GINGLE_P2P, "unknown-channel-name" };
+const buzz::StaticQName QN_GINGLE_CANDIDATE = { NS_GINGLE, LN_CANDIDATE };
+const buzz::StaticQName QN_ADDRESS = { cricket::NS_EMPTY, "address" };
+const buzz::StaticQName QN_USERNAME = { cricket::NS_EMPTY, "username" };
+const buzz::StaticQName QN_PASSWORD = { cricket::NS_EMPTY, "password" };
+const buzz::StaticQName QN_PREFERENCE = { cricket::NS_EMPTY, "preference" };
+const char GINGLE_CANDIDATE_TYPE_STUN[] = "stun";
+const char GINGLE_CANDIDATE_NAME_RTP[] = "rtp";
+const char GINGLE_CANDIDATE_NAME_RTCP[] = "rtcp";
+const char GINGLE_CANDIDATE_NAME_VIDEO_RTP[] = "video_rtp";
+const char GINGLE_CANDIDATE_NAME_VIDEO_RTCP[] = "video_rtcp";
// terminate reasons and errors
-const std::string JINGLE_ERROR_BAD_REQUEST("bad-request");
-const std::string JINGLE_ERROR_OUT_OF_ORDER("out-of-order");
-const std::string JINGLE_ERROR_UNKNOWN_SESSION("unknown-session");
+const char JINGLE_ERROR_BAD_REQUEST[] = "bad-request";
+const char JINGLE_ERROR_OUT_OF_ORDER[] = "out-of-order";
+const char JINGLE_ERROR_UNKNOWN_SESSION[] = "unknown-session";
// Call terminate reasons from XEP-166
-const std::string STR_TERMINATE_DECLINE("decline");
-const std::string STR_TERMINATE_SUCCESS("success");
-const std::string STR_TERMINATE_ERROR("general-error");
-const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS(
- "incompatible-parameters");
+const char STR_TERMINATE_DECLINE[] = "decline";
+const char STR_TERMINATE_SUCCESS[] = "success";
+const char STR_TERMINATE_ERROR[] = "general-error";
+const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[] = "incompatible-parameters";
// Old terminate reasons used by cricket
-const std::string STR_TERMINATE_CALL_ENDED("call-ended");
-const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE("recipient-unavailable");
-const std::string STR_TERMINATE_RECIPIENT_BUSY("recipient-busy");
-const std::string STR_TERMINATE_INSUFFICIENT_FUNDS("insufficient-funds");
-const std::string STR_TERMINATE_NUMBER_MALFORMED("number-malformed");
-const std::string STR_TERMINATE_NUMBER_DISALLOWED("number-disallowed");
-const std::string STR_TERMINATE_PROTOCOL_ERROR("protocol-error");
-const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR("internal-server-error");
-const std::string STR_TERMINATE_UNKNOWN_ERROR("unknown-error");
+const char STR_TERMINATE_CALL_ENDED[] = "call-ended";
+const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[] = "recipient-unavailable";
+const char STR_TERMINATE_RECIPIENT_BUSY[] = "recipient-busy";
+const char STR_TERMINATE_INSUFFICIENT_FUNDS[] = "insufficient-funds";
+const char STR_TERMINATE_NUMBER_MALFORMED[] = "number-malformed";
+const char STR_TERMINATE_NUMBER_DISALLOWED[] = "number-disallowed";
+const char STR_TERMINATE_PROTOCOL_ERROR[] = "protocol-error";
+const char STR_TERMINATE_INTERNAL_SERVER_ERROR[] = "internal-server-error";
+const char STR_TERMINATE_UNKNOWN_ERROR[] = "unknown-error";
// Draft view and notify messages.
-const buzz::QName QN_JINGLE_DRAFT_CONTENT_NAME(true, cricket::NS_EMPTY, "name");
-const std::string STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO("video");
-const std::string STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO("audio");
-const buzz::QName QN_JINGLE_DRAFT_NOTIFY(true, NS_JINGLE_DRAFT, "notify");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE(
- true, NS_JINGLE_DRAFT, "source");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_NICK(true, cricket::NS_EMPTY, "nick");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_NAME(true, cricket::NS_EMPTY, "name");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_USAGE(true, cricket::NS_EMPTY, "usage");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_STATE(true, cricket::NS_EMPTY, "state");
-const std::string STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED("removed");
-const buzz::QName QN_JINGLE_DRAFT_SOURCE_SSRC(true, NS_JINGLE_DRAFT, "ssrc");
-const buzz::QName QN_JINGLE_DRAFT_VIEW(true, NS_JINGLE_DRAFT, "view");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_TYPE(true, cricket::NS_EMPTY, "type");
-const std::string STR_JINGLE_DRAFT_VIEW_TYPE_NONE("none");
-const std::string STR_JINGLE_DRAFT_VIEW_TYPE_STATIC("static");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_SSRC(true, cricket::NS_EMPTY, "ssrc");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS(true, NS_JINGLE_DRAFT, "params");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH(
- true, cricket::NS_EMPTY, "width");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT(
- true, cricket::NS_EMPTY, "height");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE(
- true, cricket::NS_EMPTY, "framerate");
-const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE(
- true, cricket::NS_EMPTY, "preference");
+const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[] = "video";
+const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[] = "audio";
+const buzz::StaticQName QN_NICK = { cricket::NS_EMPTY, "nick" };
+const buzz::StaticQName QN_TYPE = { cricket::NS_EMPTY, "type" };
+const buzz::StaticQName QN_JINGLE_DRAFT_VIEW = { NS_JINGLE_DRAFT, "view" };
+const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[] = "none";
+const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[] = "static";
+const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS = { NS_JINGLE_DRAFT, "params" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS = { NS_JINGLE_DRAFT, "streams" };
+const buzz::StaticQName QN_JINGLE_DRAFT_STREAM = { NS_JINGLE_DRAFT, "stream" };
+const buzz::StaticQName QN_DISPLAY = { cricket::NS_EMPTY, "display" };
+const buzz::StaticQName QN_CNAME = { cricket::NS_EMPTY, "cname" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC = { NS_JINGLE_DRAFT, "ssrc" };
+const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP =
+ { NS_JINGLE_DRAFT, "ssrc-group" };
+const buzz::StaticQName QN_SEMANTICS = { cricket::NS_EMPTY, "semantics" };
+const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY = { NS_JINGLE_DRAFT, "notify" };
+const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE = { NS_JINGLE_DRAFT, "source" };
// old stuff
#ifdef FEATURE_ENABLE_VOICEMAIL
-const std::string NS_VOICEMAIL("http://www.google.com/session/voicemail");
-const buzz::QName QN_VOICEMAIL_REGARDING(true, NS_VOICEMAIL, "regarding");
+const char NS_VOICEMAIL[] = "http://www.google.com/session/voicemail";
+const buzz::StaticQName QN_VOICEMAIL_REGARDING = { NS_VOICEMAIL, "regarding" };
#endif
} // namespace cricket
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
index ab0ed2c..3e85914 100644
--- a/talk/p2p/base/constants.h
+++ b/talk/p2p/base/constants.h
@@ -42,10 +42,10 @@
// these are useful when you need to find a tag
// that has different namespaces (like <description> or <transport>)
-extern const std::string NS_EMPTY;
-extern const std::string NS_JINGLE;
-extern const std::string NS_JINGLE_DRAFT;
-extern const std::string NS_GINGLE;
+extern const char NS_EMPTY[];
+extern const char NS_JINGLE[];
+extern const char NS_JINGLE_DRAFT[];
+extern const char NS_GINGLE[];
enum SignalingProtocol {
PROTOCOL_JINGLE,
@@ -54,189 +54,187 @@
};
// actions (aka Gingle <session> or Jingle <jingle>)
-extern const buzz::QName QN_ACTION;
-extern const std::string LN_INITIATOR;
-extern const buzz::QName QN_INITIATOR;
-extern const buzz::QName QN_CREATOR;
+extern const buzz::StaticQName QN_ACTION;
+extern const char LN_INITIATOR[];
+extern const buzz::StaticQName QN_INITIATOR;
+extern const buzz::StaticQName QN_CREATOR;
-extern const buzz::QName QN_JINGLE;
-extern const buzz::QName QN_JINGLE_CONTENT;
-extern const buzz::QName QN_JINGLE_CONTENT_NAME;
-extern const buzz::QName QN_JINGLE_CONTENT_MEDIA;
-extern const buzz::QName QN_JINGLE_REASON;
-extern const std::string JINGLE_CONTENT_MEDIA_AUDIO;
-extern const std::string JINGLE_CONTENT_MEDIA_VIDEO;
-extern const std::string JINGLE_ACTION_SESSION_INITIATE;
-extern const std::string JINGLE_ACTION_SESSION_INFO;
-extern const std::string JINGLE_ACTION_SESSION_ACCEPT;
-extern const std::string JINGLE_ACTION_SESSION_TERMINATE;
-extern const std::string JINGLE_ACTION_TRANSPORT_INFO;
-extern const std::string JINGLE_ACTION_TRANSPORT_ACCEPT;
-extern const std::string JINGLE_ACTION_DESCRIPTION_INFO;
+extern const buzz::StaticQName QN_JINGLE;
+extern const buzz::StaticQName QN_JINGLE_CONTENT;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_NAME;
+extern const buzz::StaticQName QN_JINGLE_CONTENT_MEDIA;
+extern const buzz::StaticQName QN_JINGLE_REASON;
+extern const char JINGLE_CONTENT_MEDIA_AUDIO[];
+extern const char JINGLE_CONTENT_MEDIA_VIDEO[];
+extern const char JINGLE_ACTION_SESSION_INITIATE[];
+extern const char JINGLE_ACTION_SESSION_INFO[];
+extern const char JINGLE_ACTION_SESSION_ACCEPT[];
+extern const char JINGLE_ACTION_SESSION_TERMINATE[];
+extern const char JINGLE_ACTION_TRANSPORT_INFO[];
+extern const char JINGLE_ACTION_TRANSPORT_ACCEPT[];
+extern const char JINGLE_ACTION_DESCRIPTION_INFO[];
-extern const buzz::QName QN_GINGLE_SESSION;
-extern const std::string GINGLE_ACTION_INITIATE;
-extern const std::string GINGLE_ACTION_INFO;
-extern const std::string GINGLE_ACTION_ACCEPT;
-extern const std::string GINGLE_ACTION_REJECT;
-extern const std::string GINGLE_ACTION_TERMINATE;
-extern const std::string GINGLE_ACTION_CANDIDATES;
-extern const std::string GINGLE_ACTION_UPDATE;
+extern const buzz::StaticQName QN_GINGLE_SESSION;
+extern const char GINGLE_ACTION_INITIATE[];
+extern const char GINGLE_ACTION_INFO[];
+extern const char GINGLE_ACTION_ACCEPT[];
+extern const char GINGLE_ACTION_REJECT[];
+extern const char GINGLE_ACTION_TERMINATE[];
+extern const char GINGLE_ACTION_CANDIDATES[];
+extern const char GINGLE_ACTION_UPDATE[];
-extern const std::string LN_ERROR;
-extern const buzz::QName QN_GINGLE_REDIRECT;
-extern const std::string STR_REDIRECT_PREFIX;
+extern const char LN_ERROR[];
+extern const buzz::StaticQName QN_GINGLE_REDIRECT;
+extern const char STR_REDIRECT_PREFIX[];
// Session Contents (aka Gingle <session><description>
// or Jingle <content><description>)
-extern const std::string LN_DESCRIPTION;
-extern const std::string LN_PAYLOADTYPE;
-extern const buzz::QName QN_ID;
-extern const buzz::QName QN_SID;
-extern const buzz::QName QN_NAME;
-extern const buzz::QName QN_CLOCKRATE;
-extern const buzz::QName QN_BITRATE;
-extern const buzz::QName QN_CHANNELS;
-extern const buzz::QName QN_WIDTH;
-extern const buzz::QName QN_HEIGHT;
-extern const buzz::QName QN_FRAMERATE;
-extern const buzz::QName QN_PARAMETER;
-extern const std::string LN_NAME;
-extern const std::string LN_VALUE;
-extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME;
-extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE;
-extern const std::string PAYLOADTYPE_PARAMETER_BITRATE;
-extern const std::string PAYLOADTYPE_PARAMETER_HEIGHT;
-extern const std::string PAYLOADTYPE_PARAMETER_WIDTH;
-extern const std::string PAYLOADTYPE_PARAMETER_FRAMERATE;
-extern const std::string LN_BANDWIDTH;
+extern const char LN_DESCRIPTION[];
+extern const char LN_PAYLOADTYPE[];
+extern const buzz::StaticQName QN_ID;
+extern const buzz::StaticQName QN_SID;
+extern const buzz::StaticQName QN_NAME;
+extern const buzz::StaticQName QN_CLOCKRATE;
+extern const buzz::StaticQName QN_BITRATE;
+extern const buzz::StaticQName QN_CHANNELS;
+extern const buzz::StaticQName QN_PARAMETER;
+extern const char LN_NAME[];
+extern const char LN_VALUE[];
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_NAME;
+extern const buzz::StaticQName QN_PAYLOADTYPE_PARAMETER_VALUE;
+extern const char PAYLOADTYPE_PARAMETER_BITRATE[];
+extern const char PAYLOADTYPE_PARAMETER_HEIGHT[];
+extern const char PAYLOADTYPE_PARAMETER_WIDTH[];
+extern const char PAYLOADTYPE_PARAMETER_FRAMERATE[];
+extern const char LN_BANDWIDTH[];
// CN_ == "content name". When we initiate a session, we choose the
// name, and when we receive a Gingle session, we provide default
// names (since Gingle has no content names). But when we receive a
// Jingle call, the content name can be anything, so don't rely on
// these values being the same as the ones received.
-extern const std::string CN_AUDIO;
-extern const std::string CN_VIDEO;
-extern const std::string CN_OTHER;
+extern const char CN_AUDIO[];
+extern const char CN_VIDEO[];
+extern const char CN_OTHER[];
+// other SDP related strings
+// GN stands for group name
+extern const char GN_BUNDLE[];
-extern const std::string NS_JINGLE_RTP;
-extern const buzz::QName QN_JINGLE_RTP_CONTENT;
-extern const buzz::QName QN_JINGLE_SSRC;
-extern const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE;
-extern const buzz::QName QN_JINGLE_RTP_BANDWIDTH;
-extern const buzz::QName QN_JINGLE_RTCP_MUX;
+extern const char NS_JINGLE_RTP[];
+extern const buzz::StaticQName QN_JINGLE_RTP_CONTENT;
+extern const buzz::StaticQName QN_SSRC;
+extern const buzz::StaticQName QN_JINGLE_RTP_PAYLOADTYPE;
+extern const buzz::StaticQName QN_JINGLE_RTP_BANDWIDTH;
+extern const buzz::StaticQName QN_JINGLE_RTCP_MUX;
-extern const std::string NS_GINGLE_AUDIO;
-extern const buzz::QName QN_GINGLE_AUDIO_CONTENT;
-extern const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE;
-extern const buzz::QName QN_GINGLE_AUDIO_SRCID;
-extern const std::string NS_GINGLE_VIDEO;
-extern const buzz::QName QN_GINGLE_VIDEO_CONTENT;
-extern const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE;
-extern const buzz::QName QN_GINGLE_VIDEO_SRCID;
-extern const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH;
+extern const char NS_GINGLE_AUDIO[];
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_SRCID;
+extern const char NS_GINGLE_VIDEO[];
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CONTENT;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_PAYLOADTYPE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_SRCID;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_BANDWIDTH;
// Crypto support.
-extern const buzz::QName QN_ENCRYPTION;
-extern const buzz::QName QN_ENCRYPTION_REQUIRED;
-extern const buzz::QName QN_CRYPTO;
-extern const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE;
-extern const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE;
-extern const buzz::QName QN_CRYPTO_SUITE;
-extern const buzz::QName QN_CRYPTO_KEY_PARAMS;
-extern const buzz::QName QN_CRYPTO_TAG;
-extern const buzz::QName QN_CRYPTO_SESSION_PARAMS;
+extern const buzz::StaticQName QN_ENCRYPTION;
+extern const buzz::StaticQName QN_ENCRYPTION_REQUIRED;
+extern const buzz::StaticQName QN_CRYPTO;
+extern const buzz::StaticQName QN_GINGLE_AUDIO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_GINGLE_VIDEO_CRYPTO_USAGE;
+extern const buzz::StaticQName QN_CRYPTO_SUITE;
+extern const buzz::StaticQName QN_CRYPTO_KEY_PARAMS;
+extern const buzz::StaticQName QN_CRYPTO_TAG;
+extern const buzz::StaticQName QN_CRYPTO_SESSION_PARAMS;
// transports and candidates
-extern const std::string LN_TRANSPORT;
-extern const std::string LN_CANDIDATE;
-extern const buzz::QName QN_JINGLE_P2P_TRANSPORT;
-extern const buzz::QName QN_JINGLE_P2P_CANDIDATE;
-extern const buzz::QName QN_UFRAG;
-extern const buzz::QName QN_COMPONENT;
-extern const buzz::QName QN_PWD;
-extern const buzz::QName QN_IP;
-extern const buzz::QName QN_PORT;
-extern const buzz::QName QN_NETWORK;
-extern const buzz::QName QN_GENERATION;
-extern const buzz::QName QN_PRIORITY;
-extern const buzz::QName QN_PROTOCOL;
-extern const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN;
-extern const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN;
-extern const std::string JINGLE_CANDIDATE_NAME_RTP;
-extern const std::string JINGLE_CANDIDATE_NAME_RTCP;
+extern const char LN_TRANSPORT[];
+extern const char LN_CANDIDATE[];
+extern const buzz::StaticQName QN_JINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_JINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_UFRAG;
+extern const buzz::StaticQName QN_COMPONENT;
+extern const buzz::StaticQName QN_PWD;
+extern const buzz::StaticQName QN_IP;
+extern const buzz::StaticQName QN_PORT;
+extern const buzz::StaticQName QN_NETWORK;
+extern const buzz::StaticQName QN_GENERATION;
+extern const buzz::StaticQName QN_PRIORITY;
+extern const buzz::StaticQName QN_PROTOCOL;
+extern const char JINGLE_CANDIDATE_TYPE_PEER_STUN[];
+extern const char JINGLE_CANDIDATE_TYPE_SERVER_STUN[];
+extern const char JINGLE_CANDIDATE_NAME_RTP[];
+extern const char JINGLE_CANDIDATE_NAME_RTCP[];
-extern const std::string NS_GINGLE_P2P;
-extern const buzz::QName QN_GINGLE_P2P_TRANSPORT;
-extern const buzz::QName QN_GINGLE_P2P_CANDIDATE;
-extern const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
-extern const buzz::QName QN_GINGLE_CANDIDATE;
-extern const buzz::QName QN_ADDRESS;
-extern const buzz::QName QN_USERNAME;
-extern const buzz::QName QN_PASSWORD;
-extern const buzz::QName QN_PREFERENCE;
-extern const std::string GINGLE_CANDIDATE_TYPE_STUN;
-extern const std::string GINGLE_CANDIDATE_NAME_RTP;
-extern const std::string GINGLE_CANDIDATE_NAME_RTCP;
-extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP;
-extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP;
+extern const char NS_GINGLE_P2P[];
+extern const buzz::StaticQName QN_GINGLE_P2P_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_P2P_CANDIDATE;
+extern const buzz::StaticQName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
+extern const buzz::StaticQName QN_GINGLE_CANDIDATE;
+extern const buzz::StaticQName QN_ADDRESS;
+extern const buzz::StaticQName QN_USERNAME;
+extern const buzz::StaticQName QN_PASSWORD;
+extern const buzz::StaticQName QN_PREFERENCE;
+extern const char GINGLE_CANDIDATE_TYPE_STUN[];
+extern const char GINGLE_CANDIDATE_NAME_RTP[];
+extern const char GINGLE_CANDIDATE_NAME_RTCP[];
+extern const char GINGLE_CANDIDATE_NAME_VIDEO_RTP[];
+extern const char GINGLE_CANDIDATE_NAME_VIDEO_RTCP[];
-extern const std::string NS_GINGLE_RAW;
-extern const buzz::QName QN_GINGLE_RAW_TRANSPORT;
-extern const buzz::QName QN_GINGLE_RAW_CHANNEL;
+extern const char NS_GINGLE_RAW[];
+extern const buzz::StaticQName QN_GINGLE_RAW_TRANSPORT;
+extern const buzz::StaticQName QN_GINGLE_RAW_CHANNEL;
// terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html
-extern const std::string JINGLE_ERROR_BAD_REQUEST; // like parse error
+extern const char JINGLE_ERROR_BAD_REQUEST[]; // like parse error
// got transport-info before session-initiate, for example
-extern const std::string JINGLE_ERROR_OUT_OF_ORDER;
-extern const std::string JINGLE_ERROR_UNKNOWN_SESSION;
+extern const char JINGLE_ERROR_OUT_OF_ORDER[];
+extern const char JINGLE_ERROR_UNKNOWN_SESSION[];
// Call terminate reasons from XEP-166
-extern const std::string STR_TERMINATE_DECLINE; // polite reject
-extern const std::string STR_TERMINATE_SUCCESS; // polite hangup
-extern const std::string STR_TERMINATE_ERROR; // something bad happened
-extern const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS; // no codecs?
+extern const char STR_TERMINATE_DECLINE[]; // polite reject
+extern const char STR_TERMINATE_SUCCESS[]; // polite hangup
+extern const char STR_TERMINATE_ERROR[]; // something bad happened
+extern const char STR_TERMINATE_INCOMPATIBLE_PARAMETERS[]; // no codecs?
// Old terminate reasons used by cricket
-extern const std::string STR_TERMINATE_CALL_ENDED;
-extern const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE;
-extern const std::string STR_TERMINATE_RECIPIENT_BUSY;
-extern const std::string STR_TERMINATE_INSUFFICIENT_FUNDS;
-extern const std::string STR_TERMINATE_NUMBER_MALFORMED;
-extern const std::string STR_TERMINATE_NUMBER_DISALLOWED;
-extern const std::string STR_TERMINATE_PROTOCOL_ERROR;
-extern const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR;
-extern const std::string STR_TERMINATE_UNKNOWN_ERROR;
+extern const char STR_TERMINATE_CALL_ENDED[];
+extern const char STR_TERMINATE_RECIPIENT_UNAVAILABLE[];
+extern const char STR_TERMINATE_RECIPIENT_BUSY[];
+extern const char STR_TERMINATE_INSUFFICIENT_FUNDS[];
+extern const char STR_TERMINATE_NUMBER_MALFORMED[];
+extern const char STR_TERMINATE_NUMBER_DISALLOWED[];
+extern const char STR_TERMINATE_PROTOCOL_ERROR[];
+extern const char STR_TERMINATE_INTERNAL_SERVER_ERROR[];
+extern const char STR_TERMINATE_UNKNOWN_ERROR[];
// Draft view and notify messages.
-extern const buzz::QName QN_JINGLE_DRAFT_CONTENT_NAME;
-extern const std::string STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO;
-extern const std::string STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO;
-extern const buzz::QName QN_JINGLE_DRAFT_NOTIFY;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_NICK;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_NAME;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_USAGE;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_STATE;
-extern const std::string STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED;
-extern const buzz::QName QN_JINGLE_DRAFT_SOURCE_SSRC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_NAME;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_TYPE;
-extern const std::string STR_JINGLE_DRAFT_VIEW_TYPE_NONE;
-extern const std::string STR_JINGLE_DRAFT_VIEW_TYPE_STATIC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_SSRC;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE;
-extern const buzz::QName QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE;
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_VIDEO[];
+extern const char STR_JINGLE_DRAFT_CONTENT_NAME_AUDIO[];
+extern const buzz::StaticQName QN_NICK;
+extern const buzz::StaticQName QN_TYPE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_VIEW;
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_NONE[];
+extern const char STR_JINGLE_DRAFT_VIEW_TYPE_STATIC[];
+extern const buzz::StaticQName QN_JINGLE_DRAFT_PARAMS;
+extern const buzz::StaticQName QN_WIDTH;
+extern const buzz::StaticQName QN_HEIGHT;
+extern const buzz::StaticQName QN_FRAMERATE;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAM;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_STREAMS;
+extern const buzz::StaticQName QN_DISPLAY;
+extern const buzz::StaticQName QN_CNAME;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC;
+extern const buzz::StaticQName QN_JINGLE_DRAFT_SSRC_GROUP;
+extern const buzz::StaticQName QN_SEMANTICS;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_NOTIFY;
+extern const buzz::StaticQName QN_JINGLE_LEGACY_SOURCE;
// old stuff
#ifdef FEATURE_ENABLE_VOICEMAIL
-extern const std::string NS_VOICEMAIL;
-extern const buzz::QName QN_VOICEMAIL_REGARDING;
+extern const char NS_VOICEMAIL[];
+extern const buzz::StaticQName QN_VOICEMAIL_REGARDING;
#endif
} // namespace cricket
diff --git a/talk/p2p/base/fakesession.h b/talk/p2p/base/fakesession.h
new file mode 100644
index 0000000..1928613
--- /dev/null
+++ b/talk/p2p/base/fakesession.h
@@ -0,0 +1,233 @@
+// libjingle
+// Copyright 2009 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_FAKESESSION_H_
+#define TALK_SESSION_PHONE_FAKESESSION_H_
+
+#include <map>
+#include <string>
+
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+class FakeTransport;
+
+// Fake transport channel class, which can be passed to anything that needs a
+// transport channel. Can be informed of another FakeTransportChannel via
+// SetDestination.
+class FakeTransportChannel : public TransportChannelImpl {
+ public:
+ explicit FakeTransportChannel(Transport* transport,
+ const std::string& name,
+ const std::string& session_type)
+ : TransportChannelImpl(name, session_type),
+ transport_(transport),
+ dest_(NULL),
+ state_(STATE_INIT) {
+ }
+ ~FakeTransportChannel() {
+ Reset();
+ }
+
+ virtual Transport* GetTransport() {
+ return transport_;
+ }
+ virtual void Connect() {
+ if (state_ == STATE_INIT) {
+ state_ = STATE_CONNECTING;
+ }
+ }
+ virtual void Reset() {
+ if (state_ != STATE_INIT) {
+ state_ = STATE_INIT;
+ if (dest_) {
+ dest_->state_ = STATE_INIT;
+ dest_->dest_ = NULL;
+ dest_ = NULL;
+ }
+ }
+ }
+
+ void SetDestination(FakeTransportChannel* dest) {
+ if (state_ == STATE_CONNECTING && dest) {
+ // This simulates the delivery of candidates.
+ dest_ = dest;
+ dest_->dest_ = this;
+ state_ = STATE_CONNECTED;
+ dest_->state_ = STATE_CONNECTED;
+ set_writable(true);
+ dest_->set_writable(true);
+ } else if (state_ == STATE_CONNECTED && !dest) {
+ // Simulates loss of connectivity, by asymmetrically forgetting dest_.
+ dest_ = NULL;
+ state_ = STATE_CONNECTING;
+ set_writable(false);
+ }
+ }
+
+ virtual int SendPacket(const char *data, size_t len) {
+ if (state_ != STATE_CONNECTED) {
+ return -1;
+ }
+ dest_->SignalReadPacket(dest_, data, len);
+ return len;
+ }
+ virtual int SetOption(talk_base::Socket::Option opt, int value) {
+ return true;
+ }
+ virtual int GetError() {
+ return 0;
+ }
+
+ virtual void OnSignalingReady() {
+ }
+ virtual void OnCandidate(const Candidate& candidate) {
+ }
+
+ private:
+ enum State { STATE_INIT, STATE_CONNECTING, STATE_CONNECTED };
+ Transport* transport_;
+ FakeTransportChannel* dest_;
+ State state_;
+};
+
+// Fake transport class, which can be passed to anything that needs a Transport.
+// Can be informed of another FakeTransport via SetDestination (low-tech way
+// of doing candidates)
+class FakeTransport : public Transport {
+ public:
+ typedef std::map<std::string, FakeTransportChannel*> ChannelMap;
+ FakeTransport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread)
+ : Transport(signaling_thread, worker_thread, "test", NULL),
+ dest_(NULL) {
+ }
+ ~FakeTransport() {
+ DestroyAllChannels();
+ }
+
+ const ChannelMap& channels() const { return channels_; }
+
+ void SetDestination(FakeTransport* dest) {
+ dest_ = dest;
+ for (ChannelMap::iterator it = channels_.begin(); it != channels_.end();
+ ++it) {
+ SetChannelDestination(it->first, it->second);
+ }
+ }
+
+ protected:
+ virtual TransportChannelImpl* CreateTransportChannel(
+ const std::string& name, const std::string& session_type) {
+ if (channels_.find(name) != channels_.end()) {
+ return NULL;
+ }
+ FakeTransportChannel* channel =
+ new FakeTransportChannel(this, name, session_type);
+ SetChannelDestination(name, channel);
+ channels_[name] = channel;
+ return channel;
+ }
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel) {
+ channels_.erase(channel->name());
+ delete channel;
+ }
+
+ private:
+ void SetChannelDestination(const std::string& name,
+ FakeTransportChannel* channel) {
+ FakeTransportChannel* dest_channel = NULL;
+ if (dest_) {
+ dest_channel =
+ static_cast<FakeTransportChannel*>(dest_->GetChannel(name));
+ }
+ channel->SetDestination(dest_channel);
+ }
+
+ ChannelMap channels_;
+ FakeTransport* dest_;
+};
+
+// Fake session class, which can be passed into a BaseChannel object for
+// test purposes. Can be connected to other FakeSessions via Connect().
+class FakeSession : public BaseSession {
+ public:
+ FakeSession()
+ : BaseSession(talk_base::Thread::Current(),
+ talk_base::Thread::Current(),
+ NULL, "", "", true),
+ fail_create_channel_(false) {
+ }
+
+ FakeTransport* GetTransport(const std::string& content_name) {
+ return static_cast<FakeTransport*>(
+ BaseSession::GetTransport(content_name));
+ }
+
+ void Connect(FakeSession* dest) {
+ // Simulate the exchange of candidates.
+ CompleteNegotiation();
+ dest->CompleteNegotiation();
+ for (TransportMap::const_iterator it = transport_proxies().begin();
+ it != transport_proxies().end(); ++it) {
+ static_cast<FakeTransport*>(it->second->impl())->SetDestination(
+ dest->GetTransport(it->first));
+ }
+ }
+
+ virtual cricket::TransportChannel* CreateChannel(
+ const std::string& content_name, const std::string& name) {
+ if (fail_create_channel_) {
+ return NULL;
+ }
+ return BaseSession::CreateChannel(content_name, name);
+ }
+
+ void set_fail_channel_creation(bool fail_channel_creation) {
+ fail_create_channel_ = fail_channel_creation;
+ }
+
+ protected:
+ virtual Transport* CreateTransport() {
+ return new FakeTransport(signaling_thread(), worker_thread());
+ }
+ void CompleteNegotiation() {
+ for (TransportMap::const_iterator it = transport_proxies().begin();
+ it != transport_proxies().end(); ++it) {
+ it->second->CompleteNegotiation();
+ }
+ }
+
+ private:
+ bool fail_create_channel_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKESESSION_H_
diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc
index dd170ff..5f55ef9 100644
--- a/talk/p2p/base/p2ptransport.cc
+++ b/talk/p2p/base/p2ptransport.cc
@@ -143,7 +143,7 @@
return true;
}
-const buzz::QName& GetCandidateQName(SignalingProtocol protocol) {
+static const buzz::StaticQName& GetCandidateQName(SignalingProtocol protocol) {
if (protocol == PROTOCOL_GINGLE) {
return QN_GINGLE_CANDIDATE;
} else {
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
index 17f52a7..87a0222 100644
--- a/talk/p2p/base/p2ptransportchannel.cc
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -301,7 +301,7 @@
// Handle stun packets
void P2PTransportChannel::OnUnknownAddress(
Port *port, const talk_base::SocketAddress &address, StunMessage *stun_msg,
- const std::string &remote_username) {
+ const std::string &remote_username, bool port_muxed) {
ASSERT(worker_thread_ == talk_base::Thread::Current());
// Port has received a valid stun packet from an address that no Connection
@@ -316,11 +316,17 @@
break;
}
}
+
if (candidate == NULL) {
+ if (port_muxed) {
+ // When Ports are muxed, SignalUnknownAddress is delivered to all
+ // P2PTransportChannel belong to a session. Return from here will
+ // save us from sending stun binding error message from incorrect channel.
+ return;
+ }
// 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;
@@ -451,6 +457,13 @@
return true;
}
+bool P2PTransportChannel::FindConnection(
+ cricket::Connection* connection) const {
+ std::vector<Connection*>::const_iterator citer =
+ std::find(connections_.begin(), connections_.end(), connection);
+ return citer != connections_.end();
+}
+
// Maintain our remote candidate list, adding this new remote one.
void P2PTransportChannel::RememberRemoteCandidate(
const Candidate& remote_candidate, Port* origin_port) {
@@ -900,8 +913,11 @@
const char *data, size_t len) {
ASSERT(worker_thread_ == talk_base::Thread::Current());
- // Let the client know of an incoming packet
+ // Do not deliver, if packet doesn't belong to the correct transport channel.
+ if (!FindConnection(connection))
+ return;
+ // Let the client know of an incoming packet
SignalReadPacket(this, data, len);
}
@@ -933,7 +949,8 @@
void P2PTransportChannel::OnSignalingReady() {
if (waiting_for_signaling_) {
waiting_for_signaling_ = false;
- AddAllocatorSession(allocator_->CreateSession(name(), content_type()));
+ AddAllocatorSession(allocator_->CreateSession(
+ session_id(), name(), content_type()));
thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE);
}
}
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
index 0083d23..6b4d979 100644
--- a/talk/p2p/base/p2ptransportchannel.h
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -116,11 +116,12 @@
bool readable);
bool CreateConnection(Port* port, const Candidate& remote_candidate,
Port* origin_port, bool readable);
+ bool FindConnection(cricket::Connection* connection) const;
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);
+ const std::string &remote_username, bool port_muxed);
void OnPortReady(PortAllocatorSession *session, Port* port);
void OnCandidatesReady(PortAllocatorSession *session,
const std::vector<Candidate>& candidates);
diff --git a/talk/p2p/base/p2ptransportchannel_unittest.cc b/talk/p2p/base/p2ptransportchannel_unittest.cc
new file mode 100644
index 0000000..ae5ab62
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel_unittest.cc
@@ -0,0 +1,659 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fakenetwork.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/proxyserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/testrelayserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+using talk_base::SocketAddress;
+
+static const int kDefaultTimeout = 1000;
+static const int kOnlyLocalPorts = cricket::PORTALLOCATOR_DISABLE_STUN |
+ cricket::PORTALLOCATOR_DISABLE_RELAY |
+ cricket::PORTALLOCATOR_DISABLE_TCP;
+// Addresses on the public internet.
+static const SocketAddress kPublicAddrs[2] =
+ { SocketAddress("11.11.11.11", 0), SocketAddress("22.22.22.22", 0) };
+// For configuring multihomed clients.
+static const SocketAddress kAlternateAddrs[2] =
+ { SocketAddress("11.11.11.101", 0), SocketAddress("22.22.22.202", 0) };
+// Addresses for HTTP proxy servers.
+static const SocketAddress kHttpsProxyAddrs[2] =
+ { SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
+// Addresses for SOCKS proxy servers.
+static const SocketAddress kSocksProxyAddrs[2] =
+ { SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
+// Internal addresses for NAT boxes.
+static const SocketAddress kNatAddrs[2] =
+ { SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
+// Private addresses inside the NAT private networks.
+static const SocketAddress kPrivateAddrs[2] =
+ { SocketAddress("192.168.1.11", 0), SocketAddress("192.168.2.22", 0) };
+// For cascaded NATs, the internal addresses of the inner NAT boxes.
+static const SocketAddress kCascadedNatAddrs[2] =
+ { SocketAddress("192.168.10.1", 0), SocketAddress("192.168.20.1", 0) };
+// For cascaded NATs, private addresses inside the inner private networks.
+static const SocketAddress kCascadedPrivateAddrs[2] =
+ { SocketAddress("192.168.10.11", 0), SocketAddress("192.168.20.22", 0) };
+// The address of the public STUN server.
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+// The addresses for the public relay server.
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+// This test simulates 2 P2P endpoints that want to establish connectivity
+// with each other over various network topologies and conditions, which can be
+// specified in each individial test.
+// A virtual network (via VirtualSocketServer) along with virtual firewalls and
+// NATs (via Firewall/NATSocketServer) are used to simulate the various network
+// conditions. We can configure the IP addresses of the endpoints,
+// block various types of connectivity, or add arbitrary levels of NAT.
+// We also run a STUN server and a relay server on the virtual network to allow
+// our typical P2P mechanisms to do their thing.
+// For each case, we expect the P2P stack to eventually settle on a specific
+// form of connectivity to the other side. The test checks that the P2P
+// negotiation successfully establishes connectivity within a certain time,
+// and that the result is what we expect.
+// Note that this class is a base class for use by other tests, who will provide
+// specialized test behavior.
+class P2PTransportChannelTestBase : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ P2PTransportChannelTestBase()
+ : main_(talk_base::Thread::Current()),
+ pss_(new talk_base::PhysicalSocketServer),
+ vss_(new talk_base::VirtualSocketServer(pss_.get())),
+ nss_(new talk_base::NATSocketServer(vss_.get())),
+ ss_(new talk_base::FirewallSocketServer(nss_.get())),
+ ss_scope_(ss_.get()),
+ stun_server_(main_, kStunAddr),
+ relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+ kRelayTcpIntAddr, kRelayTcpExtAddr,
+ kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+ socks_server1_(ss_.get(), kSocksProxyAddrs[0],
+ ss_.get(), kSocksProxyAddrs[0]),
+ socks_server2_(ss_.get(), kSocksProxyAddrs[1],
+ ss_.get(), kSocksProxyAddrs[1]),
+ allocator1_(&network_manager1_, kStunAddr,
+ kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr),
+ allocator2_(&network_manager2_, kStunAddr,
+ kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
+ }
+
+ protected:
+ enum Config {
+ OPEN, // Open to the Internet
+ NAT_FULL_CONE, // NAT, no filtering
+ NAT_ADDR_RESTRICTED, // NAT, must send to an addr to recv
+ NAT_PORT_RESTRICTED, // NAT, must send to an addr+port to recv
+ NAT_SYMMETRIC, // NAT, endpoint-dependent bindings
+ NAT_DOUBLE_CONE, // Double NAT, both cone
+ NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
+ BLOCK_UDP, // Firewall, UDP in/out blocked
+ BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
+ BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
+ PROXY_HTTPS, // All traffic through HTTPS proxy
+ PROXY_SOCKS, // All traffic through SOCKS proxy
+ NUM_CONFIGS
+ };
+
+ struct Result {
+ Result(const std::string& lt, const std::string lp,
+ const std::string& rt, const std::string rp, int wait)
+ : local_type(lt), local_proto(lp), remote_type(rt), remote_proto(rp),
+ connect_wait(wait) {
+ }
+ std::string local_type;
+ std::string local_proto;
+ std::string remote_type;
+ std::string remote_proto;
+ int connect_wait;
+ };
+
+ // Common results.
+ static const Result kLocalUdpToLocalUdp;
+ static const Result kLocalUdpToStunUdp;
+ static const Result kStunUdpToLocalUdp;
+ static const Result kStunUdpToStunUdp;
+ static const Result kLocalUdpToRelayUdp;
+ static const Result kLocalTcpToLocalTcp;
+
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ talk_base::InitRandom(NULL, 0);
+ }
+
+ talk_base::NATSocketServer* nat() { return nss_.get(); }
+ talk_base::FirewallSocketServer* fw() { return ss_.get(); }
+
+ cricket::PortAllocator* GetAllocator(int endpoint) {
+ return (endpoint == 0) ? &allocator1_ : &allocator2_;
+ }
+ void AddAddress(int endpoint, const SocketAddress& addr) {
+ talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
+ network_manager1_ : network_manager2_;
+ manager.AddInterface(addr);
+ }
+ void RemoveAddress(int endpoint, const SocketAddress& addr) {
+ talk_base::FakeNetworkManager& manager = (endpoint == 0) ?
+ network_manager1_ : network_manager2_;
+ manager.RemoveInterface(addr);
+ }
+ void SetProxy(int endpoint, talk_base::ProxyType type) {
+ talk_base::ProxyInfo info;
+ info.type = type;
+ info.address = (type == talk_base::PROXY_HTTPS) ?
+ kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
+ GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
+ }
+ void SetAllocatorFlags(int endpoint, int flags) {
+ GetAllocator(endpoint)->set_flags(flags);
+ }
+
+ void Test(const Result& expected) {
+ int32 connect_start = talk_base::Time(), connect_time;
+
+ // Create the channels and wait for them to connect.
+ CreateChannels();
+ EXPECT_TRUE_WAIT_MARGIN(ch1_.get() != NULL && ch2_.get() != NULL &&
+ ch1_->readable() && ch1_->writable() &&
+ ch2_->readable() && ch2_->writable(),
+ expected.connect_wait,
+ 1000);
+ connect_time = talk_base::TimeSince(connect_start);
+ if (connect_time < expected.connect_wait) {
+ LOG(LS_INFO) << "Connect time: " << connect_time << " ms";
+ } else {
+ LOG(LS_INFO) << "Connect time: " << "TIMEOUT ("
+ << expected.connect_wait << " ms)";
+ }
+
+ // Allow a few turns of the crank for the best connections to emerge.
+ // This may take up to 2 seconds.
+ if (ch1_->best_connection() && ch2_->best_connection()) {
+ int32 converge_start = talk_base::Time(), converge_time;
+ int converge_wait = 2000;
+ EXPECT_TRUE_WAIT_MARGIN(
+ LocalCandidate(ch1_.get())->type() == expected.local_type &&
+ LocalCandidate(ch1_.get())->protocol() == expected.local_proto &&
+ RemoteCandidate(ch1_.get())->type() == expected.remote_type &&
+ RemoteCandidate(ch1_.get())->protocol() == expected.remote_proto,
+ converge_wait,
+ converge_wait);
+
+ // Also do EXPECT_EQ on each part so that failures are more verbose.
+ EXPECT_EQ(expected.local_type, LocalCandidate(ch1_.get())->type());
+ EXPECT_EQ(expected.local_proto, LocalCandidate(ch1_.get())->protocol());
+ EXPECT_EQ(expected.remote_type, RemoteCandidate(ch1_.get())->type());
+ EXPECT_EQ(expected.remote_proto, RemoteCandidate(ch1_.get())->protocol());
+
+ /* TODO: Check ch2 candidates.
+ EXPECT_EQ(expected.local_type2, LocalCandidate(ch1_.get())->type());
+ EXPECT_EQ(expected.local_proto2, LocalCandidate(ch1_.get())->protocol());
+ EXPECT_EQ(expected.remote_type2, RemoteCandidate(ch1_.get())->type());
+ EXPECT_EQ(expected.remote_proto2, RemoteCandidate(ch1_.get())->protocol());
+ */
+
+ converge_time = talk_base::TimeSince(converge_start);
+ if (converge_time < converge_wait) {
+ LOG(LS_INFO) << "Converge time: " << converge_time << " ms";
+ } else {
+ LOG(LS_INFO) << "Converge time: " << "TIMEOUT ("
+ << converge_wait << " ms)";
+ }
+ }
+
+ // TODO: Send some data and make sure it gets there.
+
+ // Destroy the channels, and wait for them to be fully cleaned up.
+ DestroyChannels();
+ }
+
+ void CreateChannels() {
+ ch1_.reset(new cricket::P2PTransportChannel("a", "unittest",
+ NULL, &allocator1_));
+ ch2_.reset(new cricket::P2PTransportChannel("b", "unittest",
+ NULL, &allocator2_));
+ ch1_->SignalRequestSignaling.connect(ch1_.get(),
+ &cricket::P2PTransportChannel::OnSignalingReady);
+ ch2_->SignalRequestSignaling.connect(ch2_.get(),
+ &cricket::P2PTransportChannel::OnSignalingReady);
+ ch1_->SignalCandidateReady.connect(this,
+ &P2PTransportChannelTestBase::OnCandidate);
+ ch2_->SignalCandidateReady.connect(this,
+ &P2PTransportChannelTestBase::OnCandidate);
+ ch1_->Connect();
+ ch2_->Connect();
+ }
+ void DestroyChannels() {
+ ch1_.reset();
+ ch2_.reset();
+ }
+ cricket::P2PTransportChannel* ch1() { return ch1_.get(); }
+ cricket::P2PTransportChannel* ch2() { return ch2_.get(); }
+
+ // We pass the candidates directly to the other side.
+ void OnCandidate(cricket::TransportChannelImpl* ch,
+ const cricket::Candidate& c) {
+ if (ch == ch1_.get()) {
+ LOG(LS_INFO) << "Candidate(1->2): " << c.type() << ", " << c.protocol()
+ << ", " << c.address().ToString() << ", " << c.username()
+ << ", " << c.generation();
+ ch2_->OnCandidate(c);
+ } else {
+ LOG(LS_INFO) << "Candidate(2->1): " << c.type() << ", " << c.protocol()
+ << ", " << c.address().ToString() << ", " << c.username()
+ << ", " << c.generation();
+ ch1_->OnCandidate(c);
+ }
+ }
+ static const cricket::Candidate* LocalCandidate(
+ cricket::P2PTransportChannel* ch) {
+ return (ch && ch->best_connection()) ?
+ &ch->best_connection()->local_candidate() : NULL;
+ }
+ static const cricket::Candidate* RemoteCandidate(
+ cricket::P2PTransportChannel* ch) {
+ return (ch && ch->best_connection()) ?
+ &ch->best_connection()->remote_candidate() : NULL;
+ }
+
+ private:
+ enum { MSG_CONNECT, MSG_DISCONNECT };
+ talk_base::Thread* main_;
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+ talk_base::scoped_ptr<talk_base::NATSocketServer> nss_;
+ talk_base::scoped_ptr<talk_base::FirewallSocketServer> ss_;
+ talk_base::SocketServerScope ss_scope_;
+ cricket::TestStunServer stun_server_;
+ cricket::TestRelayServer relay_server_;
+ talk_base::SocksProxyServer socks_server1_;
+ talk_base::SocksProxyServer socks_server2_;
+ talk_base::FakeNetworkManager network_manager1_;
+ talk_base::FakeNetworkManager network_manager2_;
+ cricket::BasicPortAllocator allocator1_;
+ cricket::BasicPortAllocator allocator2_;
+ talk_base::scoped_ptr<cricket::P2PTransportChannel> ch1_;
+ talk_base::scoped_ptr<cricket::P2PTransportChannel> ch2_;
+};
+
+// The tests have only a few outcomes, which we predefine.
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToLocalUdp("local", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToStunUdp("local", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kStunUdpToLocalUdp("stun", "udp", "local", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kStunUdpToStunUdp("stun", "udp", "stun", "udp", 1000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalUdpToRelayUdp("local", "udp", "relay", "udp", 2000);
+const P2PTransportChannelTestBase::Result P2PTransportChannelTestBase::
+ kLocalTcpToLocalTcp("local", "tcp", "local", "tcp", 3000);
+
+// Test the matrix of all the connectivity types we expect to see in the wild.
+// Just test every combination of the configs in the Config enum.
+class P2PTransportChannelTest : public P2PTransportChannelTestBase {
+ protected:
+ static const Result* kMatrix[NUM_CONFIGS][NUM_CONFIGS];
+ void ConfigureEndpoints(Config config1, Config config2) {
+ ConfigureEndpoint(0, config1);
+ ConfigureEndpoint(1, config2);
+ }
+ void ConfigureEndpoint(int endpoint, Config config) {
+ switch (config) {
+ case OPEN:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ break;
+ case NAT_FULL_CONE:
+ case NAT_ADDR_RESTRICTED:
+ case NAT_PORT_RESTRICTED:
+ case NAT_SYMMETRIC:
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ // Add a single NAT of the desired type
+ nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->
+ AddClient(kPrivateAddrs[endpoint]);
+ break;
+ case NAT_DOUBLE_CONE:
+ case NAT_SYMMETRIC_THEN_CONE:
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ // Add a two cascaded NATs of the desired types
+ nat()->AddTranslator(kPublicAddrs[endpoint], kNatAddrs[endpoint],
+ (config == NAT_DOUBLE_CONE) ?
+ talk_base::NAT_OPEN_CONE : talk_base::NAT_SYMMETRIC)->
+ AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+ talk_base::NAT_OPEN_CONE)->
+ AddClient(kCascadedPrivateAddrs[endpoint]);
+ break;
+ case BLOCK_UDP:
+ case BLOCK_UDP_AND_INCOMING_TCP:
+ case BLOCK_ALL_BUT_OUTGOING_HTTP:
+ case PROXY_HTTPS:
+ case PROXY_SOCKS:
+ AddAddress(endpoint, kPublicAddrs[endpoint]);
+ // Block all UDP
+ fw()->AddRule(false, talk_base::FP_UDP, talk_base::FD_ANY,
+ kPublicAddrs[endpoint]);
+ if (config == BLOCK_UDP_AND_INCOMING_TCP) {
+ // Block TCP inbound to the endpoint
+ fw()->AddRule(false, talk_base::FP_TCP, SocketAddress(),
+ kPublicAddrs[endpoint]);
+ } else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
+ // Block all TCP to/from the endpoint except 80/443 out
+ fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(talk_base::IPAddress(INADDR_ANY), 80));
+ fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+ SocketAddress(talk_base::IPAddress(INADDR_ANY), 443));
+ fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+ kPublicAddrs[endpoint]);
+ } else if (config == PROXY_HTTPS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+ kHttpsProxyAddrs[endpoint]);
+ fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, talk_base::PROXY_HTTPS);
+ } else if (config == PROXY_SOCKS) {
+ // Block all TCP to/from the endpoint except to the proxy server
+ fw()->AddRule(true, talk_base::FP_TCP, kPublicAddrs[endpoint],
+ kSocksProxyAddrs[endpoint]);
+ fw()->AddRule(false, talk_base::FP_TCP, talk_base::FD_ANY,
+ kPublicAddrs[endpoint]);
+ SetProxy(endpoint, talk_base::PROXY_SOCKS5);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+// Shorthands for use in the test matrix.
+#define LULU &kLocalUdpToLocalUdp
+#define LUSU &kLocalUdpToStunUdp
+#define SULU &kStunUdpToLocalUdp
+#define SUSU &kStunUdpToStunUdp
+#define LURU &kLocalUdpToRelayUdp
+#define LTLT &kLocalTcpToLocalTcp
+// TODO: Enable these once TestRelayServer can accept external TCP.
+#define LTRT NULL
+#define LSRS NULL
+
+// Test matrix. Originator behavior defined by rows, receiever by columns.
+// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
+// TODO: Fix NULLs caused by no HTTP proxy support.
+// TODO: Rearrange rows/columns from best to worst.
+const P2PTransportChannelTest::Result*
+ P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
+// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
+/*OP*/ {LULU, LULU, LULU, LULU, LULU, LULU, LULU, LTLT, LTLT, LSRS, NULL, LTLT},
+/*CO*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*AD*/ {LULU, LULU, LULU, SUSU, SUSU, LULU, SUSU, NULL, NULL, LSRS, NULL, LTRT},
+/*PO*/ {LULU, LUSU, SUSU, SUSU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*SY*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*2C*/ {LULU, LULU, LULU, SULU, SULU, LULU, SULU, NULL, NULL, LSRS, NULL, LTRT},
+/*SC*/ {LULU, LUSU, SUSU, LURU, LURU, LUSU, LURU, NULL, NULL, LSRS, NULL, LTRT},
+/*!U*/ {LTLT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTLT, LSRS, NULL, LTRT},
+/*!T*/ {LTRT, NULL, NULL, NULL, NULL, NULL, NULL, LTLT, LTRT, LSRS, NULL, LTRT},
+/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
+/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
+/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
+};
+
+// The actual tests that exercise all the various configurations.
+// Test names are of the form P2PTransportChannelTest_TestOPENToNAT_FULL_CONE
+#define P2P_TEST_DECLARATION(x, y, z) \
+ TEST_F(P2PTransportChannelTest, z##Test##x##To##y) { \
+ ConfigureEndpoints(x, y); \
+ if (kMatrix[x][y] != NULL) \
+ Test(*kMatrix[x][y]); \
+ else \
+ LOG(LS_WARNING) << "Not yet implemented"; \
+ }
+
+#define P2P_TEST(x, y) \
+ P2P_TEST_DECLARATION(x, y,)
+
+#define FLAKY_P2P_TEST(x, y) \
+ P2P_TEST_DECLARATION(x, y, DISABLED_)
+
+#define P2P_TEST_SET(x) \
+ P2P_TEST(x, OPEN) \
+ P2P_TEST(x, NAT_FULL_CONE) \
+ P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+ P2P_TEST(x, NAT_PORT_RESTRICTED) \
+ P2P_TEST(x, NAT_SYMMETRIC) \
+ P2P_TEST(x, NAT_DOUBLE_CONE) \
+ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+ P2P_TEST(x, BLOCK_UDP) \
+ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+ P2P_TEST(x, PROXY_HTTPS) \
+ P2P_TEST(x, PROXY_SOCKS)
+
+#define FLAKY_P2P_TEST_SET(x) \
+ P2P_TEST(x, OPEN) \
+ P2P_TEST(x, NAT_FULL_CONE) \
+ FLAKY_P2P_TEST(x, NAT_ADDR_RESTRICTED) \
+ P2P_TEST(x, NAT_PORT_RESTRICTED) \
+ P2P_TEST(x, NAT_SYMMETRIC) \
+ P2P_TEST(x, NAT_DOUBLE_CONE) \
+ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
+ P2P_TEST(x, BLOCK_UDP) \
+ P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
+ P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
+ P2P_TEST(x, PROXY_HTTPS) \
+ P2P_TEST(x, PROXY_SOCKS)
+
+P2P_TEST_SET(OPEN)
+P2P_TEST_SET(NAT_FULL_CONE)
+FLAKY_P2P_TEST_SET(NAT_ADDR_RESTRICTED)
+FLAKY_P2P_TEST_SET(NAT_PORT_RESTRICTED)
+FLAKY_P2P_TEST_SET(NAT_SYMMETRIC)
+P2P_TEST_SET(NAT_DOUBLE_CONE)
+FLAKY_P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
+P2P_TEST_SET(BLOCK_UDP)
+P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
+P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
+P2P_TEST_SET(PROXY_HTTPS)
+P2P_TEST_SET(PROXY_SOCKS)
+
+// Test that a host behind NAT cannot be reached when incoming_only
+// is set to true.
+TEST_F(P2PTransportChannelTest, IncomingOnlyBlocked) {
+ ConfigureEndpoints(NAT_FULL_CONE, OPEN);
+
+ CreateChannels();
+ ch1()->set_incoming_only(true);
+
+ // Sleep for 1 second and verify that the channels are not connected.
+ talk_base::Thread::SleepMs(1000);
+
+ EXPECT_FALSE(ch1()->readable());
+ EXPECT_FALSE(ch1()->writable());
+ EXPECT_FALSE(ch2()->readable());
+ EXPECT_FALSE(ch2()->writable());
+
+ DestroyChannels();
+}
+
+// Test that a peer behind NAT can connect to a peer that has
+// incoming_only flag set.
+TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
+ ConfigureEndpoints(OPEN, NAT_FULL_CONE);
+
+ CreateChannels();
+ ch1()->set_incoming_only(true);
+
+ EXPECT_TRUE_WAIT_MARGIN(ch1() != NULL && ch2() != NULL &&
+ ch1()->readable() && ch1()->writable() &&
+ ch2()->readable() && ch2()->writable(),
+ 1000, 1000);
+
+ DestroyChannels();
+}
+
+// Test what happens when we have 2 users behind the same NAT. This can lead
+// to interesting behavior because the STUN server will only give out the
+// address of the outermost NAT.
+class P2PTransportChannelSameNatTest : public P2PTransportChannelTestBase {
+ protected:
+ void ConfigureEndpoints(Config nat_type, Config config1, Config config2) {
+ ASSERT(nat_type >= NAT_FULL_CONE && nat_type <= NAT_SYMMETRIC);
+ talk_base::NATSocketServer::Translator* outer_nat =
+ nat()->AddTranslator(kPublicAddrs[0], kNatAddrs[0],
+ static_cast<talk_base::NATType>(nat_type - NAT_FULL_CONE));
+ ConfigureEndpoint(outer_nat, 0, config1);
+ ConfigureEndpoint(outer_nat, 1, config2);
+ }
+ void ConfigureEndpoint(talk_base::NATSocketServer::Translator* nat,
+ int endpoint, Config config) {
+ ASSERT(config <= NAT_SYMMETRIC);
+ if (config == OPEN) {
+ AddAddress(endpoint, kPrivateAddrs[endpoint]);
+ nat->AddClient(kPrivateAddrs[endpoint]);
+ } else {
+ AddAddress(endpoint, kCascadedPrivateAddrs[endpoint]);
+ nat->AddTranslator(kPrivateAddrs[endpoint], kCascadedNatAddrs[endpoint],
+ static_cast<talk_base::NATType>(config - NAT_FULL_CONE))->AddClient(
+ kCascadedPrivateAddrs[endpoint]);
+ }
+ }
+};
+
+TEST_F(P2PTransportChannelSameNatTest, TestConesBehindSameCone) {
+ ConfigureEndpoints(NAT_FULL_CONE, NAT_FULL_CONE, NAT_FULL_CONE);
+ Test(kLocalUdpToLocalUdp);
+}
+
+// Test what happens when we have multiple available pathways.
+// In the future we will try different RTTs and configs for the different
+// interfaces, so that we can simulate a user with Ethernet and VPN networks.
+class P2PTransportChannelMultihomedTest : public P2PTransportChannelTestBase {
+};
+
+// Test that we can establish connectivity when both peers are multihomed.
+TEST_F(P2PTransportChannelMultihomedTest, TestBasic) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(0, kAlternateAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ AddAddress(1, kAlternateAddrs[1]);
+ Test(kLocalUdpToLocalUdp);
+}
+
+// Test that we can quickly switch links if an interface goes down.
+TEST_F(P2PTransportChannelMultihomedTest, TestFailover) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ AddAddress(1, kAlternateAddrs[1]);
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
+ ch2()->readable() && ch2()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ch1()->best_connection() && ch2()->best_connection() &&
+ LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ // Blackhole any traffic to or from the public addrs.
+ LOG(LS_INFO) << "Failing over...";
+ fw()->AddRule(false, talk_base::FP_ANY, talk_base::FD_ANY,
+ kPublicAddrs[1]);
+
+ // We should detect loss of connectivity within 5 seconds or so.
+ EXPECT_TRUE_WAIT(!ch1()->writable(), 7000);
+
+ // We should switch over to use the alternate addr immediately
+ // when we lose writability.
+ EXPECT_TRUE_WAIT(
+ ch1()->best_connection() && ch2()->best_connection() &&
+ LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
+ 3000);
+
+ DestroyChannels();
+}
+
+// Test that we can switch links in a coordinated fashion.
+TEST_F(P2PTransportChannelMultihomedTest, TestDrain) {
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Use only local ports for simplicity.
+ SetAllocatorFlags(0, kOnlyLocalPorts);
+ SetAllocatorFlags(1, kOnlyLocalPorts);
+
+ // Create channels and let them go writable, as usual.
+ CreateChannels();
+ EXPECT_TRUE_WAIT(ch1()->readable() && ch1()->writable() &&
+ ch2()->readable() && ch2()->writable(),
+ 1000);
+ EXPECT_TRUE(
+ ch1()->best_connection() && ch2()->best_connection() &&
+ LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ch1())->address().EqualIPs(kPublicAddrs[1]));
+
+ // Remove the public interface, add the alternate interface, and allocate
+ // a new generation of candidates for the new interface (via Connect()).
+ LOG(LS_INFO) << "Draining...";
+ AddAddress(1, kAlternateAddrs[1]);
+ RemoveAddress(1, kPublicAddrs[1]);
+ ch2()->Connect();
+
+ // We should switch over to use the alternate address after
+ // an exchange of pings.
+ EXPECT_TRUE_WAIT(
+ ch1()->best_connection() && ch2()->best_connection() &&
+ LocalCandidate(ch1())->address().EqualIPs(kPublicAddrs[0]) &&
+ RemoteCandidate(ch1())->address().EqualIPs(kAlternateAddrs[1]),
+ 3000);
+
+ DestroyChannels();
+}
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
index a54d379..ebe0596 100644
--- a/talk/p2p/base/parsing.cc
+++ b/talk/p2p/base/parsing.cc
@@ -120,6 +120,14 @@
}
}
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+ const buzz::QName name,
+ const std::string& value) {
+ if (!value.empty()) {
+ elem->AddAttr(name, value);
+ }
+}
+
void AddXmlChildren(buzz::XmlElement* parent,
const std::vector<buzz::XmlElement*>& children) {
for (std::vector<buzz::XmlElement*>::const_iterator iter = children.begin();
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
index 7931431..96f4e8b 100644
--- a/talk/p2p/base/parsing.h
+++ b/talk/p2p/base/parsing.h
@@ -112,6 +112,7 @@
const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
const std::string& name);
+
bool RequireXmlChild(const buzz::XmlElement* parent,
const std::string& name,
const buzz::XmlElement** child,
@@ -120,6 +121,9 @@
const buzz::QName& name,
std::string* value,
ParseError* error);
+void AddXmlAttrIfNonEmpty(buzz::XmlElement* elem,
+ const buzz::QName name,
+ const std::string& value);
void AddXmlChildren(buzz::XmlElement* parent,
const std::vector<buzz::XmlElement*>& children);
void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest);
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
index bb4ab26..d47e90e 100644
--- a/talk/p2p/base/port.cc
+++ b/talk/p2p/base/port.cc
@@ -126,7 +126,7 @@
Port::Port(talk_base::Thread* thread, const std::string& type,
talk_base::PacketSocketFactory* factory, talk_base::Network* network,
- uint32 ip, int min_port, int max_port)
+ const talk_base::IPAddress& ip, int min_port, int max_port)
: thread_(thread),
factory_(factory),
type_(type),
@@ -212,7 +212,7 @@
} else if (!msg) {
// STUN message handled already
} else if (msg->type() == STUN_BINDING_REQUEST) {
- SignalUnknownAddress(this, addr, msg, remote_username);
+ SignalUnknownAddress(this, addr, msg, remote_username, false);
} else {
// NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
// pruned a connection for this port while it had STUN requests in flight,
@@ -351,7 +351,7 @@
StunAddressAttribute* addr_attr =
StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
addr_attr->SetPort(addr.port());
- addr_attr->SetIP(addr.ip());
+ addr_attr->SetIP(addr.ipaddr());
response.AddAttribute(addr_attr);
// Send the response message.
@@ -528,11 +528,8 @@
}
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;
+ ASSERT(local_candidate_index_ < port_->candidates().size());
+ return port_->candidates()[local_candidate_index_];
}
void Connection::set_read_state(ReadState value) {
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
index 8304146..1e81b0a 100644
--- a/talk/p2p/base/port.h
+++ b/talk/p2p/base/port.h
@@ -77,7 +77,7 @@
public:
Port(talk_base::Thread* thread, const std::string& type,
talk_base::PacketSocketFactory* factory, talk_base::Network* network,
- uint32 ip, int min_port, int max_port);
+ const talk_base::IPAddress& ip, int min_port, int max_port);
virtual ~Port();
// The thread on which this port performs its I/O.
@@ -107,7 +107,6 @@
const std::string& password() const { return password_; }
void set_password(const std::string& password) { password_ = password; }
-
// A value in [0,1] that indicates the preference for this port versus other
// ports on this client. (Larger indicates more preference.)
float preference() const { return preference_; }
@@ -141,7 +140,8 @@
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);
+ virtual 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 };
@@ -160,15 +160,15 @@
// 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;
+ sigslot::signal5<Port*, const talk_base::SocketAddress&, StunMessage*,
+ const std::string&, bool> 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(
+ virtual void SendBindingResponse(StunMessage* request,
+ const talk_base::SocketAddress& addr);
+ virtual void SendBindingErrorResponse(
StunMessage* request, const talk_base::SocketAddress& addr,
int error_code, const std::string& reason);
@@ -211,6 +211,9 @@
// Debugging description of this port
std::string ToString() const;
+ talk_base::IPAddress& ip() { return ip_; }
+ int min_port() { return min_port_; }
+ int max_port() { return max_port_; }
protected:
// Fills in the local address of the port.
@@ -241,7 +244,7 @@
talk_base::PacketSocketFactory* factory_;
std::string type_;
talk_base::Network* network_;
- uint32 ip_;
+ talk_base::IPAddress ip_;
int min_port_;
int max_port_;
uint32 generation_;
diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc
new file mode 100644
index 0000000..9a84606
--- /dev/null
+++ b/talk/p2p/base/port_unittest.cc
@@ -0,0 +1,761 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.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"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/base/testrelayserver.h"
+
+using talk_base::AsyncPacketSocket;
+using talk_base::NATType;
+using talk_base::NAT_OPEN_CONE;
+using talk_base::NAT_ADDR_RESTRICTED;
+using talk_base::NAT_PORT_RESTRICTED;
+using talk_base::NAT_SYMMETRIC;
+using talk_base::PacketSocketFactory;
+using talk_base::scoped_ptr;
+using talk_base::Socket;
+using talk_base::SocketAddress;
+using namespace cricket;
+
+static const int kTimeout = 1000;
+static const SocketAddress kLocalAddr1 = SocketAddress("192.168.1.2", 0);
+static const SocketAddress kLocalAddr2 = SocketAddress("192.168.1.3", 0);
+static const SocketAddress kNatAddr1 = SocketAddress("77.77.77.77",
+ talk_base::NAT_SERVER_PORT);
+static const SocketAddress kNatAddr2 = SocketAddress("88.88.88.88",
+ talk_base::NAT_SERVER_PORT);
+static const SocketAddress kStunAddr = SocketAddress("99.99.99.1",
+ STUN_SERVER_PORT);
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+static Candidate GetCandidate(Port* port) {
+ assert(port->candidates().size() == 1);
+ return port->candidates()[0];
+}
+
+static SocketAddress GetAddress(Port* port) {
+ return GetCandidate(port).address();
+}
+
+class TestChannel : public sigslot::has_slots<> {
+ public:
+ TestChannel(Port* p1, Port* p2)
+ : src_(p1), dst_(p2), address_count_(0), conn_(NULL),
+ remote_request_(NULL) {
+ src_->SignalAddressReady.connect(this, &TestChannel::OnAddressReady);
+ src_->SignalUnknownAddress.connect(this, &TestChannel::OnUnknownAddress);
+ }
+
+ int address_count() { return address_count_; }
+ Connection* conn() { return conn_; }
+ const SocketAddress& remote_address() { return remote_address_; }
+ const std::string remote_fragment() { return remote_frag_; }
+
+ void Start() {
+ src_->PrepareAddress();
+ }
+ void CreateConnection() {
+ conn_ = src_->CreateConnection(GetCandidate(dst_), Port::ORIGIN_MESSAGE);
+ }
+ void AcceptConnection() {
+ ASSERT_TRUE(remote_request_ != NULL);
+ Candidate c = GetCandidate(dst_);
+ c.set_address(remote_address_);
+ conn_ = src_->CreateConnection(c, Port::ORIGIN_MESSAGE);
+ src_->SendBindingResponse(remote_request_, remote_address_);
+ delete remote_request_;
+ }
+ void Ping() {
+ conn_->Ping(0);
+ }
+ void Stop() {
+ conn_->SignalDestroyed.connect(this, &TestChannel::OnDestroyed);
+ conn_->Destroy();
+ }
+
+ void OnAddressReady(Port* port) {
+ address_count_++;
+ }
+
+ void OnUnknownAddress(Port* port, const SocketAddress& addr,
+ StunMessage* msg, const std::string& rf,
+ bool /*port_muxed*/) {
+ ASSERT_EQ(src_.get(), port);
+ if (!remote_address_.IsAny()) {
+ ASSERT_EQ(remote_address_, addr);
+ delete remote_request_;
+ }
+ remote_address_ = addr;
+ remote_request_ = msg;
+ remote_frag_ = rf;
+ }
+
+ void OnDestroyed(Connection* conn) {
+ ASSERT_EQ(conn_, conn);
+ conn_ = NULL;
+ }
+
+ private:
+ talk_base::Thread* thread_;
+ talk_base::scoped_ptr<Port> src_;
+ Port* dst_;
+
+ int address_count_;
+ Connection* conn_;
+ SocketAddress remote_address_;
+ StunMessage* remote_request_;
+ std::string remote_frag_;
+};
+
+class PortTest : public testing::Test {
+ public:
+ PortTest()
+ : main_(talk_base::Thread::Current()),
+ pss_(new talk_base::PhysicalSocketServer),
+ ss_(new talk_base::VirtualSocketServer(pss_.get())),
+ ss_scope_(ss_.get()),
+ network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+ socket_factory_(talk_base::Thread::Current()),
+ nat_factory1_(ss_.get(), kNatAddr1),
+ nat_factory2_(ss_.get(), kNatAddr2),
+ nat_socket_factory1_(&nat_factory1_),
+ nat_socket_factory2_(&nat_factory2_),
+ stun_server_(main_, kStunAddr),
+ relay_server_(main_, kRelayUdpIntAddr, kRelayUdpExtAddr,
+ kRelayTcpIntAddr, kRelayTcpExtAddr,
+ kRelaySslTcpIntAddr, kRelaySslTcpExtAddr) {
+ }
+
+ protected:
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ talk_base::InitRandom(NULL, 0);
+ }
+
+ void TestLocalToLocal() {
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+ TestConnectivity("udp", port1, "udp", port2, true, true, true, true);
+ }
+ void TestLocalToStun(NATType type) {
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ nat_server2_.reset(CreateNatServer(kNatAddr2, type));
+ StunPort* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ TestConnectivity("udp", port1, StunName(type), port2,
+ type == NAT_OPEN_CONE, true, type != NAT_SYMMETRIC, true);
+ }
+ void TestLocalToRelay(ProtocolType proto) {
+ UDPPort* port1 = CreateUdpPort(kLocalAddr1);
+ RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+ TestConnectivity("udp", port1, RelayName(proto), port2,
+ true, true, true, true);
+ }
+ void TestStunToLocal(NATType type) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, type));
+ StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ UDPPort* port2 = CreateUdpPort(kLocalAddr2);
+ TestConnectivity(StunName(type), port1, "udp", port2,
+ true, type != NAT_SYMMETRIC, true, true);
+ }
+ void TestStunToStun(NATType type1, NATType type2) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, type1));
+ StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ nat_server2_.reset(CreateNatServer(kNatAddr2, type2));
+ StunPort* port2 = CreateStunPort(kLocalAddr2, &nat_socket_factory2_);
+ TestConnectivity(StunName(type1), port1, StunName(type2), port2,
+ type2 == NAT_OPEN_CONE,
+ type1 != NAT_SYMMETRIC, type2 != NAT_SYMMETRIC,
+ type1 + type2 < (NAT_PORT_RESTRICTED + NAT_SYMMETRIC));
+ }
+ void TestStunToRelay(NATType type, ProtocolType proto) {
+ nat_server1_.reset(CreateNatServer(kNatAddr1, type));
+ StunPort* port1 = CreateStunPort(kLocalAddr1, &nat_socket_factory1_);
+ RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_UDP);
+ TestConnectivity(StunName(type), port1, RelayName(proto), port2,
+ true, type != NAT_SYMMETRIC, true, true);
+ }
+ void TestTcpToTcp() {
+ TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+ TCPPort* port2 = CreateTcpPort(kLocalAddr2);
+ TestConnectivity("tcp", port1, "tcp", port2, true, false, true, true);
+ }
+ void TestTcpToRelay(ProtocolType proto) {
+ TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+ RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_TCP);
+ TestConnectivity("tcp", port1, RelayName(proto), port2,
+ true, false, true, true);
+ }
+ void TestSslTcpToRelay(ProtocolType proto) {
+ TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+ RelayPort* port2 = CreateRelayPort(kLocalAddr2, proto, PROTO_SSLTCP);
+ TestConnectivity("ssltcp", port1, RelayName(proto), port2,
+ true, false, true, true);
+ }
+
+ // helpers for above functions
+ UDPPort* CreateUdpPort(const SocketAddress& addr) {
+ return CreateUdpPort(addr, &socket_factory_);
+ }
+ UDPPort* CreateUdpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ return UDPPort::Create(main_, socket_factory, &network_,
+ addr.ipaddr(), 0, 0);
+ }
+ TCPPort* CreateTcpPort(const SocketAddress& addr) {
+ return CreateTcpPort(addr, &socket_factory_);
+ }
+ TCPPort* CreateTcpPort(const SocketAddress& addr,
+ PacketSocketFactory* socket_factory) {
+ return TCPPort::Create(main_, socket_factory, &network_,
+ addr.ipaddr(), 0, 0, true);
+ }
+ StunPort* CreateStunPort(const SocketAddress& addr,
+ talk_base::PacketSocketFactory* factory) {
+ return StunPort::Create(main_, factory, &network_,
+ addr.ipaddr(), 0, 0, kStunAddr);
+ }
+ RelayPort* CreateRelayPort(const SocketAddress& addr,
+ ProtocolType int_proto, ProtocolType ext_proto) {
+ std::string user = talk_base::CreateRandomString(16);
+ std::string pass = talk_base::CreateRandomString(16);
+ RelayPort* port = RelayPort::Create(main_, &socket_factory_, &network_,
+ addr.ipaddr(), 0, 0, user, pass, "");
+ SocketAddress addrs[] =
+ { kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr };
+ port->AddServerAddress(ProtocolAddress(addrs[int_proto], int_proto));
+ // TODO: Add an external address for ext_proto, so that the
+ // other side can connect to this port using a non-UDP protocol.
+ return port;
+ }
+ talk_base::NATServer* CreateNatServer(const SocketAddress& addr,
+ talk_base::NATType type) {
+ return new talk_base::NATServer(type, ss_.get(), addr, ss_.get(), addr);
+ }
+ static const char* StunName(NATType type) {
+ switch (type) {
+ case NAT_OPEN_CONE: return "stun(open cone)";
+ case NAT_ADDR_RESTRICTED: return "stun(addr restricted)";
+ case NAT_PORT_RESTRICTED: return "stun(port restricted)";
+ case NAT_SYMMETRIC: return "stun(symmetric)";
+ default: return "stun(?)";
+ }
+ }
+ static const char* RelayName(ProtocolType type) {
+ switch (type) {
+ case PROTO_UDP: return "relay(udp)";
+ case PROTO_TCP: return "relay(tcp)";
+ case PROTO_SSLTCP: return "relay(ssltcp)";
+ default: return "relay(?)";
+ }
+ }
+
+ // this does all the work
+ void TestConnectivity(const char* name1, Port* port1,
+ const char* name2, Port* port2,
+ bool accept, bool same_addr1,
+ bool same_addr2, bool possible);
+
+ private:
+ talk_base::Thread* main_;
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> ss_;
+ talk_base::SocketServerScope ss_scope_;
+ talk_base::Network network_;
+ talk_base::BasicPacketSocketFactory socket_factory_;
+ talk_base::scoped_ptr<talk_base::NATServer> nat_server1_;
+ talk_base::scoped_ptr<talk_base::NATServer> nat_server2_;
+ talk_base::NATSocketFactory nat_factory1_;
+ talk_base::NATSocketFactory nat_factory2_;
+ talk_base::BasicPacketSocketFactory nat_socket_factory1_;
+ talk_base::BasicPacketSocketFactory nat_socket_factory2_;
+ TestStunServer stun_server_;
+ TestRelayServer relay_server_;
+};
+
+void PortTest::TestConnectivity(const char* name1, Port* port1,
+ const char* name2, Port* port2,
+ bool accept, bool same_addr1,
+ bool same_addr2, bool possible) {
+ LOG(LS_INFO) << "Test: " << name1 << " to " << name2 << ": ";
+ port1->set_name("src");
+ port2->set_name("dst");
+
+ // Set up channels.
+ TestChannel ch1(port1, port2);
+ TestChannel ch2(port2, port1);
+ EXPECT_EQ(0, ch1.address_count());
+ EXPECT_EQ(0, ch2.address_count());
+
+ // Acquire addresses.
+ ch1.Start();
+ ch2.Start();
+ ASSERT_EQ_WAIT(1, ch1.address_count(), kTimeout);
+ ASSERT_EQ_WAIT(1, ch2.address_count(), kTimeout);
+
+ // Send a ping from src to dst. This may or may not make it.
+ ch1.CreateConnection();
+ ASSERT_TRUE(ch1.conn() != NULL);
+ EXPECT_TRUE_WAIT(ch1.conn()->connected(), kTimeout); // for TCP connect
+ ch1.Ping();
+ WAIT(!ch2.remote_address().IsAny(), kTimeout);
+
+ if (accept) {
+ // We are able to send a ping from src to dst. This is the case when
+ // sending to UDP ports and cone NATs.
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+ EXPECT_EQ(ch2.remote_fragment(), port1->username_fragment());
+
+ // Ensure the ping came from the same address used for src.
+ // This is the case unless the source NAT was symmetric.
+ if (same_addr1) EXPECT_EQ(ch2.remote_address(), GetAddress(port1));
+ EXPECT_TRUE(same_addr2);
+
+ // Send a ping from dst to src.
+ ch2.AcceptConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+ kTimeout);
+ } else {
+ // We can't send a ping from src to dst, so flip it around. This will happen
+ // when the destination NAT is addr/port restricted or symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+ EXPECT_TRUE(ch2.remote_address().IsAny());
+
+ // Send a ping from dst to src. Again, this may or may not make it.
+ ch2.CreateConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ WAIT(ch2.conn()->write_state() == Connection::STATE_WRITABLE, kTimeout);
+
+ if (same_addr1 && same_addr2) {
+ // The new ping got back to the source.
+ EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+
+ // First connection may not be writable if the first ping did not get
+ // through. So we will have to do another.
+ if (ch1.conn()->write_state() == Connection::STATE_WRITE_CONNECT) {
+ ch1.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ }
+ } else if (!same_addr1 && possible) {
+ // The new ping went to the candidate address, but that address was bad.
+ // This will happen when the source NAT is symmetric.
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+ EXPECT_TRUE(ch2.remote_address().IsAny());
+
+ // However, since we have now sent a ping to the source IP, we should be
+ // able to get a ping from it. This gives us the real source address.
+ ch1.Ping();
+ EXPECT_TRUE_WAIT(!ch2.remote_address().IsAny(), kTimeout);
+ EXPECT_EQ(Connection::STATE_READ_TIMEOUT, ch2.conn()->read_state());
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+
+ // Pick up the actual address and establish the connection.
+ ch2.AcceptConnection();
+ ASSERT_TRUE(ch2.conn() != NULL);
+ ch2.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch2.conn()->write_state(),
+ kTimeout);
+ } else if (!same_addr2 && possible) {
+ // The new ping came in, but from an unexpected address. This will happen
+ // when the destination NAT is symmetric.
+ EXPECT_FALSE(ch1.remote_address().IsAny());
+ EXPECT_EQ(Connection::STATE_READ_TIMEOUT, ch1.conn()->read_state());
+
+ // Update our address and complete the connection.
+ ch1.AcceptConnection();
+ ch1.Ping();
+ EXPECT_EQ_WAIT(Connection::STATE_WRITABLE, ch1.conn()->write_state(),
+ kTimeout);
+ } else { // (!possible)
+ // There should be s no way for the pings to reach each other. Check it.
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+ EXPECT_TRUE(ch2.remote_address().IsAny());
+ ch1.Ping();
+ WAIT(!ch2.remote_address().IsAny(), kTimeout);
+ EXPECT_TRUE(ch1.remote_address().IsAny());
+ EXPECT_TRUE(ch2.remote_address().IsAny());
+ }
+ }
+
+ // Everything should be good, unless we know the situation is impossible.
+ ASSERT_TRUE(ch1.conn() != NULL);
+ ASSERT_TRUE(ch2.conn() != NULL);
+ if (possible) {
+ EXPECT_EQ(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_EQ(Connection::STATE_READABLE, ch2.conn()->read_state());
+ EXPECT_EQ(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ } else {
+ EXPECT_NE(Connection::STATE_READABLE, ch1.conn()->read_state());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch1.conn()->write_state());
+ EXPECT_NE(Connection::STATE_READABLE, ch2.conn()->read_state());
+ EXPECT_NE(Connection::STATE_WRITABLE, ch2.conn()->write_state());
+ }
+
+ // Tear down and ensure that goes smoothly.
+ ch1.Stop();
+ ch2.Stop();
+ EXPECT_TRUE_WAIT(ch1.conn() == NULL, kTimeout);
+ EXPECT_TRUE_WAIT(ch2.conn() == NULL, kTimeout);
+}
+
+class FakePacketSocketFactory : public talk_base::PacketSocketFactory {
+ public:
+ FakePacketSocketFactory()
+ : next_udp_socket_(NULL),
+ next_server_tcp_socket_(NULL),
+ next_client_tcp_socket_(NULL) {
+ }
+ virtual ~FakePacketSocketFactory() { }
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) {
+ EXPECT_TRUE(next_udp_socket_ != NULL);
+ AsyncPacketSocket* result = next_udp_socket_;
+ next_udp_socket_ = NULL;
+ return result;
+ }
+
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ bool ssl) {
+ EXPECT_TRUE(next_server_tcp_socket_ != NULL);
+ AsyncPacketSocket* result = next_server_tcp_socket_;
+ next_server_tcp_socket_ = NULL;
+ return result;
+ }
+
+ // TODO: |proxy_info| and |user_agent| should be set
+ // per-factory and not when socket is created.
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const talk_base::ProxyInfo& proxy_info,
+ const std::string& user_agent, bool ssl) {
+ EXPECT_TRUE(next_client_tcp_socket_ != NULL);
+ AsyncPacketSocket* result = next_client_tcp_socket_;
+ next_client_tcp_socket_ = NULL;
+ return result;
+ }
+
+ void set_next_udp_socket(AsyncPacketSocket* next_udp_socket) {
+ next_udp_socket_ = next_udp_socket;
+ }
+ void set_next_server_tcp_socket(AsyncPacketSocket* next_server_tcp_socket) {
+ next_server_tcp_socket_ = next_server_tcp_socket;
+ }
+ void set_next_client_tcp_socket(AsyncPacketSocket* next_client_tcp_socket) {
+ next_client_tcp_socket_ = next_client_tcp_socket;
+ }
+
+ private:
+ AsyncPacketSocket* next_udp_socket_;
+ AsyncPacketSocket* next_server_tcp_socket_;
+ AsyncPacketSocket* next_client_tcp_socket_;
+};
+
+class FakeAsyncPacketSocket : public AsyncPacketSocket {
+ public:
+ // Returns current local address. Address may be set to NULL if the
+ // socket is not bound yet (GetState() returns STATE_BINDING).
+ virtual SocketAddress GetLocalAddress() const {
+ return SocketAddress();
+ }
+
+ // Returns remote address. Returns zeroes if this is not a client TCP socket.
+ virtual SocketAddress GetRemoteAddress() const {
+ return SocketAddress();
+ }
+
+ // Send a packet.
+ virtual int Send(const void *pv, size_t cb) {
+ return cb;
+ }
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+ return cb;
+ }
+ virtual int Close() {
+ return 0;
+ }
+
+ virtual State GetState() const { return state_; }
+ virtual int GetOption(Socket::Option opt, int* value) { return 0; }
+ virtual int SetOption(Socket::Option opt, int value) { return 0; }
+ virtual int GetError() const { return 0; }
+ virtual void SetError(int error) { }
+
+ void set_state(State state) { state_ = state; }
+
+ private:
+ State state_;
+};
+
+// Local -> XXXX
+TEST_F(PortTest, TestLocalToLocal) {
+ TestLocalToLocal();
+}
+
+TEST_F(PortTest, TestLocalToConeNat) {
+ TestLocalToStun(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestLocalToARNat) {
+ TestLocalToStun(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToPRNat) {
+ TestLocalToStun(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestLocalToSymNat) {
+ TestLocalToStun(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestLocalToRelay) {
+ TestLocalToRelay(PROTO_UDP);
+}
+
+TEST_F(PortTest, TestLocalToTcpRelay) {
+ TestLocalToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestLocalToSslTcpRelay) {
+ TestLocalToRelay(PROTO_SSLTCP);
+}
+
+// Cone NAT -> XXXX
+TEST_F(PortTest, TestConeNatToLocal) {
+ TestStunToLocal(NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToConeNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestConeNatToARNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToPRNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestConeNatToSymNat) {
+ TestStunToStun(NAT_OPEN_CONE, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestConeNatToRelay) {
+ TestStunToRelay(NAT_OPEN_CONE, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestConeNatToTcpRelay) {
+ TestStunToRelay(NAT_OPEN_CONE, PROTO_TCP);
+}
+
+// Address-restricted NAT -> XXXX
+TEST_F(PortTest, TestARNatToLocal) {
+ TestStunToLocal(NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToConeNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestARNatToARNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToPRNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestARNatToSymNat) {
+ TestStunToStun(NAT_ADDR_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestARNatToRelay) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestARNATNatToTcpRelay) {
+ TestStunToRelay(NAT_ADDR_RESTRICTED, PROTO_TCP);
+}
+
+// Port-restricted NAT -> XXXX
+TEST_F(PortTest, TestPRNatToLocal) {
+ TestStunToLocal(NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToConeNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestPRNatToARNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToPRNat) {
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestPRNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_PORT_RESTRICTED, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestPRNatToRelay) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestPRNatToTcpRelay) {
+ TestStunToRelay(NAT_PORT_RESTRICTED, PROTO_TCP);
+}
+
+// Symmetric NAT -> XXXX
+TEST_F(PortTest, TestSymNatToLocal) {
+ TestStunToLocal(NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToConeNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_OPEN_CONE);
+}
+
+TEST_F(PortTest, TestSymNatToARNat) {
+ TestStunToStun(NAT_SYMMETRIC, NAT_ADDR_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToPRNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_PORT_RESTRICTED);
+}
+
+TEST_F(PortTest, TestSymNatToSymNat) {
+ // Will "fail"
+ TestStunToStun(NAT_SYMMETRIC, NAT_SYMMETRIC);
+}
+
+TEST_F(PortTest, TestSymNatToRelay) {
+ TestStunToRelay(NAT_SYMMETRIC, PROTO_UDP);
+}
+
+TEST_F(PortTest, TestSymNatToTcpRelay) {
+ TestStunToRelay(NAT_SYMMETRIC, PROTO_TCP);
+}
+
+// Outbound TCP -> XXXX
+TEST_F(PortTest, TestTcpToTcp) {
+ TestTcpToTcp();
+}
+
+/* TODO: Enable these once testrelayserver can accept external TCP.
+TEST_F(PortTest, TestTcpToTcpRelay) {
+ TestTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestTcpToSslTcpRelay) {
+ TestTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+// Outbound SSLTCP -> XXXX
+/* TODO: Enable these once testrelayserver can accept external SSL.
+TEST_F(PortTest, TestSslTcpToTcpRelay) {
+ TestSslTcpToRelay(PROTO_TCP);
+}
+
+TEST_F(PortTest, TestSslTcpToSslTcpRelay) {
+ TestSslTcpToRelay(PROTO_SSLTCP);
+}
+*/
+
+TEST_F(PortTest, TestTcpNoDelay) {
+ TCPPort* port1 = CreateTcpPort(kLocalAddr1);
+ int option_value = -1;
+ int success = port1->GetOption(talk_base::Socket::OPT_NODELAY,
+ &option_value);
+ ASSERT_EQ(0, success); // GetOption() should complete successfully w/ 0
+ ASSERT_EQ(1, option_value);
+ delete port1;
+}
+
+TEST_F(PortTest, TestDelayedBindingUdp) {
+ FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_udp_socket(socket);
+ scoped_ptr<UDPPort> port(
+ CreateUdpPort(kLocalAddr1, &socket_factory));
+
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ port->PrepareAddress();
+
+ EXPECT_EQ(0U, port->candidates().size());
+ socket->SignalAddressReady(socket, kLocalAddr2);
+
+ EXPECT_EQ(1U, port->candidates().size());
+}
+
+TEST_F(PortTest, TestDelayedBindingTcp) {
+ FakeAsyncPacketSocket *socket = new FakeAsyncPacketSocket();
+ FakePacketSocketFactory socket_factory;
+
+ socket_factory.set_next_server_tcp_socket(socket);
+ scoped_ptr<TCPPort> port(
+ CreateTcpPort(kLocalAddr1, &socket_factory));
+
+ socket->set_state(AsyncPacketSocket::STATE_BINDING);
+ port->PrepareAddress();
+
+ EXPECT_EQ(0U, port->candidates().size());
+ socket->SignalAddressReady(socket, kLocalAddr2);
+
+ EXPECT_EQ(1U, port->candidates().size());
+}
diff --git a/talk/p2p/base/portallocator.cc b/talk/p2p/base/portallocator.cc
new file mode 100644
index 0000000..974117f
--- /dev/null
+++ b/talk/p2p/base/portallocator.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/portallocator.h"
+
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+
+namespace cricket {
+
+PortAllocator::~PortAllocator() {
+ for (SessionMuxerMap::iterator iter = muxers_.begin();
+ iter != muxers_.end(); ++iter) {
+ delete iter->second;
+ }
+}
+
+PortAllocatorSession* PortAllocator::CreateSession(
+ const std::string& sid,
+ const std::string& name,
+ const std::string& session_type) {
+ if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) {
+ PortAllocatorSessionMuxer* muxer = GetSessionMuxer(sid);
+ if (!muxer) {
+ PortAllocatorSession* session_impl = CreateSession(name, session_type);
+ // Create PortAllocatorSessionMuxer object for |session_impl|.
+ muxer = new PortAllocatorSessionMuxer(session_impl);
+ muxer->SignalDestroyed.connect(
+ this, &PortAllocator::OnSessionMuxerDestroyed);
+ // Add PortAllocatorSession to the map.
+ muxers_[sid] = muxer;
+ }
+ PortAllocatorSessionProxy* proxy =
+ new PortAllocatorSessionProxy(name, session_type, flags_);
+ muxer->RegisterSessionProxy(proxy);
+ return proxy;
+ }
+ return CreateSession(name, session_type);
+}
+
+PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer(
+ const std::string& sid) const {
+ SessionMuxerMap::const_iterator iter = muxers_.find(sid);
+ if (iter != muxers_.end())
+ return iter->second;
+ return NULL;
+}
+
+void PortAllocator::OnSessionMuxerDestroyed(
+ PortAllocatorSessionMuxer* session) {
+ SessionMuxerMap::iterator iter;
+ for (iter = muxers_.begin(); iter != muxers_.end(); ++iter) {
+ if (iter->second == session)
+ break;
+ }
+ if (iter != muxers_.end())
+ muxers_.erase(iter);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h
index 175bdbc..79a08c1 100644
--- a/talk/p2p/base/portallocator.h
+++ b/talk/p2p/base/portallocator.h
@@ -47,18 +47,31 @@
const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+const uint32 PORTALLOCATOR_ENABLE_BUNDLE = 0x20;
const uint32 kDefaultPortAllocatorFlags = 0;
+class PortAllocatorSessionMuxer;
+
class PortAllocatorSession : public sigslot::has_slots<> {
public:
- explicit PortAllocatorSession(uint32 flags) : flags_(flags) {}
+ // TODO Remove session_type argument (and other places), as
+ // its not used.
+ explicit PortAllocatorSession(const std::string& name,
+ const std::string& session_type,
+ uint32 flags) :
+ name_(name),
+ session_type_(session_type),
+ flags_(flags) {
+ }
// Subclasses should clean up any ports created.
virtual ~PortAllocatorSession() {}
uint32 flags() const { return flags_; }
void set_flags(uint32 flags) { flags_ = flags; }
+ const std::string& name() const { return name_; }
+ const std::string& session_type() const { return session_type_; }
// Prepares an initial set of ports to try.
virtual void GetInitialPorts() = 0;
@@ -74,23 +87,33 @@
uint32 generation() { return generation_; }
void set_generation(uint32 generation) { generation_ = generation; }
+ sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
+
+ protected:
+ std::string name_;
+ std::string session_type_;
private:
uint32 flags_;
uint32 generation_;
};
-class PortAllocator {
+class PortAllocator : public sigslot::has_slots<> {
public:
PortAllocator() :
flags_(kDefaultPortAllocatorFlags),
min_port_(0),
max_port_(0) {
}
- virtual ~PortAllocator() {}
+ virtual ~PortAllocator();
- virtual PortAllocatorSession *CreateSession(const std::string &name,
- const std::string &session_type) = 0;
+ PortAllocatorSession* CreateSession(
+ const std::string& sid,
+ const std::string& name,
+ const std::string& session_type);
+
+ PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& sid) const;
+ void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session);
uint32 flags() const { return flags_; }
void set_flags(uint32 flags) { flags_ = flags; }
@@ -116,11 +139,18 @@
}
protected:
+ virtual PortAllocatorSession* CreateSession(const std::string &name,
+ const std::string &session_type) = 0;
+
+ typedef std::map<std::string, PortAllocatorSessionMuxer*> SessionMuxerMap;
+
uint32 flags_;
std::string agent_;
talk_base::ProxyInfo proxy_;
int min_port_;
int max_port_;
+
+ SessionMuxerMap muxers_;
};
} // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.cc b/talk/p2p/base/portallocatorsessionproxy.cc
new file mode 100644
index 0000000..8305cd3
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.cc
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/portproxy.h"
+
+namespace cricket {
+
+PortAllocatorSessionMuxer::PortAllocatorSessionMuxer(
+ PortAllocatorSession* session)
+ : session_(session) {
+ session_->SignalPortReady.connect(
+ this, &PortAllocatorSessionMuxer::OnPortReady);
+}
+
+PortAllocatorSessionMuxer::~PortAllocatorSessionMuxer() {
+ for (size_t i = 0; i < session_proxies_.size(); ++i)
+ delete session_proxies_[i];
+
+ SignalDestroyed(this);
+}
+
+void PortAllocatorSessionMuxer::RegisterSessionProxy(
+ PortAllocatorSessionProxy* session_proxy) {
+ session_proxies_.push_back(session_proxy);
+ session_proxy->SignalDestroyed.connect(
+ this, &PortAllocatorSessionMuxer::OnSessionProxyDestroyed);
+ session_proxy->set_impl(session_.get());
+}
+
+void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session,
+ Port* port) {
+ ASSERT(session == session_.get());
+ ports_.push_back(port);
+ port->SignalDestroyed.connect(
+ this, &PortAllocatorSessionMuxer::OnPortDestroyed);
+}
+
+void PortAllocatorSessionMuxer::OnPortDestroyed(Port* port) {
+ std::vector<Port*>::iterator it =
+ std::find(ports_.begin(), ports_.end(), port);
+ if (it != ports_.end())
+ ports_.erase(it);
+}
+
+void PortAllocatorSessionMuxer::OnSessionProxyDestroyed(
+ PortAllocatorSession* proxy) {
+ std::vector<PortAllocatorSessionProxy*>::iterator it =
+ std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+ if (it != session_proxies_.end())
+ session_proxies_.erase(it);
+
+ if (session_proxies_.empty()) {
+ // Destroy PortAllocatorSession and its associated muxer object if all
+ // proxies belonging to this session are already destroyed.
+ delete this;
+ }
+}
+
+PortAllocatorSessionProxy::~PortAllocatorSessionProxy() {
+ std::map<Port*, PortProxy*>::iterator it;
+ for (it = proxy_ports_.begin(); it != proxy_ports_.end(); it++)
+ delete it->second;
+
+ SignalDestroyed(this);
+}
+
+void PortAllocatorSessionProxy::set_impl(
+ PortAllocatorSession* session) {
+ impl_ = session;
+
+ impl_->SignalCandidatesReady.connect(
+ this, &PortAllocatorSessionProxy::OnCandidatesReady);
+ impl_->SignalPortReady.connect(
+ this, &PortAllocatorSessionProxy::OnPortReady);
+}
+
+void PortAllocatorSessionProxy::GetInitialPorts() {
+ ASSERT(impl_ != NULL);
+ impl_->GetInitialPorts();
+}
+
+void PortAllocatorSessionProxy::StartGetAllPorts() {
+ ASSERT(impl_ != NULL);
+ impl_->StartGetAllPorts();
+}
+
+void PortAllocatorSessionProxy::StopGetAllPorts() {
+ ASSERT(impl_ != NULL);
+ impl_->StartGetAllPorts();
+}
+
+bool PortAllocatorSessionProxy::IsGettingAllPorts() {
+ ASSERT(impl_ != NULL);
+ return impl_->IsGettingAllPorts();
+}
+
+void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session,
+ Port* port) {
+ ASSERT(session == impl_);
+
+ PortProxy* proxy_port = new PortProxy(
+ port->thread(), port->type(), port->socket_factory(), port->network(),
+ port->ip(), port->min_port(), port->max_port());
+ proxy_port->set_impl(port);
+ proxy_ports_[port] = proxy_port;
+ SignalPortReady(this, proxy_port);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesReady(
+ PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates) {
+ ASSERT(session == impl_);
+
+ // Since all proxy sessions share a common PortAllocatorSession,
+ // all Candidates will have name associated with the common PAS.
+ // Change Candidate name with the PortAllocatorSessionProxy name.
+ std::vector<Candidate> our_candidates;
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ Candidate new_local_candidate = candidates[i];
+ new_local_candidate.set_name(name_);
+ our_candidates.push_back(new_local_candidate);
+ }
+
+ SignalCandidatesReady(this, our_candidates);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.h b/talk/p2p/base/portallocatorsessionproxy.h
new file mode 100644
index 0000000..5f9eaec
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.h
@@ -0,0 +1,101 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+#define TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+
+#include <string>
+
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+class PortAllocator;
+class PortAllocatorSessionProxy;
+class PortProxy;
+
+// This class maintains the list of cricket::Port* objects. Ports will be
+// deleted upon receiving SignalDestroyed signal. This class is used when
+// PORTALLOCATOR_ENABLE_BUNDLE flag is set.
+
+class PortAllocatorSessionMuxer : public sigslot::has_slots<> {
+ public:
+ explicit PortAllocatorSessionMuxer(PortAllocatorSession* session);
+ virtual ~PortAllocatorSessionMuxer();
+
+ void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy);
+
+ void OnPortReady(PortAllocatorSession* session, Port* port);
+ void OnPortDestroyed(Port* port);
+
+ const std::vector<Port*>& ports() { return ports_; }
+
+ sigslot::signal1<PortAllocatorSessionMuxer*> SignalDestroyed;
+
+ private:
+ void OnSessionProxyDestroyed(PortAllocatorSession* proxy);
+
+ // Port will be deleted when SignalDestroyed received, otherwise delete
+ // happens when PortAllocatorSession dtor is called.
+ std::vector<Port*> ports_;
+ talk_base::scoped_ptr<PortAllocatorSession> session_;
+ std::vector<PortAllocatorSessionProxy*> session_proxies_;
+};
+
+class PortAllocatorSessionProxy : public PortAllocatorSession {
+ public:
+ PortAllocatorSessionProxy(const std::string& name,
+ const std::string& session_type,
+ uint32 flags)
+ : PortAllocatorSession(name, session_type, flags),
+ impl_(NULL) {}
+
+ virtual ~PortAllocatorSessionProxy();
+
+ PortAllocatorSession* impl() { return impl_; }
+ void set_impl(PortAllocatorSession* session);
+
+ // Forwards call to the actual PortAllocatorSession.
+ virtual void GetInitialPorts();
+ virtual void StartGetAllPorts();
+ virtual void StopGetAllPorts();
+ virtual bool IsGettingAllPorts();
+
+ private:
+ void OnPortReady(PortAllocatorSession* session, Port* port);
+ void OnCandidatesReady(PortAllocatorSession* session,
+ const std::vector<Candidate>& candidates);
+ void OnPortDestroyed(Port* port);
+
+ // This is the actual PortAllocatorSession, owned by PortAllocator.
+ PortAllocatorSession* impl_;
+ std::map<Port*, PortProxy*> proxy_ports_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
diff --git a/talk/p2p/base/portproxy.cc b/talk/p2p/base/portproxy.cc
new file mode 100644
index 0000000..6c20127
--- /dev/null
+++ b/talk/p2p/base/portproxy.cc
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/base/portproxy.h"
+
+namespace cricket {
+
+void PortProxy::set_impl(Port* port) {
+ impl_ = port;
+ impl_->SignalUnknownAddress.connect(
+ this, &PortProxy::OnUnknownAddress);
+ impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed);
+}
+
+void PortProxy::PrepareAddress() {
+ impl_->PrepareAddress();
+}
+
+Connection* PortProxy::CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) {
+ ASSERT(impl_ != NULL);
+ return impl_->CreateConnection(remote_candidate, origin);
+}
+
+int PortProxy::SendTo(const void* data,
+ size_t size,
+ const talk_base::SocketAddress& addr,
+ bool payload) {
+ ASSERT(impl_ != NULL);
+ return impl_->SendTo(data, size, addr, payload);
+}
+
+int PortProxy::SetOption(talk_base::Socket::Option opt,
+ int value) {
+ ASSERT(impl_ != NULL);
+ return impl_->SetOption(opt, value);
+}
+
+int PortProxy::GetError() {
+ ASSERT(impl_ != NULL);
+ return impl_->GetError();
+}
+
+void PortProxy::OnUnknownAddress(
+ Port *port,
+ const talk_base::SocketAddress &addr,
+ StunMessage *stun_msg,
+ const std::string &remote_username,
+ bool port_muxed) {
+ ASSERT(port == impl_);
+ ASSERT(!port_muxed);
+ SignalUnknownAddress(this, addr, stun_msg, remote_username, true);
+}
+
+void PortProxy::OnPortDestroyed(Port* port) {
+ ASSERT(port == impl_);
+ // |port| will be destroyed in PortAllocatorSessionMuxer.
+ SignalDestroyed(this);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/portproxy.h b/talk/p2p/base/portproxy.h
new file mode 100644
index 0000000..49707d4
--- /dev/null
+++ b/talk/p2p/base/portproxy.h
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PORTPROXY_H_
+#define TALK_P2P_BASE_PORTPROXY_H_
+
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class PortProxy : public Port {
+ public:
+ PortProxy(talk_base::Thread* thread, const std::string& type,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ const talk_base::IPAddress& ip, int min_port, int max_port)
+ : Port(thread, type, factory, network, ip, min_port, max_port) {
+ }
+ virtual ~PortProxy() {}
+
+ Port* impl() { return impl_; }
+ void set_impl(Port* port);
+
+ // Forwards call to the actual Port.
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin);
+ virtual int SendTo(
+ const void* data, size_t size, const talk_base::SocketAddress& addr,
+ bool payload);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ virtual void SendBindingResponse(StunMessage* request,
+ const talk_base::SocketAddress& addr) {
+ impl_->SendBindingResponse(request, addr);
+ }
+
+ virtual Connection* GetConnection(
+ const talk_base::SocketAddress& remote_addr) {
+ return impl_->GetConnection(remote_addr);
+ }
+
+ virtual void SendBindingErrorResponse(
+ StunMessage* request, const talk_base::SocketAddress& addr,
+ int error_code, const std::string& reason) {
+ impl_->SendBindingErrorResponse(request, addr, error_code, reason);
+ }
+
+ private:
+ void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
+ StunMessage *stun_msg,
+ const std::string &remote_username,
+ bool port_muxed);
+ void OnPortDestroyed(Port* port);
+ Port* impl_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PORTPROXY_H_
diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc
index fc903e0..99a1f43 100644
--- a/talk/p2p/base/pseudotcp.cc
+++ b/talk/p2p/base/pseudotcp.cc
@@ -38,7 +38,7 @@
#include "talk/base/logging.h"
#include "talk/base/socket.h"
#include "talk/base/stringutils.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
// The following logging is for detailed (packet-level) analysis only.
#define _DBG_NONE 0
diff --git a/talk/p2p/base/pseudotcp_unittest.cc b/talk/p2p/base/pseudotcp_unittest.cc
new file mode 100644
index 0000000..7bf5874
--- /dev/null
+++ b/talk/p2p/base/pseudotcp_unittest.cc
@@ -0,0 +1,845 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/pseudotcp.h"
+
+using cricket::PseudoTcp;
+
+static const int kConnectTimeoutMs = 10000; // ~3 * default RTO of 3000ms
+static const int kTransferTimeoutMs = 15000;
+static const int kBlockSize = 4096;
+
+class PseudoTcpForTest : public cricket::PseudoTcp {
+ public:
+ PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32 conv)
+ : PseudoTcp(notify, conv) {
+ }
+
+ bool isReceiveBufferFull() const {
+ return PseudoTcp::isReceiveBufferFull();
+ }
+
+ void disableWindowScale() {
+ PseudoTcp::disableWindowScale();
+ }
+};
+
+class PseudoTcpTestBase : public testing::Test,
+ public talk_base::MessageHandler,
+ public cricket::IPseudoTcpNotify {
+ public:
+ PseudoTcpTestBase()
+ : local_(this, 1),
+ remote_(this, 1),
+ have_connected_(false),
+ have_disconnected_(false),
+ local_mtu_(65535),
+ remote_mtu_(65535),
+ delay_(0),
+ loss_(0) {
+ // Set use of the test RNG to get predictable loss patterns.
+ talk_base::SetRandomTestMode(true);
+ }
+ ~PseudoTcpTestBase() {
+ // Put it back for the next test.
+ talk_base::SetRandomTestMode(false);
+ }
+ void SetLocalMtu(int mtu) {
+ local_.NotifyMTU(mtu);
+ local_mtu_ = mtu;
+ }
+ void SetRemoteMtu(int mtu) {
+ remote_.NotifyMTU(mtu);
+ remote_mtu_ = mtu;
+ }
+ void SetDelay(int delay) {
+ delay_ = delay;
+ }
+ void SetLoss(int percent) {
+ loss_ = percent;
+ }
+ void SetOptNagling(bool enable_nagles) {
+ local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
+ }
+ void SetOptAckDelay(int ack_delay) {
+ local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
+ }
+ void SetOptSndBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ remote_.SetOption(PseudoTcp::OPT_SNDBUF, size);
+ }
+ void SetRemoteOptRcvBuf(int size) {
+ remote_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void SetLocalOptRcvBuf(int size) {
+ local_.SetOption(PseudoTcp::OPT_RCVBUF, size);
+ }
+ void DisableRemoteWindowScale() {
+ remote_.disableWindowScale();
+ }
+ void DisableLocalWindowScale() {
+ local_.disableWindowScale();
+ }
+
+ protected:
+ int Connect() {
+ int ret = local_.Connect();
+ if (ret == 0) {
+ UpdateLocalClock();
+ }
+ return ret;
+ }
+ void Close() {
+ local_.Close(false);
+ UpdateLocalClock();
+ }
+
+ enum { MSG_LPACKET, MSG_RPACKET, MSG_LCLOCK, MSG_RCLOCK, MSG_IOCOMPLETE,
+ MSG_WRITE};
+ virtual void OnTcpOpen(PseudoTcp* tcp) {
+ // Consider ourselves connected when the local side gets OnTcpOpen.
+ // OnTcpWriteable isn't fired at open, so we trigger it now.
+ LOG(LS_VERBOSE) << "Opened";
+ if (tcp == &local_) {
+ have_connected_ = true;
+ OnTcpWriteable(tcp);
+ }
+ }
+ // Test derived from the base should override
+ // virtual void OnTcpReadable(PseudoTcp* tcp)
+ // and
+ // virtual void OnTcpWritable(PseudoTcp* tcp)
+ virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) {
+ // Consider ourselves closed when the remote side gets OnTcpClosed.
+ // TODO: OnTcpClosed is only ever notified in case of error in
+ // the current implementation. Solicited close is not (yet) supported.
+ LOG(LS_VERBOSE) << "Closed";
+ EXPECT_EQ(0U, error);
+ if (tcp == &remote_) {
+ have_disconnected_ = true;
+ }
+ }
+ virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer, size_t len) {
+ // Randomly drop the desired percentage of packets.
+ // Also drop packets that are larger than the configured MTU.
+ if (talk_base::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
+ LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
+ } else if (len > static_cast<size_t>(
+ talk_base::_min(local_mtu_, remote_mtu_))) {
+ LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size=" << len;
+ } else {
+ int id = (tcp == &local_) ? MSG_RPACKET : MSG_LPACKET;
+ std::string packet(buffer, len);
+ talk_base::Thread::Current()->PostDelayed(delay_, this, id,
+ talk_base::WrapMessageData(packet));
+ }
+ return WR_SUCCESS;
+ }
+
+ void UpdateLocalClock() { UpdateClock(&local_, MSG_LCLOCK); }
+ void UpdateRemoteClock() { UpdateClock(&remote_, MSG_RCLOCK); }
+ void UpdateClock(PseudoTcp* tcp, uint32 message) {
+ long interval; // NOLINT
+ tcp->GetNextClock(PseudoTcp::Now(), interval);
+ interval = talk_base::_max<int>(interval, 0L); // sometimes interval is < 0
+ talk_base::Thread::Current()->Clear(this, message);
+ talk_base::Thread::Current()->PostDelayed(interval, this, message);
+ }
+
+ virtual void OnMessage(talk_base::Message* message) {
+ switch (message->message_id) {
+ case MSG_LPACKET: {
+ const std::string& s(
+ talk_base::UseMessageData<std::string>(message->pdata));
+ local_.NotifyPacket(s.c_str(), s.size());
+ UpdateLocalClock();
+ break;
+ }
+ case MSG_RPACKET: {
+ const std::string& s(
+ talk_base::UseMessageData<std::string>(message->pdata));
+ remote_.NotifyPacket(s.c_str(), s.size());
+ UpdateRemoteClock();
+ break;
+ }
+ case MSG_LCLOCK:
+ local_.NotifyClock(PseudoTcp::Now());
+ UpdateLocalClock();
+ break;
+ case MSG_RCLOCK:
+ remote_.NotifyClock(PseudoTcp::Now());
+ UpdateRemoteClock();
+ break;
+ default:
+ break;
+ }
+ delete message->pdata;
+ }
+
+ PseudoTcpForTest local_;
+ PseudoTcpForTest remote_;
+ talk_base::MemoryStream send_stream_;
+ talk_base::MemoryStream recv_stream_;
+ bool have_connected_;
+ bool have_disconnected_;
+ int local_mtu_;
+ int remote_mtu_;
+ int delay_;
+ int loss_;
+};
+
+class PseudoTcpTest : public PseudoTcpTestBase {
+ public:
+ void TestTransfer(int size) {
+ uint32 start, elapsed;
+ size_t received;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = talk_base::Time();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and complete when all data has
+ // been received.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = talk_base::TimeSince(start);
+ recv_stream_.GetSize(&received);
+ // Ensure we closed down OK and we got the right data.
+ // TODO: Ensure the errors are cleared properly.
+ //EXPECT_EQ(0, local_.GetError());
+ //EXPECT_EQ(0, remote_.GetError());
+ EXPECT_EQ(static_cast<size_t>(size), received);
+ EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+ recv_stream_.GetBuffer(), size));
+ LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed
+ << " ms (" << size * 8 / elapsed << " Kbps)";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ // Stream bytes to the recv stream as they arrive.
+ if (tcp == &remote_) {
+ ReadData();
+
+ // TODO: OnTcpClosed() is currently only notified on error -
+ // there is no on-the-wire equivalent of TCP FIN.
+ // So we fake the notification when all the data has been read.
+ size_t received, required;
+ recv_stream_.GetPosition(&received);
+ send_stream_.GetSize(&required);
+ if (received == required)
+ OnTcpClosed(&remote_, 0);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ if (tcp == &local_) {
+ LOG(LS_VERBOSE) << "Flow Control Lifted";
+ bool done;
+ WriteData(&done);
+ if (done) {
+ Close();
+ }
+ }
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData(bool* done) {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+ talk_base::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = tosend = 0;
+ }
+ } while (sent > 0);
+ *done = (tosend == 0);
+ }
+
+ private:
+ talk_base::MemoryStream send_stream_;
+ talk_base::MemoryStream recv_stream_;
+};
+
+
+class PseudoTcpTestPingPong : public PseudoTcpTestBase {
+ public:
+ PseudoTcpTestPingPong()
+ : iterations_remaining_(0),
+ sender_(NULL),
+ receiver_(NULL),
+ bytes_per_send_(0) {
+ }
+ void SetBytesPerSend(int bytes) {
+ bytes_per_send_ = bytes;
+ }
+ void TestPingPong(int size, int iterations) {
+ uint32 start, elapsed;
+ iterations_remaining_ = iterations;
+ receiver_ = &remote_;
+ sender_ = &local_;
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Connect and wait until connected.
+ start = talk_base::Time();
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+ // Sending will start from OnTcpWriteable and stop when the required
+ // number of iterations have completed.
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+ elapsed = talk_base::TimeSince(start);
+ LOG(LS_INFO) << "Performed " << iterations << " pings in "
+ << elapsed << " ms";
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ if (tcp != receiver_) {
+ LOG_F(LS_ERROR) << "unexpected OnTcpReadable";
+ return;
+ }
+ // Stream bytes to the recv stream as they arrive.
+ ReadData();
+ // If we've received the desired amount of data, rewind things
+ // and send it back the other way!
+ size_t position, desired;
+ recv_stream_.GetPosition(&position);
+ send_stream_.GetSize(&desired);
+ if (position == desired) {
+ if (receiver_ == &local_ && --iterations_remaining_ == 0) {
+ Close();
+ // TODO: Fake OnTcpClosed() on the receiver for now.
+ OnTcpClosed(&remote_, 0);
+ return;
+ }
+ PseudoTcp* tmp = receiver_;
+ receiver_ = sender_;
+ sender_ = tmp;
+ recv_stream_.Rewind();
+ send_stream_.Rewind();
+ OnTcpWriteable(sender_);
+ }
+ }
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ if (tcp != sender_)
+ return;
+ // Write bytes from the send stream when we can.
+ // Shut down when we've sent everything.
+ LOG(LS_VERBOSE) << "Flow Control Lifted";
+ WriteData();
+ }
+
+ void ReadData() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+ do {
+ rcvd = receiver_->Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+ }
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block);
+ if (send_stream_.Read(block, tosend, &tosend, NULL) !=
+ talk_base::SR_EOS) {
+ sent = sender_->Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = tosend = 0;
+ }
+ } while (sent > 0);
+ }
+
+ private:
+ int iterations_remaining_;
+ PseudoTcp* sender_;
+ PseudoTcp* receiver_;
+ int bytes_per_send_;
+};
+
+// Fill the receiver window until it is full, drain it and then
+// fill it with the same amount. This is to test that receiver window
+// contracts and enlarges correctly.
+class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase {
+ public:
+ // Not all the data are transfered, |size| just need to be big enough
+ // to fill up the receiver window twice.
+ void TestTransfer(int size) {
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+
+ // Connect and wait until connected.
+ EXPECT_EQ(0, Connect());
+ EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
+
+ talk_base::Thread::Current()->Post(this, MSG_WRITE);
+ EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
+
+ ASSERT_EQ(2u, send_position_.size());
+ ASSERT_EQ(2u, recv_position_.size());
+
+ const size_t estimated_recv_window = recv_position_[0];
+
+ // The difference in consecutive send positions should equal the
+ // receive window size or match very closely. This verifies that receive
+ // window is open after receiver drained all the data.
+ const size_t send_position_diff = send_position_[1] - send_position_[0];
+ EXPECT_GE(1024u, estimated_recv_window - send_position_diff);
+
+ // Receiver drained the receive window twice.
+ EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]);
+ }
+
+ virtual void OnMessage(talk_base::Message* message) {
+ int message_id = message->message_id;
+ PseudoTcpTestBase::OnMessage(message);
+
+ switch (message_id) {
+ case MSG_WRITE: {
+ WriteData();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ uint32 EstimateReceiveWindowSize() const {
+ return recv_position_[0];
+ }
+
+ uint32 EstimateSendWindowSize() const {
+ return send_position_[0] - recv_position_[0];
+ }
+
+ private:
+ // IPseudoTcpNotify interface
+ virtual void OnTcpReadable(PseudoTcp* tcp) {
+ }
+
+ virtual void OnTcpWriteable(PseudoTcp* tcp) {
+ }
+
+ void ReadUntilIOPending() {
+ char block[kBlockSize];
+ size_t position;
+ int rcvd;
+
+ do {
+ rcvd = remote_.Recv(block, sizeof(block));
+ if (rcvd != -1) {
+ recv_stream_.Write(block, rcvd, NULL, NULL);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Received: " << position;
+ }
+ } while (rcvd > 0);
+
+ recv_stream_.GetPosition(&position);
+ recv_position_.push_back(position);
+
+ // Disconnect if we have done two transfers.
+ if (recv_position_.size() == 2u) {
+ Close();
+ OnTcpClosed(&remote_, 0);
+ } else {
+ WriteData();
+ }
+ }
+
+ void WriteData() {
+ size_t position, tosend;
+ int sent;
+ char block[kBlockSize];
+ do {
+ send_stream_.GetPosition(&position);
+ if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
+ talk_base::SR_EOS) {
+ sent = local_.Send(block, tosend);
+ UpdateLocalClock();
+ if (sent != -1) {
+ send_stream_.SetPosition(position + sent);
+ LOG(LS_VERBOSE) << "Sent: " << position + sent;
+ } else {
+ send_stream_.SetPosition(position);
+ LOG(LS_VERBOSE) << "Flow Controlled";
+ }
+ } else {
+ sent = tosend = 0;
+ }
+ } while (sent > 0);
+
+ if (remote_.isReceiveBufferFull()) {
+ send_stream_.GetPosition(&position);
+ send_position_.push_back(position);
+
+ // Drain the receiver buffer.
+ ReadUntilIOPending();
+ } else {
+ // If the receiver side is not full then keep writing.
+ talk_base::Thread::Current()->PostDelayed(10, this, MSG_WRITE);
+ }
+ }
+
+ private:
+ talk_base::MemoryStream send_stream_;
+ talk_base::MemoryStream recv_stream_;
+
+ std::vector<size_t> send_position_;
+ std::vector<size_t> recv_position_;
+};
+
+// Basic end-to-end data transfer tests
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(PseudoTcpTest, TestSend) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestTransfer(1000000);
+}
+
+// Test sending data with a 50 ms RTT. Transmission should take longer due
+// to a slower ramp-up in send rate.
+TEST_F(PseudoTcpTest, TestSendWithDelay) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ TestTransfer(1000000);
+}
+
+// Test sending data with packet loss. Transmission should take much longer due
+// to send back-off when loss occurs.
+TEST_F(PseudoTcpTest, TestSendWithLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should
+// take much longer due to send back-off and slower detection of loss.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetLoss(10);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Nagling disabled. Transmission
+// should take about the same time as with Nagling enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 10% packet loss and Delayed ACK disabled.
+// Transmission should be slightly faster than with it enabled.
+TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLoss(10);
+ SetOptAckDelay(0);
+ TestTransfer(100000);
+}
+
+// Test sending data with 50ms delay and Nagling disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptNagling(false);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test sending data with 50ms delay and Delayed ACK disabled.
+TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetDelay(50);
+ SetOptAckDelay(0);
+ TestTransfer(100000); // less data so test runs faster
+}
+
+// Test a large receive buffer with a sender that doesn't support scaling.
+TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetLocalOptRcvBuf(100000);
+ DisableRemoteWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test a large sender-side receive buffer with a receiver that doesn't support
+// scaling.
+TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ DisableLocalWindowScale();
+ TestTransfer(1000000);
+}
+
+// Test when both sides use window scaling.
+TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1000000);
+}
+
+// Test using a large window scale value.
+TEST_F(PseudoTcpTest, TestSendLargeInFlight) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ SetOptSndBuf(150000);
+ TestTransfer(1000000);
+}
+
+TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(1000000);
+ SetLocalOptRcvBuf(1000000);
+ TestTransfer(10000000);
+}
+
+// Test using a small receive buffer.
+TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(10000);
+ SetLocalOptRcvBuf(10000);
+ TestTransfer(1000000);
+}
+
+// Test using a very small receive buffer.
+TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetRemoteOptRcvBuf(100);
+ SetLocalOptRcvBuf(100);
+ TestTransfer(100000);
+}
+
+// Ping-pong (request/response) tests
+
+// Test sending <= 1x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(100, 100);
+}
+
+// Test sending 2x-3x MTU of data in each ping/pong. Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(400, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong.
+// Should take ~1s, due to interaction between Nagling and Delayed ACK.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ TestPingPong(2000, 5);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(0);
+ TestPingPong(2000, 100);
+}
+
+// Test sending 1x-2x MTU of data in each ping/pong with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ TestPingPong(2000, 5);
+}
+
+// Test sending a ping as pair of short (non-full) segments.
+// Should take ~1s, due to Delayed ACK interaction with Nagling.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptAckDelay(5000);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending ping as a pair of short (non-full) segments, with Nagling off.
+// Should take <10ms.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ TestPingPong(100, 5);
+}
+
+// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK.
+// Should take ~1s.
+TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetBytesPerSend(50); // i.e. two Send calls per payload
+ SetOptAckDelay(0);
+ TestPingPong(100, 5);
+}
+
+// Test that receive window expands and contract correctly.
+TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ TestTransfer(1024 * 1000);
+}
+
+// Test setting send window size to a very small value.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetOptSndBuf(900);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(900u, EstimateSendWindowSize());
+}
+
+// Test setting receive window size to a value other than default.
+TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1500);
+ SetOptNagling(false);
+ SetOptAckDelay(0);
+ SetRemoteOptRcvBuf(100000);
+ SetLocalOptRcvBuf(100000);
+ TestTransfer(1024 * 1000);
+ EXPECT_EQ(100000u, EstimateReceiveWindowSize());
+}
+
+/* Test sending data with mismatched MTUs. We should detect this and reduce
+// our packet size accordingly.
+// TODO: This doesn't actually work right now. The current code
+// doesn't detect if the MTU is set too high on either side.
+TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) {
+ SetLocalMtu(1500);
+ SetRemoteMtu(1280);
+ TestTransfer(1000000);
+}
+*/
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
index 8736053..0c2691c 100644
--- a/talk/p2p/base/rawtransportchannel.cc
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -95,7 +95,8 @@
void RawTransportChannel::Connect() {
// Create an allocator that only returns stun and relay ports.
- allocator_session_ = allocator_->CreateSession(name(), content_type());
+ allocator_session_ = allocator_->CreateSession(
+ session_id(), name(), content_type());
uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
index 76bd5ff..c6c066a 100644
--- a/talk/p2p/base/relayport.cc
+++ b/talk/p2p/base/relayport.cc
@@ -187,9 +187,9 @@
RelayPort::RelayPort(
talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip, int min_port, int max_port,
- const std::string& username, const std::string& password,
- const std::string& magic_cookie)
+ talk_base::Network* network, const talk_base::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password, const std::string& magic_cookie)
: Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port),
ready_(false),
magic_cookie_(magic_cookie),
@@ -554,7 +554,7 @@
StunAddressAttribute* addr_attr =
StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
- addr_attr->SetIP(addr.ip());
+ addr_attr->SetIP(addr.ipaddr());
addr_attr->SetPort(addr.port());
request.AddAttribute(addr_attr);
@@ -702,7 +702,7 @@
return;
}
- talk_base::SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port());
+ talk_base::SocketAddress remote_addr2(addr_attr->ipaddr(), addr_attr->port());
const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
if (!data_attr) {
@@ -764,7 +764,7 @@
} else if (addr_attr->family() != 1) {
LOG(INFO) << "Mapped address has bad family";
} else {
- talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+ talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
entry_->OnConnect(addr, connection_);
}
diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h
index 62bb758..2137606 100644
--- a/talk/p2p/base/relayport.h
+++ b/talk/p2p/base/relayport.h
@@ -55,9 +55,9 @@
// RelayPort doesn't yet do anything fancy in the ctor.
static RelayPort* Create(
talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip, int min_port, int max_port,
- const std::string& username, const std::string& password,
- const std::string& magic_cookie) {
+ talk_base::Network* network, const talk_base::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password, const std::string& magic_cookie) {
return new RelayPort(thread, factory, network, ip, min_port, max_port,
username, password, magic_cookie);
}
@@ -85,9 +85,9 @@
protected:
RelayPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network*, uint32 ip, int min_port, int max_port,
- const std::string& username, const std::string& password,
- const std::string& magic_cookie);
+ talk_base::Network*, const talk_base::IPAddress& ip,
+ int min_port, int max_port, const std::string& username,
+ const std::string& password, const std::string& magic_cookie);
bool Init();
void SetReady();
diff --git a/talk/p2p/base/relayport_unittest.cc b/talk/p2p/base/relayport_unittest.cc
new file mode 100644
index 0000000..688e635
--- /dev/null
+++ b/talk/p2p/base/relayport_unittest.cc
@@ -0,0 +1,292 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/logging.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/relayserver.h"
+
+using talk_base::SocketAddress;
+
+static const SocketAddress kLocalAddress = SocketAddress("192.168.1.2", 0);
+static const SocketAddress kRelayUdpAddr = SocketAddress("99.99.99.1", 5000);
+static const SocketAddress kRelayTcpAddr = SocketAddress("99.99.99.2", 5001);
+static const SocketAddress kRelaySslAddr = SocketAddress("99.99.99.3", 443);
+static const SocketAddress kRelayExtAddr = SocketAddress("99.99.99.3", 5002);
+
+static const int kTimeoutMs = 1000;
+static const int kMaxTimeoutMs = 5000;
+
+// Tests connecting a RelayPort to a fake relay server
+// (cricket::RelayServer) using all currently available protocols. The
+// network layer is faked out by using a VirtualSocketServer for
+// creating sockets. The test will monitor the current state of the
+// RelayPort and created sockets by listening for signals such as,
+// SignalConnectFailure, SignalConnectTimeout, SignalSocketClosed and
+// SignalReadPacket.
+class RelayPortTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ RelayPortTest()
+ : main_(talk_base::Thread::Current()),
+ physical_socket_server_(new talk_base::PhysicalSocketServer),
+ virtual_socket_server_(new talk_base::VirtualSocketServer(
+ physical_socket_server_.get())),
+ ss_scope_(virtual_socket_server_.get()),
+ network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+ socket_factory_(talk_base::Thread::Current()),
+ username_(talk_base::CreateRandomString(16)),
+ password_(talk_base::CreateRandomString(16)),
+ relay_port_(cricket::RelayPort::Create(main_, &socket_factory_,
+ &network_,
+ kLocalAddress.ipaddr(),
+ 0, 0, username_, password_, "")),
+ relay_server_(new cricket::RelayServer(main_)) {
+ }
+
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ received_packet_count_[socket]++;
+ }
+
+ void OnConnectFailure(const cricket::ProtocolAddress* addr) {
+ failed_connections_.push_back(*addr);
+ }
+
+ void OnSoftTimeout(const cricket::ProtocolAddress* addr) {
+ soft_timedout_connections_.push_back(*addr);
+ }
+
+ protected:
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ talk_base::InitRandom(NULL, 0);
+ }
+
+ virtual void SetUp() {
+ // The relay server needs an external socket to work properly.
+ talk_base::AsyncUDPSocket* ext_socket =
+ CreateAsyncUdpSocket(kRelayExtAddr);
+ relay_server_->AddExternalSocket(ext_socket);
+
+ // Listen for failures.
+ relay_port_->SignalConnectFailure.
+ connect(this, &RelayPortTest::OnConnectFailure);
+
+ // Listen for soft timeouts.
+ relay_port_->SignalSoftTimeout.
+ connect(this, &RelayPortTest::OnSoftTimeout);
+ }
+
+ // Udp has the highest 'goodness' value of the three different
+ // protocols used for connecting to the relay server. As soon as
+ // PrepareAddress is called, the RelayPort will start trying to
+ // connect to the given UDP address. As soon as a response to the
+ // sent STUN allocate request message has been received, the
+ // RelayPort will consider the connection to be complete and will
+ // abort any other connection attempts.
+ void TestConnectUdp() {
+ // Add a UDP socket to the relay server.
+ talk_base::AsyncUDPSocket* internal_udp_socket =
+ CreateAsyncUdpSocket(kRelayUdpAddr);
+ talk_base::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+
+ relay_server_->AddInternalSocket(internal_udp_socket);
+ relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+ // Now add our relay addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP));
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+ relay_port_->PrepareAddress();
+
+ // Should be connected.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kTimeoutMs);
+
+ // Make sure that we are happy with UDP, ie. not continuing with
+ // TCP, SSLTCP, etc.
+ WAIT(relay_server_->HasConnection(kRelayTcpAddr), kTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the UDP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelayUdpAddr));
+ }
+
+ // TCP has the second best 'goodness' value, and as soon as UDP
+ // connection has failed, the RelayPort will attempt to connect via
+ // TCP. Here we add a fake UDP address together with a real TCP
+ // address to simulate an UDP failure. As soon as UDP has failed the
+ // RelayPort will try the TCP adress and succed.
+ void TestConnectTcp() {
+ // Create a fake UDP address for relay port to simulate a failure.
+ cricket::ProtocolAddress fake_protocol_address =
+ cricket::ProtocolAddress(kRelayUdpAddr, cricket::PROTO_UDP);
+
+ // Create a server socket for the RelayServer.
+ talk_base::AsyncSocket* server_socket = CreateServerSocket(kRelayTcpAddr);
+ relay_server_->AddInternalServerSocket(server_socket, cricket::PROTO_TCP);
+
+ // Add server addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(fake_protocol_address));
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP));
+ relay_port_->PrepareAddress();
+
+ EXPECT_FALSE(relay_port_->IsReady());
+
+ // Should have timed out in 200 + 200 + 400 + 800 + 1600 ms.
+ EXPECT_TRUE_WAIT(HasFailed(&fake_protocol_address), 3600);
+
+ // Wait until relayport is ready.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the TCP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelayTcpAddr));
+ }
+
+ void TestConnectSslTcp() {
+ // Create a fake TCP address for relay port to simulate a failure.
+ // We skip UDP here since transition from UDP to TCP has been
+ // tested above.
+ cricket::ProtocolAddress fake_protocol_address =
+ cricket::ProtocolAddress(kRelayTcpAddr, cricket::PROTO_TCP);
+
+ // Create a ssl server socket for the RelayServer.
+ talk_base::AsyncSocket* ssl_server_socket =
+ CreateServerSocket(kRelaySslAddr);
+ relay_server_->AddInternalServerSocket(ssl_server_socket,
+ cricket::PROTO_SSLTCP);
+
+ // Create a tcp server socket that listens on the fake address so
+ // the relay port can attempt to connect to it.
+ talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_server_socket(
+ CreateServerSocket(kRelayTcpAddr));
+
+ // Add server addresses to the relay port and let it start.
+ relay_port_->AddServerAddress(fake_protocol_address);
+ relay_port_->AddServerAddress(
+ cricket::ProtocolAddress(kRelaySslAddr, cricket::PROTO_SSLTCP));
+ relay_port_->PrepareAddress();
+ EXPECT_FALSE(relay_port_->IsReady());
+
+ // Should have timed out in 3000 ms(relayport.cc, kSoftConnectTimeoutMs).
+ EXPECT_TRUE_WAIT_MARGIN(HasTimedOut(&fake_protocol_address), 3000, 100);
+
+ // Wait until relayport is ready.
+ EXPECT_TRUE_WAIT(relay_port_->IsReady(), kMaxTimeoutMs);
+
+ // Should have only one connection.
+ EXPECT_EQ(1, relay_server_->GetConnectionCount());
+
+ // Should be the SSLTCP address.
+ EXPECT_TRUE(relay_server_->HasConnection(kRelaySslAddr));
+ }
+
+ private:
+ talk_base::AsyncUDPSocket* CreateAsyncUdpSocket(const SocketAddress addr) {
+ talk_base::AsyncSocket* socket =
+ virtual_socket_server_->CreateAsyncSocket(SOCK_DGRAM);
+ talk_base::AsyncUDPSocket* packet_socket =
+ talk_base::AsyncUDPSocket::Create(socket, addr);
+ EXPECT_TRUE(packet_socket != NULL);
+ packet_socket->SignalReadPacket.connect(this, &RelayPortTest::OnReadPacket);
+ return packet_socket;
+ }
+
+ talk_base::AsyncSocket* CreateServerSocket(const SocketAddress addr) {
+ talk_base::AsyncSocket* socket =
+ virtual_socket_server_->CreateAsyncSocket(SOCK_STREAM);
+ EXPECT_GE(socket->Bind(addr), 0);
+ EXPECT_GE(socket->Listen(5), 0);
+ return socket;
+ }
+
+ bool HasFailed(cricket::ProtocolAddress* addr) {
+ for (size_t i = 0; i < failed_connections_.size(); i++) {
+ if (failed_connections_[i].address == addr->address &&
+ failed_connections_[i].proto == addr->proto) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool HasTimedOut(cricket::ProtocolAddress* addr) {
+ for (size_t i = 0; i < soft_timedout_connections_.size(); i++) {
+ if (soft_timedout_connections_[i].address == addr->address &&
+ soft_timedout_connections_[i].proto == addr->proto) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ typedef std::map<talk_base::AsyncPacketSocket*, int> PacketMap;
+
+ talk_base::Thread* main_;
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer>
+ physical_socket_server_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> virtual_socket_server_;
+ talk_base::SocketServerScope ss_scope_;
+ talk_base::Network network_;
+ talk_base::BasicPacketSocketFactory socket_factory_;
+ std::string username_;
+ std::string password_;
+ talk_base::scoped_ptr<cricket::RelayPort> relay_port_;
+ talk_base::scoped_ptr<cricket::RelayServer> relay_server_;
+ std::vector<cricket::ProtocolAddress> failed_connections_;
+ std::vector<cricket::ProtocolAddress> soft_timedout_connections_;
+ PacketMap received_packet_count_;
+};
+
+TEST_F(RelayPortTest, ConnectUdp) {
+ TestConnectUdp();
+}
+
+TEST_F(RelayPortTest, ConnectTcp) {
+ TestConnectTcp();
+}
+
+TEST_F(RelayPortTest, ConnectSslTcp) {
+ TestConnectSslTcp();
+}
diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc
index 554337f..b17da5b 100644
--- a/talk/p2p/base/relayserver.cc
+++ b/talk/p2p/base/relayserver.cc
@@ -444,7 +444,7 @@
StunAddressAttribute* addr_attr =
StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
- addr_attr->SetIP(ext_addr.ip());
+ addr_attr->SetIP(ext_addr.ipaddr());
addr_attr->SetPort(ext_addr.port());
response.AddAttribute(addr_attr);
@@ -478,7 +478,7 @@
return;
}
- talk_base::SocketAddress ext_addr(addr_attr->ip(), addr_attr->port());
+ talk_base::SocketAddress ext_addr(addr_attr->ipaddr(), addr_attr->port());
RelayServerConnection* ext_conn =
int_conn->binding()->GetExternalConnection(ext_addr);
if (!ext_conn) {
@@ -620,7 +620,7 @@
StunAddressAttribute* addr_attr =
StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
- addr_attr->SetIP(from_addr.ip());
+ addr_attr->SetIP(from_addr.ipaddr());
addr_attr->SetPort(from_addr.port());
msg.AddAttribute(addr_attr);
diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h
index d34c099..a5fcc24 100644
--- a/talk/p2p/base/relayserver.h
+++ b/talk/p2p/base/relayserver.h
@@ -35,7 +35,7 @@
#include "talk/base/asyncudpsocket.h"
#include "talk/base/socketaddresspair.h"
#include "talk/base/thread.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/p2p/base/port.h"
#include "talk/p2p/base/stun.h"
diff --git a/talk/p2p/base/relayserver_unittest.cc b/talk/p2p/base/relayserver_unittest.cc
new file mode 100644
index 0000000..0f96e27
--- /dev/null
+++ b/talk/p2p/base/relayserver_unittest.cc
@@ -0,0 +1,546 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/relayserver.h"
+
+using talk_base::SocketAddress;
+using namespace cricket;
+
+static const uint32 LIFETIME = 4; // seconds
+static const SocketAddress server_int_addr("127.0.0.1", 5000);
+static const SocketAddress server_ext_addr("127.0.0.1", 5001);
+static const SocketAddress client1_addr("127.0.0.1", 6000 + (rand() % 1000));
+static const SocketAddress client2_addr("127.0.0.1", 7000 + (rand() % 1000));
+static 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";
+static const char* msg1 = "spamspamspamspamspamspamspambakedbeansspam";
+static const char* msg2 = "Lobster Thermidor a Crevette with a mornay sauce...";
+
+class RelayServerTest : public testing::Test {
+ public:
+ static void SetUpTestCase() {
+ talk_base::InitRandom(NULL, 0);
+ }
+ RelayServerTest()
+ : main_(talk_base::Thread::Current()), ss_(main_->socketserver()),
+ username_(talk_base::CreateRandomString(12)),
+ password_(talk_base::CreateRandomString(12)) {
+ }
+ protected:
+ virtual void SetUp() {
+ server_.reset(new RelayServer(main_));
+
+ server_->AddInternalSocket(
+ talk_base::AsyncUDPSocket::Create(ss_, server_int_addr));
+ server_->AddExternalSocket(
+ talk_base::AsyncUDPSocket::Create(ss_, server_ext_addr));
+
+ client1_.reset(new talk_base::TestClient(
+ talk_base::AsyncUDPSocket::Create(ss_, client1_addr)));
+ client2_.reset(new talk_base::TestClient(
+ talk_base::AsyncUDPSocket::Create(ss_, client2_addr)));
+ }
+
+ void Allocate() {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST));
+ AddUsernameAttr(req.get(), username_);
+ AddLifetimeAttr(req.get(), LIFETIME);
+ Send1(req.get());
+ delete Receive1();
+ }
+ void Bind() {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST));
+ AddUsernameAttr(req.get(), username_);
+ Send2(req.get());
+ delete Receive1();
+ }
+
+ void Send1(const StunMessage* msg) {
+ talk_base::ByteBuffer buf;
+ msg->Write(&buf);
+ SendRaw1(buf.Data(), buf.Length());
+ }
+ void Send2(const StunMessage* msg) {
+ talk_base::ByteBuffer buf;
+ msg->Write(&buf);
+ SendRaw2(buf.Data(), buf.Length());
+ }
+ void SendRaw1(const char* data, int len) {
+ return Send(client1_.get(), data, len, server_int_addr);
+ }
+ void SendRaw2(const char* data, int len) {
+ return Send(client2_.get(), data, len, server_ext_addr);
+ }
+ void Send(talk_base::TestClient* client, const char* data,
+ int len, const SocketAddress& addr) {
+ client->SendTo(data, len, addr);
+ }
+
+ StunMessage* Receive1() {
+ return Receive(client1_.get());
+ }
+ StunMessage* Receive2() {
+ return Receive(client2_.get());
+ }
+ std::string ReceiveRaw1() {
+ return ReceiveRaw(client1_.get());
+ }
+ std::string ReceiveRaw2() {
+ return ReceiveRaw(client2_.get());
+ }
+ StunMessage* Receive(talk_base::TestClient* client) {
+ StunMessage* msg = NULL;
+ talk_base::TestClient::Packet* packet = client->NextPacket();
+ if (packet) {
+ talk_base::ByteBuffer buf(packet->buf, packet->size);
+ msg = new StunMessage();
+ msg->Read(&buf);
+ delete packet;
+ }
+ return msg;
+ }
+ std::string ReceiveRaw(talk_base::TestClient* client) {
+ std::string raw;
+ talk_base::TestClient::Packet* packet = client->NextPacket();
+ if (packet) {
+ raw = std::string(packet->buf, packet->size);
+ delete packet;
+ }
+ return raw;
+ }
+
+ static StunMessage* CreateStunMessage(StunMessageType type) {
+ StunMessage* msg = new StunMessage();
+ msg->SetType(type);
+ msg->SetTransactionID(
+ talk_base::CreateRandomString(kStunTransactionIdLength));
+ return msg;
+ }
+ static void AddMagicCookieAttr(StunMessage* msg) {
+ StunByteStringAttribute* attr =
+ StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ attr->CopyBytes(TURN_MAGIC_COOKIE_VALUE, sizeof(TURN_MAGIC_COOKIE_VALUE));
+ msg->AddAttribute(attr);
+ }
+ static void AddUsernameAttr(StunMessage* msg, const std::string& val) {
+ StunByteStringAttribute* attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ attr->CopyBytes(val.c_str(), val.size());
+ msg->AddAttribute(attr);
+ }
+ static void AddLifetimeAttr(StunMessage* msg, int val) {
+ StunUInt32Attribute* attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ attr->SetValue(val);
+ msg->AddAttribute(attr);
+ }
+ static void AddDestinationAttr(StunMessage* msg, const SocketAddress& addr) {
+ StunAddressAttribute* attr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ attr->SetIP(addr.ipaddr());
+ attr->SetPort(addr.port());
+ msg->AddAttribute(attr);
+ }
+
+ talk_base::Thread* main_;
+ talk_base::SocketServer* ss_;
+ talk_base::scoped_ptr<RelayServer> server_;
+ talk_base::scoped_ptr<talk_base::TestClient> client1_;
+ talk_base::scoped_ptr<talk_base::TestClient> client2_;
+ std::string username_;
+ std::string password_;
+};
+
+// Send a complete nonsense message and verify that it is rejected.
+TEST_F(RelayServerTest, TestBadRequest) {
+ talk_base::scoped_ptr<StunMessage> res;
+
+ SendRaw1(bad, std::strlen(bad));
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send an allocate request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestAllocateNoUsername) {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_ALLOCATE_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(32, err->number());
+ EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a binding request and verify that it is rejected.
+TEST_F(RelayServerTest, TestBindingRequest) {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Send an allocate request and verify that it is accepted.
+TEST_F(RelayServerTest, TestAllocate) {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+ AddLifetimeAttr(req.get(), LIFETIME);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+ EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+ const StunUInt32Attribute* res_lifetime_attr =
+ res->GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(res_lifetime_attr != NULL);
+ EXPECT_EQ(LIFETIME, res_lifetime_attr->value());
+}
+
+// Send a second allocate request and verify that it is also accepted, though
+// the lifetime should be ignored.
+TEST_F(RelayServerTest, TestReallocate) {
+ Allocate();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_ALLOCATE_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_ALLOCATE_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ res->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(server_ext_addr.port(), mapped_addr->port());
+ EXPECT_EQ(server_ext_addr.ipaddr(), mapped_addr->ipaddr());
+
+ const StunUInt32Attribute* lifetime_attr =
+ res->GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(lifetime_attr != NULL);
+ EXPECT_EQ(LIFETIME, lifetime_attr->value());
+}
+
+// Send a request from another client and see that it arrives at the first
+// client in the binding.
+TEST_F(RelayServerTest, TestRemoteBind) {
+ Allocate();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddUsernameAttr(req.get(), username_);
+
+ Send2(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+ const StunByteStringAttribute* recv_data =
+ res->GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(recv_data != NULL);
+
+ talk_base::ByteBuffer buf(recv_data->bytes(), recv_data->length());
+ talk_base::scoped_ptr<StunMessage> res2(new StunMessage());
+ EXPECT_TRUE(res2->Read(&buf));
+ EXPECT_EQ(STUN_BINDING_REQUEST, res2->type());
+ EXPECT_EQ(req->transaction_id(), res2->transaction_id());
+
+ const StunAddressAttribute* src_addr =
+ res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(src_addr != NULL);
+ EXPECT_EQ(1, src_addr->family());
+ EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+ EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+ EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a complete nonsense message to the established connection and verify
+// that it is dropped by the server.
+TEST_F(RelayServerTest, TestRemoteBadRequest) {
+ Allocate();
+ Bind();
+
+ SendRaw1(bad, std::strlen(bad));
+ EXPECT_TRUE(Receive1() == NULL);
+ EXPECT_TRUE(Receive2() == NULL);
+}
+
+// Send a send request without a username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestMissingUsername) {
+ Allocate();
+ Bind();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(32, err->number());
+ EXPECT_EQ("Missing Username", err->reason());
+}
+
+// Send a send request with the wrong username and verify it is rejected.
+TEST_F(RelayServerTest, TestSendRequestBadUsername) {
+ Allocate();
+ Bind();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), "foobarbizbaz");
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(30, err->number());
+ EXPECT_EQ("Stale Credentials", err->reason());
+}
+
+// Send a send request without a destination address and verify that it is
+// rejected.
+TEST_F(RelayServerTest, TestSendRequestNoDestinationAddress) {
+ Allocate();
+ Bind();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a send request without data and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestNoData) {
+ Allocate();
+ Bind();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(00, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+}
+
+// Send a binding request after an allocate and verify that it is rejected.
+TEST_F(RelayServerTest, TestSendRequestWrongType) {
+ Allocate();
+ Bind();
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_BINDING_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, res->type());
+ EXPECT_EQ(req->transaction_id(), res->transaction_id());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+}
+
+// Verify that we can send traffic back and forth between the clients after a
+// successful allocate and bind.
+TEST_F(RelayServerTest, TestSendRaw) {
+ Allocate();
+ Bind();
+
+ for (int i = 0; i < 10; i++) {
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ StunByteStringAttribute* send_data =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ send_data->CopyBytes(msg1);
+ req->AddAttribute(send_data);
+
+ Send1(req.get());
+ EXPECT_EQ(msg1, ReceiveRaw2());
+ SendRaw2(msg2, std::strlen(msg2));
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_DATA_INDICATION, res->type());
+
+ const StunAddressAttribute* src_addr =
+ res->GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(src_addr != NULL);
+ EXPECT_EQ(1, src_addr->family());
+ EXPECT_EQ(client2_addr.ipaddr(), src_addr->ipaddr());
+ EXPECT_EQ(client2_addr.port(), src_addr->port());
+
+ const StunByteStringAttribute* recv_data =
+ res->GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(recv_data != NULL);
+ EXPECT_EQ(strlen(msg2), recv_data->length());
+ EXPECT_EQ(0, memcmp(msg2, recv_data->bytes(), recv_data->length()));
+ }
+}
+
+// Verify that a binding expires properly, and rejects send requests.
+TEST_F(RelayServerTest, TestExpiration) {
+ Allocate();
+ Bind();
+
+ // Wait twice the lifetime to make sure the server has expired the binding.
+ talk_base::Thread::Current()->ProcessMessages((LIFETIME * 2) * 1000);
+
+ talk_base::scoped_ptr<StunMessage> req(
+ CreateStunMessage(STUN_SEND_REQUEST)), res;
+ AddMagicCookieAttr(req.get());
+ AddUsernameAttr(req.get(), username_);
+ AddDestinationAttr(req.get(), client2_addr);
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ data_attr->CopyBytes(msg1);
+ req->AddAttribute(data_attr);
+
+ Send1(req.get());
+ res.reset(Receive1());
+
+ ASSERT_TRUE(res.get() != NULL);
+ EXPECT_EQ(STUN_SEND_ERROR_RESPONSE, res->type());
+
+ const StunErrorCodeAttribute* err = res->GetErrorCode();
+ ASSERT_TRUE(err != NULL);
+ EXPECT_EQ(6, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Operation Not Supported", err->reason());
+
+ // Also verify that traffic from the external client is ignored.
+ SendRaw2(msg2, std::strlen(msg2));
+ EXPECT_TRUE(ReceiveRaw1().empty());
+}
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
index b5be361..aba5ac4 100644
--- a/talk/p2p/base/session.cc
+++ b/talk/p2p/base/session.cc
@@ -32,11 +32,11 @@
#include "talk/base/scoped_ptr.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/jid.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
#include "talk/p2p/base/sessionclient.h"
#include "talk/p2p/base/transport.h"
#include "talk/p2p/base/transportchannelproxy.h"
-#include "talk/p2p/base/p2ptransport.h"
-#include "talk/p2p/base/p2ptransportchannel.h"
#include "talk/p2p/base/constants.h"
@@ -64,13 +64,21 @@
iter->second->SignalDestroyed(iter->second);
delete iter->second;
}
- delete transport_;
+ if (owner_)
+ delete transport_;
}
std::string TransportProxy::type() const {
return transport_->type();
}
+void TransportProxy::SetImplementation(Transport* impl, bool owner) {
+ if (owner_ && transport_)
+ delete transport_;
+ transport_ = impl;
+ owner_ = owner;
+}
+
TransportChannel* TransportProxy::GetChannel(const std::string& name) {
return GetProxy(name);
}
@@ -148,6 +156,7 @@
TransportChannelImpl* impl = transport_->GetChannel(name);
if (impl == NULL) {
impl = transport_->CreateChannel(name, content_type);
+ impl->set_session_id(sid_);
}
return impl;
}
@@ -156,7 +165,34 @@
const std::string& name, TransportChannelProxy* proxy) {
TransportChannelImpl* impl = GetOrCreateImpl(name, proxy->content_type());
ASSERT(impl != NULL);
- proxy->SetImplementation(impl);
+ proxy->SetImplementation(impl, true);
+}
+
+// This method will use TransportChannelImpls of and deletes what it owns.
+void TransportProxy::CopyTransportProxyChannels(TransportProxy* proxy) {
+ size_t index = 0;
+ for (ChannelMap::const_iterator iter = proxy->channels().begin();
+ iter != proxy->channels().end(); ++iter, ++index) {
+ ReplaceImpl(iter->second, index);
+ }
+}
+
+void TransportProxy::ReplaceImpl(TransportChannelProxy* channel,
+ size_t index) {
+ if (index < channels().size()) {
+ ChannelMap::const_iterator iter = channels().begin();
+ // Get handle the index which needs to be replaced.
+ for (size_t i = 0; i < index; ++i, ++iter);
+
+ TransportChannelProxy* target_channel = iter->second;
+ if (target_channel) {
+ // Deleting TransportChannelImpl before replacing it.
+ transport_->DestroyChannel(iter->first);
+ target_channel->SetImplementation(channel->impl(), false);
+ }
+ } else {
+ LOG(LS_WARNING) << "invalid TransportChannelProxy index to replace";
+ }
}
BaseSession::BaseSession(talk_base::Thread* signaling_thread,
@@ -249,7 +285,7 @@
transport->SignalChannelGone.connect(
this, &BaseSession::OnTransportChannelGone);
- transproxy = new TransportProxy(content_name, transport);
+ transproxy = new TransportProxy(sid_, content_name, transport);
transports_[content_name] = transproxy;
return transproxy;
@@ -323,6 +359,59 @@
}
}
+bool BaseSession::ContentsGrouped() {
+ // TODO - present implementation checks for groups present
+ // in SDP. It may be necessary to check content_names in groups of both
+ // local and remote descriptions. Assumption here is that when this method
+ // returns true, media contents can be muxed.
+ if (local_description()->HasGroup(GN_BUNDLE) &&
+ remote_description()->HasGroup(GN_BUNDLE)) {
+ return true;
+ }
+ return false;
+}
+
+bool BaseSession::MaybeEnableMuxingSupport() {
+ bool ret = true;
+ if (!ContentsGrouped()) {
+ LOG(LS_INFO) << "Contents are not grouped together cannot be muxed";
+ } else {
+ // Always use first content name from the group for muxing. Hence ordering
+ // of content names in SDP should match to the order in group.
+ const ContentGroup* muxed_content_group =
+ local_description()->GetGroupByName(GN_BUNDLE);
+ const std::string* content_name =
+ muxed_content_group->FirstContentName();
+ if (content_name) {
+ const ContentInfo* content =
+ local_description_->GetContentByName(*content_name);
+ ASSERT(content != NULL);
+ SetSelectedProxy(content->name, muxed_content_group);
+ }
+ }
+ return ret;
+}
+
+void BaseSession::SetSelectedProxy(const std::string& content_name,
+ const ContentGroup* muxed_group) {
+ TransportProxy* selected_proxy = GetTransportProxy(content_name);
+ if (selected_proxy) {
+ ASSERT(selected_proxy->negotiated());
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ // If content is part of group, then try to replace the Proxy with
+ // the selected.
+ if (iter->first != content_name &&
+ muxed_group->HasContentName(iter->first)) {
+ TransportProxy* proxy = iter->second;
+ proxy->CopyTransportProxyChannels(selected_proxy);
+ // After replacing the TransportChannels, replace Transport
+ proxy->SetImplementation(selected_proxy->impl(), false);
+ }
+ }
+ }
+}
+
void BaseSession::OnMessage(talk_base::Message *pmsg) {
switch (pmsg->message_id) {
case MSG_TIMEOUT:
@@ -414,6 +503,7 @@
return false;
}
+ MaybeEnableMuxingSupport(); // Enable transport channel mux if supported.
SetState(Session::STATE_SENTACCEPT);
return true;
}
@@ -648,8 +738,8 @@
case ACTION_TRANSPORT_ACCEPT:
valid = OnTransportAcceptMessage(msg, &error);
break;
- case ACTION_UPDATE:
- valid = OnUpdateMessage(msg, &error);
+ case ACTION_DESCRIPTION_INFO:
+ valid = OnDescriptionInfoMessage(msg, &error);
break;
default:
valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
@@ -796,6 +886,7 @@
OnInitiateAcked();
set_remote_description(new SessionDescription(accept.ClearContents()));
+ MaybeEnableMuxingSupport(); // Enable transport channel mux if supported.
SetState(STATE_RECEIVEDACCEPT);
// Users of Session may listen to state change and call Reject().
@@ -856,7 +947,7 @@
return true;
}
-bool Session::OnUpdateMessage(const SessionMessage& msg,
+bool Session::OnDescriptionInfoMessage(const SessionMessage& msg,
MessageError* error) {
if (!CheckState(STATE_INPROGRESS, error))
return false;
@@ -869,27 +960,33 @@
}
ContentInfos updated_contents = description_info.ClearContents();
- ContentInfos::iterator it;
+ // TODO: Currently, reflector sends back
+ // video stream updates even for an audio-only call, which causes
+ // this to fail. Put this back once reflector is fixed.
+ //
+ // ContentInfos::iterator it;
// First, ensure all updates are valid before modifying remote_description_.
- for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
- if (remote_description()->GetContentByName(it->name) == NULL) {
- return false;
- }
+ // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+ // if (remote_description()->GetContentByName(it->name) == NULL) {
+ // return false;
+ // }
+ // }
- // TODO: We should add a check to ensure that the updated
- // contents are compatible with the original contents.
- }
+ // TODO: We used to replace contents from an update, but
+ // that no longer works with partial updates. We need to figure out
+ // a way to merge patial updates into contents. For now, users of
+ // Session should listen to SignalRemoteDescriptionUpdate and handle
+ // updates. They should not expect remote_description to be the
+ // latest value.
+ //
+ // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+ // remote_description()->RemoveContentByName(it->name);
+ // remote_description()->AddContent(it->name, it->type, it->description);
+ // }
+ // }
- // Merge the updates into the remote description.
- for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
- LOG(LS_INFO) << "Updating content " << it->name;
- remote_description()->RemoveContentByName(it->name);
- remote_description()->AddContent(it->name, it->type, it->description);
- }
-
- SignalRemoteDescriptionUpdate(this);
-
+ SignalRemoteDescriptionUpdate(this, updated_contents);
return true;
}
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
index cb3c1fc..b57468b 100644
--- a/talk/p2p/base/session.h
+++ b/talk/p2p/base/session.h
@@ -76,17 +76,29 @@
// initiate, and for speculatively connecting channels. Previously, a
// session had one ChannelMap and transport. Now, with multiple
// transports per session, we need multiple ChannelMaps as well.
+
+typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
+
class TransportProxy {
public:
- TransportProxy(const std::string& content_name, Transport* transport)
- : content_name_(content_name),
+ TransportProxy(
+ const std::string& sid,
+ const std::string& content_name,
+ Transport* transport)
+ : sid_(sid),
+ content_name_(content_name),
transport_(transport),
+ owner_(true),
state_(STATE_INIT),
sent_candidates_(false) {}
~TransportProxy();
std::string content_name() const { return content_name_; }
Transport* impl() const { return transport_; }
+ // TransportProxy can contain a pointer to Transport for which it's not the
+ // actual owner. In that case it shouldn't try to delete Transport object.
+ // TODO - Remove this hack when ref count support is available.
+ void SetImplementation(Transport* impl, bool owner);
std::string type() const;
bool negotiated() const { return state_ == STATE_NEGOTIATED; }
const Candidates& sent_candidates() const { return sent_candidates_; }
@@ -102,6 +114,8 @@
void ClearUnsentCandidates() { unsent_candidates_.clear(); }
void SpeculativelyConnectChannels();
void CompleteNegotiation();
+ void CopyTransportProxyChannels(TransportProxy* proxy);
+ const ChannelMap& channels() { return channels_; }
private:
enum TransportState {
@@ -110,15 +124,16 @@
STATE_NEGOTIATED
};
- typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
-
TransportChannelProxy* GetProxy(const std::string& name);
+ void ReplaceImpl(TransportChannelProxy* channel_proxy, size_t index);
TransportChannelImpl* GetOrCreateImpl(const std::string& name,
const std::string& content_type);
void SetProxyImpl(const std::string& name, TransportChannelProxy* proxy);
+ std::string sid_;
std::string content_name_;
Transport* transport_;
+ bool owner_;
TransportState state_;
ChannelMap channels_;
Candidates sent_candidates_;
@@ -231,12 +246,12 @@
// Returns the current state of the session. See the enum above for details.
// Each time the state changes, we will fire this signal.
State state() const { return state_; }
- sigslot::signal2<BaseSession *, State> SignalState;
+ sigslot::signal2<BaseSession* , State> SignalState;
// Returns the last error in the session. See the enum above for details.
// Each time the an error occurs, we will fire this signal.
Error error() const { return error_; }
- sigslot::signal2<BaseSession *, Error> SignalError;
+ sigslot::signal2<BaseSession* , Error> SignalError;
// Updates the state, signaling if necessary.
virtual void SetState(State state);
@@ -244,8 +259,10 @@
// Updates the error state, signaling if necessary.
virtual void SetError(Error error);
- // Fired when the remote description is updated.
- sigslot::signal1<BaseSession *> SignalRemoteDescriptionUpdate;
+ // Fired when the remote description is updated, with the updated
+ // contents.
+ sigslot::signal2<BaseSession* , const ContentInfos&>
+ SignalRemoteDescriptionUpdate;
// Returns the transport that has been negotiated or NULL if
// negotiation is still in progress.
@@ -270,6 +287,7 @@
// Send when doing so.
virtual void DestroyChannel(const std::string& content_name,
const std::string& channel_name);
+
protected:
const TransportMap& transport_proxies() const { return transports_; }
// Get a TransportProxy by content_name or transport. NULL if not found.
@@ -283,6 +301,9 @@
void OnSignalingReady();
void SpeculativelyConnectAllTransportChannels();
+ // This method will mux transport channels by content_name.
+ // First content is used for muxing.
+ bool MaybeEnableMuxingSupport();
// Called when a transport requests signaling.
virtual void OnTransportRequestSignaling(Transport* transport) {
@@ -325,10 +346,19 @@
virtual void OnMessage(talk_base::Message *pmsg);
protected:
-// private:
State state_;
Error error_;
+
private:
+ // This method will check GroupInfo in local and remote SessionDescriptions.
+ bool ContentsGrouped();
+ // This method will delete the Transport and TransportChannelImpl's and
+ // replace those with the selected Transport objects. Selection is done
+ // based on the content_name and in this case first MediaContent information
+ // is used for mux.
+ void SetSelectedProxy(const std::string& content_name,
+ const ContentGroup* muxed_group);
+
talk_base::Thread* signaling_thread_;
talk_base::Thread* worker_thread_;
PortAllocator* port_allocator_;
@@ -511,7 +541,7 @@
// 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;
+ sigslot::signal2<Session* , const buzz::XmlElement*> SignalOutgoingMessage;
void OnIncomingMessage(const SessionMessage& msg);
void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
@@ -539,7 +569,7 @@
bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
- bool OnUpdateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnDescriptionInfoMessage(const SessionMessage& msg, MessageError* error);
bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
// Verifies that we are in the appropriate state to receive this message.
diff --git a/talk/p2p/base/session_unittest.cc b/talk/p2p/base/session_unittest.cc
new file mode 100644
index 0000000..aec4986
--- /dev/null
+++ b/talk/p2p/base/session_unittest.cc
@@ -0,0 +1,2231 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <cstring>
+#include <sstream>
+#include <deque>
+#include <map>
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/natserver.h"
+#include "talk/base/natsocketfactory.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/relayserver.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/stunserver.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/xmpp/constants.h"
+
+using cricket::SignalingProtocol;
+using cricket::PROTOCOL_HYBRID;
+using cricket::PROTOCOL_JINGLE;
+using cricket::PROTOCOL_GINGLE;
+
+static const std::string kInitiator = "init@init.com";
+static const std::string kResponder = "resp@resp.com";
+// Expected from test random number generator.
+static const std::string kSessionId = "2154761789";
+// TODO: When we need to test more than one transport type,
+// allow this to be injected like the content types are.
+static const std::string kTransportType = "http://www.google.com/transport/p2p";
+
+// Controls how long we wait for a session to send messages that we
+// expect, in milliseconds. We put it high to avoid flaky tests.
+static const int kEventTimeout = 5000;
+
+static const int kNumPorts = 2;
+static const int kPort0 = 28653;
+static const int kPortStep = 5;
+
+static const std::string kNotifyNick1 = "derekcheng_google.com^59422C27";
+static const std::string kNotifyNick2 = "someoneelses_google.com^7abd6a7a20";
+static const uint32 kNotifyAudioSsrc1 = 2625839801U;
+static const uint32 kNotifyAudioSsrc2 = 2529430427U;
+static const uint32 kNotifyVideoSsrc1 = 3;
+static const uint32 kNotifyVideoSsrc2 = 2;
+
+static const std::string kViewRequestNick = "param_google.com^16A3CDBE";
+static const uint32 kViewRequestSsrc = 4;
+static const int kViewRequestWidth = 320;
+static const int kViewRequestHeight = 200;
+static const int kViewRequestFrameRate = 15;
+
+int GetPort(int port_index) {
+ return kPort0 + (port_index * kPortStep);
+}
+
+std::string GetPortString(int port_index) {
+ return talk_base::ToString(GetPort(port_index));
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetUsername(int port_index) {
+ return "username" + std::string(8, talk_base::ToString(port_index)[0]);
+}
+
+// Only works for port_index < 10, which is fine for our purposes.
+std::string GetPassword(int port_index) {
+ return "password" + std::string(8, talk_base::ToString(port_index)[0]);
+}
+
+std::string IqAck(const std::string& id,
+ const std::string& from,
+ const std::string& to) {
+ return "<cli:iq"
+ " to=\"" + to + "\""
+ " id=\"" + id + "\""
+ " type=\"result\""
+ " from=\"" + from + "\""
+ " xmlns:cli=\"jabber:client\""
+ "/>";
+}
+
+std::string IqSet(const std::string& id,
+ const std::string& from,
+ const std::string& to,
+ const std::string& content) {
+ return "<cli:iq"
+ " to=\"" + to + "\""
+ " type=\"set\""
+ " from=\"" + from + "\""
+ " id=\"" + id + "\""
+ " xmlns:cli=\"jabber:client\""
+ ">"
+ + content +
+ "</cli:iq>";
+}
+
+std::string IqError(const std::string& id,
+ const std::string& from,
+ const std::string& to,
+ const std::string& content) {
+ return "<cli:error"
+ " to=\"" + to + "\""
+ " type=\"error\""
+ " from=\"" + from + "\""
+ " id=\"" + id + "\""
+ " xmlns:cli=\"jabber:client\""
+ ">"
+ + content +
+ "</cli:error>";
+}
+
+std::string GingleSessionXml(const std::string& type,
+ const std::string& content) {
+ return "<session"
+ " xmlns=\"http://www.google.com/session\""
+ " type=\"" + type + "\""
+ " id=\"" + kSessionId + "\""
+ " initiator=\"" + kInitiator + "\""
+ ">"
+ + content +
+ "</session>";
+}
+
+std::string GingleDescriptionXml(const std::string& content_type) {
+ return "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>";
+}
+
+std::string P2pCandidateXml(const std::string& name, int port_index) {
+ return "<candidate"
+ " name=\"" + name + "\""
+ " address=\"127.0.0.1\""
+ " port=\"" + GetPortString(port_index) + "\""
+ " preference=\"1\""
+ " username=\"" + GetUsername(port_index) + "\""
+ " protocol=\"udp\""
+ " generation=\"0\""
+ " password=\"" + GetPassword(port_index) + "\""
+ " type=\"local\""
+ " network=\"network\""
+ "/>";
+}
+
+std::string JingleActionXml(const std::string& action,
+ const std::string& content) {
+ return "<jingle"
+ " xmlns=\"urn:xmpp:jingle:1\""
+ " action=\"" + action + "\""
+ " sid=\"" + kSessionId + "\""
+ ">"
+ + content +
+ "</jingle>";
+}
+
+std::string JingleInitiateActionXml(const std::string& content) {
+ return "<jingle"
+ " xmlns=\"urn:xmpp:jingle:1\""
+ " action=\"session-initiate\""
+ " sid=\"" + kSessionId + "\""
+ " initiator=\"" + kInitiator + "\""
+ ">"
+ + content +
+ "</jingle>";
+}
+
+std::string JingleEmptyContentXml(const std::string& content_name,
+ const std::string& content_type,
+ const std::string& transport_type) {
+ return "<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>"
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ "/>"
+ "</content>";
+}
+
+std::string JingleContentXml(const std::string& content_name,
+ const std::string& content_type,
+ const std::string& transport_type,
+ const std::string& transport_main) {
+ std::string transport = transport_type.empty() ? "" :
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ ">"
+ + transport_main +
+ "</transport>";
+
+ return"<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<description"
+ " xmlns=\"" + content_type + "\""
+ "/>"
+ + transport +
+ "</content>";
+}
+
+std::string JingleTransportContentXml(const std::string& content_name,
+ const std::string& transport_type,
+ const std::string& content) {
+ return "<content"
+ " name=\"" + content_name + "\""
+ " creator=\"initiator\""
+ ">"
+ "<transport"
+ " xmlns=\"" + transport_type + "\""
+ ">"
+ + content +
+ "</transport>"
+ "</content>";
+}
+
+std::string GingleInitiateXml(const std::string& content_type) {
+ return GingleSessionXml(
+ "initiate",
+ GingleDescriptionXml(content_type));
+}
+
+std::string JingleInitiateXml(const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b) {
+ if (content_name_b.empty()) {
+ return JingleInitiateActionXml(
+ JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType));
+ } else {
+ return JingleInitiateActionXml(
+ JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType) +
+ JingleEmptyContentXml(
+ content_name_b, content_type_b, kTransportType));
+ }
+}
+
+std::string GingleAcceptXml(const std::string& content_type) {
+ return GingleSessionXml(
+ "accept",
+ GingleDescriptionXml(content_type));
+}
+
+std::string JingleAcceptXml(const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b) {
+ if (content_name_b.empty()) {
+ return JingleActionXml(
+ "session-accept",
+ JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType));
+ } else {
+ return JingleActionXml(
+ "session-accept",
+ JingleEmptyContentXml(
+ content_name_a, content_type_a, kTransportType) +
+ JingleEmptyContentXml(
+ content_name_b, content_type_b, kTransportType));
+ }
+}
+
+std::string Gingle2CandidatesXml(const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ return GingleSessionXml(
+ "candidates",
+ P2pCandidateXml(channel_name, port_index0) +
+ P2pCandidateXml(channel_name, port_index1));
+}
+
+std::string Gingle4CandidatesXml(const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ return GingleSessionXml(
+ "candidates",
+ P2pCandidateXml(channel_name_a, port_index0) +
+ P2pCandidateXml(channel_name_a, port_index1) +
+ P2pCandidateXml(channel_name_b, port_index2) +
+ P2pCandidateXml(channel_name_b, port_index3));
+}
+
+std::string Jingle2TransportInfoXml(const std::string& content_name,
+ const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ return JingleActionXml(
+ "transport-info",
+ JingleTransportContentXml(
+ content_name, kTransportType,
+ P2pCandidateXml(channel_name, port_index0) +
+ P2pCandidateXml(channel_name, port_index1)));
+}
+
+std::string Jingle4TransportInfoXml(const std::string& content_name,
+ const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ return JingleActionXml(
+ "transport-info",
+ JingleTransportContentXml(
+ content_name, kTransportType,
+ P2pCandidateXml(channel_name_a, port_index0) +
+ P2pCandidateXml(channel_name_a, port_index1) +
+ P2pCandidateXml(channel_name_b, port_index2) +
+ P2pCandidateXml(channel_name_b, port_index3)));
+}
+
+std::string JingleDescriptionInfoXml(const std::string& content_name,
+ const std::string& content_type) {
+ return JingleActionXml(
+ "description-info",
+ JingleContentXml(content_name, content_type, "", ""));
+}
+
+std::string GingleRejectXml(const std::string& reason) {
+ return GingleSessionXml(
+ "reject",
+ "<" + reason + "/>");
+}
+
+std::string JingleTerminateXml(const std::string& reason) {
+ return JingleActionXml(
+ "session-terminate",
+ "<reason><" + reason + "/></reason>");
+}
+
+std::string GingleTerminateXml(const std::string& reason) {
+ return GingleSessionXml(
+ "terminate",
+ "<" + reason + "/>");
+}
+
+std::string GingleRedirectXml(const std::string& intitiate,
+ const std::string& target) {
+ return intitiate +
+ "<error code=\"302\" type=\"modify\">"
+ "<redirect xmlns=\"http://www.google.com/session\">"
+ "xmpp:" + target +
+ "</redirect>"
+ "</error>";
+}
+
+std::string JingleRedirectXml(const std::string& intitiate,
+ const std::string& target) {
+ return intitiate +
+ "<error code=\"302\" type=\"modify\">"
+ "<redirect xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"
+ "xmpp:" + target +
+ "</redirect>"
+ "</error>";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleInitiateXml(content_name_a, content_type_a,
+ content_name_b, content_type_b);
+ case PROTOCOL_GINGLE:
+ return GingleInitiateXml(gingle_content_type);
+ case PROTOCOL_HYBRID:
+ return JingleInitiateXml(content_name_a, content_type_a,
+ content_name_b, content_type_b) +
+ GingleInitiateXml(gingle_content_type);
+ }
+ return "";
+}
+
+std::string InitiateXml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& content_type) {
+ return InitiateXml(protocol,
+ content_type,
+ content_name, content_type,
+ "", "");
+}
+
+std::string AcceptXml(SignalingProtocol protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_name_a,
+ const std::string& content_type_a,
+ const std::string& content_name_b,
+ const std::string& content_type_b) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleAcceptXml(content_name_a, content_type_a,
+ content_name_b, content_type_b);
+ case PROTOCOL_GINGLE:
+ return GingleAcceptXml(gingle_content_type);
+ case PROTOCOL_HYBRID:
+ return
+ JingleAcceptXml(content_name_a, content_type_a,
+ content_name_b, content_type_b) +
+ GingleAcceptXml(gingle_content_type);
+ }
+ return "";
+}
+
+
+std::string AcceptXml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& content_type) {
+ return AcceptXml(protocol,
+ content_type,
+ content_name, content_type,
+ "", "");
+}
+
+std::string TransportInfo2Xml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& channel_name,
+ int port_index0,
+ int port_index1) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return Jingle2TransportInfoXml(
+ content_name,
+ channel_name, port_index0, port_index1);
+ case PROTOCOL_GINGLE:
+ return Gingle2CandidatesXml(
+ channel_name, port_index0, port_index1);
+ case PROTOCOL_HYBRID:
+ return
+ Jingle2TransportInfoXml(
+ content_name,
+ channel_name, port_index0, port_index1) +
+ Gingle2CandidatesXml(
+ channel_name, port_index0, port_index1);
+ }
+ return "";
+}
+
+std::string TransportInfo4Xml(SignalingProtocol protocol,
+ const std::string& content_name,
+ const std::string& channel_name_a,
+ int port_index0,
+ int port_index1,
+ const std::string& channel_name_b,
+ int port_index2,
+ int port_index3) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return Jingle4TransportInfoXml(
+ content_name,
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ case PROTOCOL_GINGLE:
+ return Gingle4CandidatesXml(
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ case PROTOCOL_HYBRID:
+ return
+ Jingle4TransportInfoXml(
+ content_name,
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3) +
+ Gingle4CandidatesXml(
+ channel_name_a, port_index0, port_index1,
+ channel_name_b, port_index2, port_index3);
+ }
+ return "";
+}
+
+std::string RejectXml(SignalingProtocol protocol,
+ const std::string& reason) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleTerminateXml(reason);
+ case PROTOCOL_GINGLE:
+ return GingleRejectXml(reason);
+ case PROTOCOL_HYBRID:
+ return JingleTerminateXml(reason) +
+ GingleRejectXml(reason);
+ }
+ return "";
+}
+
+std::string TerminateXml(SignalingProtocol protocol,
+ const std::string& reason) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleTerminateXml(reason);
+ case PROTOCOL_GINGLE:
+ return GingleTerminateXml(reason);
+ case PROTOCOL_HYBRID:
+ return JingleTerminateXml(reason) +
+ GingleTerminateXml(reason);
+ }
+ return "";
+}
+
+std::string RedirectXml(SignalingProtocol protocol,
+ const std::string& initiate,
+ const std::string& target) {
+ switch (protocol) {
+ case PROTOCOL_JINGLE:
+ return JingleRedirectXml(initiate, target);
+ case PROTOCOL_GINGLE:
+ return GingleRedirectXml(initiate, target);
+ }
+ return "";
+}
+
+// TODO: Break out and join with fakeportallocator.h
+class TestPortAllocatorSession : public cricket::PortAllocatorSession {
+ public:
+ TestPortAllocatorSession(const std::string& name,
+ const std::string& session_type,
+ const int port_offset)
+ : PortAllocatorSession(name, session_type, 0),
+ port_offset_(port_offset),
+ ports_(kNumPorts),
+ address_("127.0.0.1", 0),
+ network_("network", "unittest", address_.ipaddr()),
+ socket_factory_(talk_base::Thread::Current()),
+ running_(false),
+ port_(28653) {
+ }
+
+ ~TestPortAllocatorSession() {
+ for (size_t i = 0; i < ports_.size(); i++)
+ delete ports_[i];
+ }
+
+ virtual void GetInitialPorts() {
+ for (int i = 0; i < kNumPorts; i++) {
+ int index = port_offset_ + i;
+ ports_[i] = cricket::UDPPort::Create(
+ talk_base::Thread::Current(), &socket_factory_,
+ &network_, address_.ipaddr(), GetPort(index), GetPort(index));
+ ports_[i]->set_username_fragment(GetUsername(index));
+ ports_[i]->set_password(GetPassword(index));
+ AddPort(ports_[i]);
+ }
+ }
+
+ virtual void StartGetAllPorts() { running_ = true; }
+ virtual void StopGetAllPorts() { running_ = false; }
+ virtual bool IsGettingAllPorts() { return running_; }
+
+ void AddPort(cricket::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(cricket::Port* port) {
+ for (size_t i = 0; i < ports_.size(); i++) {
+ if (ports_[i] == port)
+ ports_[i] = NULL;
+ }
+ }
+
+ void OnAddressReady(cricket::Port* port) {
+ SignalCandidatesReady(this, port->candidates());
+ }
+
+ private:
+ int port_offset_;
+ std::vector<cricket::Port*> ports_;
+ talk_base::SocketAddress address_;
+ talk_base::Network network_;
+ talk_base::BasicPacketSocketFactory socket_factory_;
+ bool running_;
+ int port_;
+};
+
+class TestPortAllocator : public cricket::PortAllocator {
+ public:
+ TestPortAllocator() : port_offset_(0) {}
+
+ virtual cricket::PortAllocatorSession*
+ CreateSession(const std::string &name,
+ const std::string &content_type) {
+ port_offset_ += 2;
+ return new TestPortAllocatorSession(name, content_type, port_offset_ - 2);
+ }
+
+ int port_offset_;
+};
+
+class TestContentDescription : public cricket::ContentDescription {
+ public:
+ explicit TestContentDescription(const std::string& gingle_content_type,
+ const std::string& content_type)
+ : gingle_content_type(gingle_content_type),
+ content_type(content_type) {
+ }
+
+ std::string gingle_content_type;
+ std::string content_type;
+};
+
+cricket::SessionDescription* NewTestSessionDescription(
+ const std::string gingle_content_type,
+ const std::string& content_name_a, const std::string& content_type_a,
+ const std::string& content_name_b, const std::string& content_type_b) {
+
+ cricket::SessionDescription* offer = new cricket::SessionDescription();
+ offer->AddContent(content_name_a, content_type_a,
+ new TestContentDescription(gingle_content_type,
+ content_type_a));
+ if (content_name_a != content_name_b) {
+ offer->AddContent(content_name_b, content_type_b,
+ new TestContentDescription(gingle_content_type,
+ content_type_b));
+ }
+ return offer;
+}
+
+cricket::SessionDescription* NewTestSessionDescription(
+ const std::string& content_name, const std::string& content_type) {
+
+ cricket::SessionDescription* offer = new cricket::SessionDescription();
+ offer->AddContent(content_name, content_type,
+ new TestContentDescription(content_type,
+ content_type));
+ return offer;
+}
+
+struct TestSessionClient: public cricket::SessionClient,
+ public sigslot::has_slots<> {
+ public:
+ TestSessionClient() {
+ }
+
+ ~TestSessionClient() {
+ }
+
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const cricket::ContentDescription** content,
+ cricket::ParseError* error) {
+ std::string content_type;
+ std::string gingle_content_type;
+ if (protocol == PROTOCOL_GINGLE) {
+ gingle_content_type = elem->Name().Namespace();
+ } else {
+ content_type = elem->Name().Namespace();
+ }
+
+ *content = new TestContentDescription(gingle_content_type, content_type);
+ return true;
+ }
+
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const cricket::ContentDescription* untyped_content,
+ buzz::XmlElement** elem,
+ cricket::WriteError* error) {
+ const TestContentDescription* content =
+ static_cast<const TestContentDescription*>(untyped_content);
+ std::string content_type = (protocol == PROTOCOL_GINGLE ?
+ content->gingle_content_type :
+ content->content_type);
+ *elem = new buzz::XmlElement(
+ buzz::QName(content_type, "description"), true);
+ return true;
+ }
+
+ void OnSessionCreate(cricket::Session* session, bool initiate) {
+ }
+
+ void OnSessionDestroy(cricket::Session* session) {
+ }
+};
+
+struct ChannelHandler : sigslot::has_slots<> {
+ explicit ChannelHandler(cricket::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);
+ }
+
+ bool writable() const {
+ return last_writable && channel->writable();
+ }
+
+ bool readable() const {
+ return last_readable && channel->readable();
+ }
+
+ void OnReadableState(cricket::TransportChannel* p) {
+ EXPECT_EQ(channel, p);
+ last_readable = channel->readable();
+ }
+
+ void OnWritableState(cricket::TransportChannel* p) {
+ EXPECT_EQ(channel, p);
+ last_writable = channel->writable();
+ }
+
+ void OnReadPacket(cricket::TransportChannel* p, const char* buf,
+ size_t size) {
+ EXPECT_EQ(channel, p);
+ EXPECT_LE(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);
+ EXPECT_EQ(static_cast<int>(size), result);
+ }
+
+ cricket::TransportChannel* channel;
+ bool last_readable, last_writable;
+ int data_count;
+ char last_data[4096];
+ size_t last_size;
+};
+
+void PrintStanza(const std::string& message,
+ const buzz::XmlElement* stanza) {
+ printf("%s: %s\n", message.c_str(), stanza->Str().c_str());
+}
+
+class TestClient : public sigslot::has_slots<> {
+ public:
+ TestClient(cricket::PortAllocator* port_allocator,
+ int* next_message_id,
+ const std::string& local_name,
+ SignalingProtocol start_protocol,
+ const std::string& content_type,
+ const std::string& content_name_a,
+ const std::string& channel_name_a,
+ const std::string& content_name_b,
+ const std::string& channel_name_b) {
+ Construct(port_allocator, next_message_id, local_name, start_protocol,
+ content_type, content_name_a, channel_name_a, std::string(""),
+ content_name_b, channel_name_b, std::string(""));
+ }
+
+ TestClient(cricket::PortAllocator* port_allocator,
+ int* next_message_id,
+ const std::string& local_name,
+ SignalingProtocol start_protocol,
+ const std::string& content_type,
+ const std::string& content_name_a,
+ const std::string& channel_name_a,
+ const std::string& channel_name_aa,
+ const std::string& content_name_b,
+ const std::string& channel_name_b,
+ const std::string& channel_name_bb) {
+ Construct(port_allocator, next_message_id, local_name, start_protocol,
+ content_type, content_name_a, channel_name_a, channel_name_aa,
+ content_name_b, channel_name_b, channel_name_bb);
+ }
+
+ ~TestClient() {
+ if (session) {
+ session_manager->DestroySession(session);
+ EXPECT_EQ(1U, session_destroyed_count);
+ }
+ delete session_manager;
+ delete client;
+ }
+
+ void Construct(cricket::PortAllocator* pa,
+ int* message_id,
+ const std::string& lname,
+ SignalingProtocol protocol,
+ const std::string& cont_type,
+ const std::string& cont_name_a,
+ const std::string& chan_name_a,
+ const std::string& chan_name_aa,
+ const std::string& cont_name_b,
+ const std::string& chan_name_b,
+ const std::string& chan_name_bb) {
+ port_allocator_ = pa;
+ next_message_id = message_id;
+ local_name = lname;
+ start_protocol = protocol;
+ content_type = cont_type;
+ content_name_a = cont_name_a;
+ channel_name_a = chan_name_a;
+ channel_name_aa = chan_name_aa;
+ content_name_b = cont_name_b;
+ channel_name_b = chan_name_b;
+ channel_name_bb = chan_name_bb;
+ session_created_count = 0;
+ session_destroyed_count = 0;
+ session_remote_description_update_count = 0;
+ last_expected_sent_stanza = NULL;
+ session = NULL;
+ last_session_state = cricket::BaseSession::STATE_INIT;
+ chan_a = NULL;
+ chan_b = NULL;
+ blow_up_on_error = true;
+ error_count = 0;
+ if (!chan_name_aa.empty() || !chan_name_bb.empty()) {
+ chan_aa = NULL;
+ chan_bb = NULL;
+ }
+
+ session_manager = new cricket::SessionManager(port_allocator_);
+ session_manager->SignalSessionCreate.connect(
+ this, &TestClient::OnSessionCreate);
+ session_manager->SignalSessionDestroy.connect(
+ this, &TestClient::OnSessionDestroy);
+ session_manager->SignalOutgoingMessage.connect(
+ this, &TestClient::OnOutgoingMessage);
+
+ client = new TestSessionClient();
+ session_manager->AddClient(content_type, client);
+ EXPECT_EQ(client, session_manager->GetClient(content_type));
+ }
+
+ uint32 sent_stanza_count() const {
+ return sent_stanzas.size();
+ }
+
+ const buzz::XmlElement* stanza() const {
+ return last_expected_sent_stanza;
+ }
+
+ cricket::BaseSession::State session_state() const {
+ EXPECT_EQ(last_session_state, session->state());
+ return session->state();
+ }
+
+ void SetSessionState(cricket::BaseSession::State state) {
+ session->SetState(state);
+ EXPECT_EQ_WAIT(last_session_state, session->state(), kEventTimeout);
+ }
+
+ void CreateSession() {
+ session_manager->CreateSession(local_name, content_type);
+ }
+
+ void DeliverStanza(const buzz::XmlElement* stanza) {
+ session_manager->OnIncomingMessage(stanza);
+ }
+
+ void DeliverStanza(const std::string& str) {
+ buzz::XmlElement* stanza = buzz::XmlElement::ForStr(str);
+ session_manager->OnIncomingMessage(stanza);
+ delete stanza;
+ }
+
+ void DeliverAckToLastStanza() {
+ const buzz::XmlElement* orig_stanza = stanza();
+ const buzz::XmlElement* response_stanza =
+ buzz::XmlElement::ForStr(IqAck(orig_stanza->Attr(buzz::QN_IQ), "", ""));
+ session_manager->OnIncomingResponse(orig_stanza, response_stanza);
+ delete response_stanza;
+ }
+
+ void ExpectSentStanza(const std::string& expected) {
+ EXPECT_TRUE(!sent_stanzas.empty()) <<
+ "Found no stanza when expected " << expected;
+
+ last_expected_sent_stanza = sent_stanzas.front();
+ sent_stanzas.pop_front();
+
+ std::string actual = last_expected_sent_stanza->Str();
+ EXPECT_EQ(expected, actual);
+ }
+
+ void SkipUnsentStanza() {
+ GetNextOutgoingMessageID();
+ }
+
+ bool HasTransport(const std::string& content_name) const {
+ ASSERT(session != NULL);
+ const cricket::Transport* transport = session->GetTransport(content_name);
+ return transport != NULL && (kTransportType == transport->type());
+ }
+
+ bool HasChannel(const std::string& content_name,
+ const std::string& channel_name) const {
+ ASSERT(session != NULL);
+ const cricket::TransportChannel* channel =
+ session->GetChannel(content_name, channel_name);
+ return channel != NULL && (channel_name == channel->name());
+ }
+
+ cricket::TransportChannel* GetChannel(const std::string& content_name,
+ const std::string& channel_name) const {
+ ASSERT(session != NULL);
+ return session->GetChannel(content_name, channel_name);
+ }
+
+ void OnSessionCreate(cricket::Session* created_session, bool initiate) {
+ session_created_count += 1;
+
+ session = created_session;
+ session->set_current_protocol(start_protocol);
+ session->set_allow_local_ips(true);
+ session->SignalState.connect(this, &TestClient::OnSessionState);
+ session->SignalError.connect(this, &TestClient::OnSessionError);
+ session->SignalRemoteDescriptionUpdate.connect(
+ this, &TestClient::OnSessionRemoteDescriptionUpdate);
+
+ CreateChannels();
+ }
+
+ void OnSessionDestroy(cricket::Session *session) {
+ session_destroyed_count += 1;
+ }
+
+ void OnSessionState(cricket::BaseSession* session,
+ cricket::BaseSession::State state) {
+ // EXPECT_EQ does not allow use of this, hence the tmp variable.
+ cricket::BaseSession* tmp = this->session;
+ EXPECT_EQ(tmp, session);
+ last_session_state = state;
+ }
+
+ void OnSessionError(cricket::BaseSession* session,
+ cricket::BaseSession::Error error) {
+ // EXPECT_EQ does not allow use of this, hence the tmp variable.
+ cricket::BaseSession* tmp = this->session;
+ EXPECT_EQ(tmp, session);
+ if (blow_up_on_error) {
+ EXPECT_TRUE(false);
+ } else {
+ error_count++;
+ }
+ }
+
+ void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
+ const cricket::ContentInfos& contents) {
+ session_remote_description_update_count++;
+ }
+
+ void PrepareCandidates() {
+ session_manager->OnSignalingReady();
+ }
+
+ void OnOutgoingMessage(cricket::SessionManager* manager,
+ const buzz::XmlElement* stanza) {
+ buzz::XmlElement* elem = new buzz::XmlElement(*stanza);
+ EXPECT_TRUE(elem->Name() == buzz::QN_IQ);
+ EXPECT_TRUE(elem->HasAttr(buzz::QN_TO));
+ EXPECT_FALSE(elem->HasAttr(buzz::QN_FROM));
+ EXPECT_TRUE(elem->HasAttr(buzz::QN_TYPE));
+ EXPECT_TRUE((elem->Attr(buzz::QN_TYPE) == "set") ||
+ (elem->Attr(buzz::QN_TYPE) == "result") ||
+ (elem->Attr(buzz::QN_TYPE) == "error"));
+
+ elem->SetAttr(buzz::QN_FROM, local_name);
+ if (elem->Attr(buzz::QN_TYPE) == "set") {
+ EXPECT_FALSE(elem->HasAttr(buzz::QN_ID));
+ elem->SetAttr(buzz::QN_ID, GetNextOutgoingMessageID());
+ }
+
+ // Uncommenting this is useful for debugging.
+ // PrintStanza("OutgoingMessage", elem);
+ sent_stanzas.push_back(elem);
+ }
+
+ std::string GetNextOutgoingMessageID() {
+ int message_id = (*next_message_id)++;
+ std::ostringstream ost;
+ ost << message_id;
+ return ost.str();
+ }
+
+ void CreateChannels() {
+ ASSERT(session != NULL);
+ chan_a = new ChannelHandler(
+ session->CreateChannel(content_name_a, channel_name_a));
+ chan_b = new ChannelHandler(
+ session->CreateChannel(content_name_b, channel_name_b));
+ if (!channel_name_aa.empty() && !channel_name_bb.empty()) {
+ chan_aa = new ChannelHandler(
+ session->CreateChannel(content_name_a, channel_name_aa));
+ chan_bb = new ChannelHandler(
+ session->CreateChannel(content_name_b, channel_name_bb));
+ }
+ }
+
+ int* next_message_id;
+ std::string local_name;
+ SignalingProtocol start_protocol;
+ std::string content_type;
+ std::string content_name_a;
+ std::string channel_name_a;
+ std::string channel_name_aa;
+ std::string content_name_b;
+ std::string channel_name_b;
+ std::string channel_name_bb;
+
+ uint32 session_created_count;
+ uint32 session_destroyed_count;
+ uint32 session_remote_description_update_count;
+ std::deque<buzz::XmlElement*> sent_stanzas;
+ buzz::XmlElement* last_expected_sent_stanza;
+
+ cricket::SessionManager* session_manager;
+ TestSessionClient* client;
+ cricket::PortAllocator* port_allocator_;
+ cricket::Session* session;
+ cricket::BaseSession::State last_session_state;
+ ChannelHandler* chan_a;
+ ChannelHandler* chan_aa;
+ ChannelHandler* chan_b;
+ ChannelHandler* chan_bb;
+ bool blow_up_on_error;
+ int error_count;
+};
+
+class SessionTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Seed needed for each test to satisfy expectations.
+ talk_base::SetRandomTestMode(true);
+ }
+
+ virtual void TearDown() {
+ talk_base::SetRandomTestMode(false);
+ }
+
+ // Tests sending data between two clients, over two channels.
+ void TestSendRecv(ChannelHandler* chan1a,
+ ChannelHandler* chan1b,
+ ChannelHandler* chan2a,
+ ChannelHandler* chan2b) {
+ const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam";
+ const char* dat2a = "mapssnaebdekabmapsmapsmapsmapsmapsmapsmaps";
+ const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce...";
+ const char* dat2b = "...ecuas yanrom a htiw etteverC a rodimrehT retsboL";
+
+ for (int i = 0; i < 20; i++) {
+ chan1a->Send(dat1a, strlen(dat1a));
+ chan1b->Send(dat1b, strlen(dat1b));
+ chan2a->Send(dat2a, strlen(dat2a));
+ chan2b->Send(dat2b, strlen(dat2b));
+
+ EXPECT_EQ_WAIT(i + 1, chan1a->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan1b->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan2a->data_count, kEventTimeout);
+ EXPECT_EQ_WAIT(i + 1, chan2b->data_count, kEventTimeout);
+
+ EXPECT_EQ(strlen(dat2a), chan1a->last_size);
+ EXPECT_EQ(strlen(dat2b), chan1b->last_size);
+ EXPECT_EQ(strlen(dat1a), chan2a->last_size);
+ EXPECT_EQ(strlen(dat1b), chan2b->last_size);
+
+ EXPECT_EQ(0, std::memcmp(chan1a->last_data, dat2a,
+ strlen(dat2a)));
+ EXPECT_EQ(0, std::memcmp(chan1b->last_data, dat2b,
+ strlen(dat2b)));
+ EXPECT_EQ(0, std::memcmp(chan2a->last_data, dat1a,
+ strlen(dat1a)));
+ EXPECT_EQ(0, std::memcmp(chan2b->last_data, dat1b,
+ strlen(dat1b)));
+ }
+ }
+
+ // Test an initiate from one client to another, each with
+ // independent initial protocols. Checks for the correct initiates,
+ // candidates, and accept messages, and tests that working network
+ // channels are established.
+ void TestSession(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol,
+ const std::string& gingle_content_type,
+ const std::string& content_type,
+ const std::string& content_name_a,
+ const std::string& channel_name_a,
+ const std::string& content_name_b,
+ const std::string& channel_name_b,
+ const std::string& initiate_xml,
+ const std::string& transport_info_a_xml,
+ const std::string& transport_info_b_xml,
+ const std::string& transport_info_reply_a_xml,
+ const std::string& transport_info_reply_b_xml,
+ const std::string& accept_xml) {
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, initiator_protocol,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b));
+ talk_base::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, responder_protocol,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ EXPECT_TRUE(initiator->HasTransport(content_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_a));
+ EXPECT_TRUE(initiator->HasTransport(content_name_b));
+ EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_b));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Deliver the initiate. Expect ack and session created with
+ // transports.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("0", kResponder, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), kResponder);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasTransport(content_name_a));
+ EXPECT_TRUE(responder->HasChannel(content_name_a, channel_name_a));
+ EXPECT_TRUE(responder->HasTransport(content_name_b));
+ EXPECT_TRUE(responder->HasChannel(content_name_b, channel_name_b));
+
+ // Expect transport-info message from initiator.
+ // But don't send candidates until initiate ack is received.
+ initiator->PrepareCandidates();
+ WAIT(initiator->sent_stanza_count() > 0, 100);
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ initiator->DeliverAckToLastStanza();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_a_xml));
+
+ // Deliver transport-info and expect ack.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("1", kResponder, kInitiator));
+
+ if (!transport_info_b_xml.empty()) {
+ // Expect second transport-info message from initiator.
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("2", kInitiator, kResponder, transport_info_b_xml));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Deliver second transport-info message and expect ack.
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("2", kResponder, kInitiator));
+ } else {
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+ initiator->SkipUnsentStanza();
+ }
+
+ // Expect reply transport-info message from responder.
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("3", kResponder, kInitiator, transport_info_reply_a_xml));
+
+ // Deliver reply transport-info and expect ack.
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("3", kInitiator, kResponder));
+
+ if (!transport_info_reply_b_xml.empty()) {
+ // Expect second reply transport-info message from responder.
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("4", kResponder, kInitiator, transport_info_reply_b_xml));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver second reply transport-info message and expect ack.
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("4", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ } else {
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+ responder->SkipUnsentStanza();
+ }
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ EXPECT_TRUE(responder->session->Accept(answer));
+ EXPECT_EQ(responder->session->local_description(), answer);
+
+ responder->ExpectSentStanza(
+ IqSet("5", kResponder, kInitiator, accept_xml));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver the accept message and expect an ack.
+ initiator->DeliverStanza(responder->stanza());
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("5", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ(resulting_protocol, initiator->session->current_protocol());
+ EXPECT_EQ(resulting_protocol, responder->session->current_protocol());
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ TestSendRecv(initiator->chan_a, initiator->chan_b,
+ responder->chan_a, responder->chan_b);
+
+ if (resulting_protocol == PROTOCOL_JINGLE) {
+ // Deliver a description-info message to the initiator and check if the
+ // content description changes.
+ EXPECT_EQ(0U, initiator->session_remote_description_update_count);
+
+ const cricket::SessionDescription* old_session_desc =
+ initiator->session->remote_description();
+ const cricket::ContentInfo* old_content_a =
+ old_session_desc->GetContentByName(content_name_a);
+ const cricket::ContentDescription* old_content_desc_a =
+ old_content_a->description;
+ const cricket::ContentInfo* old_content_b =
+ old_session_desc->GetContentByName(content_name_b);
+ const cricket::ContentDescription* old_content_desc_b =
+ old_content_b->description;
+ EXPECT_TRUE(old_content_desc_a != NULL);
+ EXPECT_TRUE(old_content_desc_b != NULL);
+
+ LOG(LS_INFO) << "A " << old_content_a->name;
+ LOG(LS_INFO) << "B " << old_content_b->name;
+
+ std::string description_info_xml =
+ JingleDescriptionInfoXml(content_name_a, content_type);
+ initiator->DeliverStanza(
+ IqSet("6", kResponder, kInitiator, description_info_xml));
+ responder->SkipUnsentStanza();
+ EXPECT_EQ(1U, initiator->session_remote_description_update_count);
+
+ const cricket::SessionDescription* new_session_desc =
+ initiator->session->remote_description();
+ const cricket::ContentInfo* new_content_a =
+ new_session_desc->GetContentByName(content_name_a);
+ const cricket::ContentDescription* new_content_desc_a =
+ new_content_a->description;
+ const cricket::ContentInfo* new_content_b =
+ new_session_desc->GetContentByName(content_name_b);
+ const cricket::ContentDescription* new_content_desc_b =
+ new_content_b->description;
+ EXPECT_TRUE(new_content_desc_a != NULL);
+ EXPECT_TRUE(new_content_desc_b != NULL);
+
+ // TODO: We used to replace contents from an update, but
+ // that no longer works with partial updates. We need to figure out
+ // a way to merge patial updates into contents. For now, users of
+ // Session should listen to SignalRemoteDescriptionUpdate and handle
+ // updates. They should not expect remote_description to be the
+ // latest value.
+ // See session.cc OnDescriptionInfoMessage.
+
+ // EXPECT_NE(old_content_desc_a, new_content_desc_a);
+
+ // if (content_name_a != content_name_b) {
+ // // If content_name_a != content_name_b, then b's content description
+ // // should not have changed since the description-info message only
+ // // contained an update for content_name_a.
+ // EXPECT_EQ(old_content_desc_b, new_content_desc_b);
+ // }
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("6", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+ } else {
+ responder->SkipUnsentStanza();
+ }
+
+ initiator->session->Terminate();
+ initiator->ExpectSentStanza(
+ IqSet("7", kInitiator, kResponder,
+ TerminateXml(resulting_protocol,
+ cricket::STR_TERMINATE_SUCCESS)));
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("7", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+ initiator->session_state());
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ responder->session_state());
+ }
+
+ // Test an initiate with other content, called "main".
+ void TestOtherContent(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string content_name_a = content_name;
+ std::string channel_name_a = "rtcp";
+ std::string content_name_b = content_name;
+ std::string channel_name_b = "rtp";
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ content_name_a, content_type);
+ std::string transport_info_a_xml = TransportInfo4Xml(
+ initiator_protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_b_xml = "";
+ std::string transport_info_reply_a_xml = TransportInfo4Xml(
+ resulting_protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string transport_info_reply_b_xml = "";
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ content_name_a, content_type);
+
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ content_type,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ // Test an initiate with audio content.
+ void TestAudioContent(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string gingle_content_type = cricket::NS_GINGLE_AUDIO;
+ std::string content_name = cricket::CN_AUDIO;
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string channel_name_a = "rtcp";
+ std::string channel_name_b = "rtp";
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ gingle_content_type,
+ content_name, content_type,
+ "", "");
+ std::string transport_info_a_xml = TransportInfo4Xml(
+ initiator_protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_b_xml = "";
+ std::string transport_info_reply_a_xml = TransportInfo4Xml(
+ resulting_protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string transport_info_reply_b_xml = "";
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ gingle_content_type,
+ content_name, content_type,
+ "", "");
+
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ gingle_content_type,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ // Since media content is "split" into two contents (audio and
+ // video), we need to treat it special.
+ void TestVideoContents(SignalingProtocol initiator_protocol,
+ SignalingProtocol responder_protocol,
+ SignalingProtocol resulting_protocol) {
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+ std::string content_name_a = cricket::CN_AUDIO;
+ std::string channel_name_a = "rtcp";
+ std::string content_name_b = cricket::CN_VIDEO;
+ std::string channel_name_b = "video_rtp";
+
+ std::string initiate_xml = InitiateXml(
+ initiator_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ std::string transport_info_a_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_a,
+ channel_name_a, 0, 1);
+ std::string transport_info_b_xml = TransportInfo2Xml(
+ initiator_protocol, content_name_b,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_a_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_a,
+ channel_name_a, 4, 5);
+ std::string transport_info_reply_b_xml = TransportInfo2Xml(
+ resulting_protocol, content_name_b,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ resulting_protocol,
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+
+ TestSession(initiator_protocol, responder_protocol, resulting_protocol,
+ gingle_content_type,
+ content_type,
+ content_name_a, channel_name_a,
+ content_name_b, channel_name_b,
+ initiate_xml,
+ transport_info_a_xml, transport_info_b_xml,
+ transport_info_reply_a_xml, transport_info_reply_b_xml,
+ accept_xml);
+ }
+
+ void TestBadRedirect(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "chana";
+ std::string channel_name_b = "chanb";
+ std::string initiate_xml = InitiateXml(
+ protocol, content_name, content_type);
+ std::string transport_info_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ protocol, content_name, content_type);
+ std::string responder_full = kResponder + "/full";
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ talk_base::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ responder_full, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Expect transport-info message from initiator.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+ // Send an unauthorized redirect to the initiator and expect it be ignored.
+ initiator->blow_up_on_error = false;
+ const buzz::XmlElement* initiate_stanza = initiator->stanza();
+ talk_base::scoped_ptr<buzz::XmlElement> redirect_stanza(
+ buzz::XmlElement::ForStr(
+ IqError("ER", kResponder, kInitiator,
+ RedirectXml(protocol, initiate_xml, "not@allowed.com"))));
+ initiator->session_manager->OnFailedSend(
+ initiate_stanza, redirect_stanza.get());
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ initiator->blow_up_on_error = true;
+ EXPECT_EQ(initiator->error_count, 1);
+ }
+
+ void TestGoodRedirect(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "chana";
+ std::string channel_name_b = "chanb";
+ std::string initiate_xml = InitiateXml(
+ protocol, content_name, content_type);
+ std::string transport_info_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3);
+ std::string transport_info_reply_xml = TransportInfo4Xml(
+ protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7);
+ std::string accept_xml = AcceptXml(
+ protocol, content_name, content_type);
+ std::string responder_full = kResponder + "/full";
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ talk_base::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ responder_full, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_EQ(1U, initiator->session_created_count);
+ EXPECT_EQ(kSessionId, initiator->session->id());
+ EXPECT_EQ(initiator->session->local_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_INIT,
+ initiator->session_state());
+
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+ EXPECT_EQ(initiator->session->remote_name(), kResponder);
+ EXPECT_EQ(initiator->session->local_description(), offer);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder, initiate_xml));
+
+ // Expect transport-info message from initiator.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder, transport_info_xml));
+
+ // Send a redirect to the initiator and expect all of the message
+ // to be resent.
+ const buzz::XmlElement* initiate_stanza = initiator->stanza();
+ talk_base::scoped_ptr<buzz::XmlElement> redirect_stanza(
+ buzz::XmlElement::ForStr(
+ IqError("ER2", kResponder, kInitiator,
+ RedirectXml(protocol, initiate_xml, responder_full))));
+ initiator->session_manager->OnFailedSend(
+ initiate_stanza, redirect_stanza.get());
+ EXPECT_EQ(initiator->session->remote_name(), responder_full);
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("2", kInitiator, responder_full, initiate_xml));
+ initiator->ExpectSentStanza(
+ IqSet("3", kInitiator, responder_full, transport_info_xml));
+
+ // Deliver the initiate. Expect ack and session created with
+ // transports.
+ responder->DeliverStanza(
+ IqSet("2", kInitiator, responder_full, initiate_xml));
+ responder->ExpectSentStanza(
+ IqAck("2", responder_full, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), responder_full);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasChannel(content_name, channel_name_a));
+ EXPECT_TRUE(responder->HasChannel(content_name, channel_name_b));
+
+ // Deliver transport-info and expect ack.
+ responder->DeliverStanza(
+ IqSet("3", kInitiator, responder_full, transport_info_xml));
+ responder->ExpectSentStanza(
+ IqAck("3", responder_full, kInitiator));
+
+ // Expect reply transport-infos sent to new remote JID
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("4", responder_full, kInitiator, transport_info_reply_xml));
+
+ initiator->DeliverStanza(responder->stanza());
+ initiator->ExpectSentStanza(
+ IqAck("4", kInitiator, responder_full));
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(responder->session->Accept(answer));
+ EXPECT_EQ(responder->session->local_description(), answer);
+
+ responder->ExpectSentStanza(
+ IqSet("5", responder_full, kInitiator, accept_xml));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Deliver the accept message and expect an ack.
+ initiator->DeliverStanza(responder->stanza());
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("5", kInitiator, responder_full));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ TestSendRecv(initiator->chan_a, initiator->chan_b,
+ responder->chan_a, responder->chan_b);
+ }
+
+ void TestCandidatesInInitiateAndAccept(const std::string& test_name) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+ std::string channel_name_a = "rtcp";
+ std::string channel_name_b = "rtp";
+ cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ talk_base::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, protocol,
+ content_type,
+ content_name, channel_name_a,
+ content_name, channel_name_b));
+
+ // Create Session and check channels and state.
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->HasTransport(content_name));
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_a));
+ EXPECT_TRUE(initiator->HasTransport(content_name));
+ EXPECT_TRUE(initiator->HasChannel(content_name, channel_name_b));
+
+ // Initiate and expect initiate message sent.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(initiator->session->Initiate(kResponder, offer));
+
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+
+ // Fake the delivery the initiate and candidates together.
+ responder->DeliverStanza(
+ IqSet("A", kInitiator, kResponder,
+ JingleInitiateActionXml(
+ JingleContentXml(
+ content_name, content_type, kTransportType,
+ P2pCandidateXml(channel_name_a, 0) +
+ P2pCandidateXml(channel_name_a, 1) +
+ P2pCandidateXml(channel_name_b, 2) +
+ P2pCandidateXml(channel_name_b, 3)))));
+ responder->ExpectSentStanza(
+ IqAck("A", kResponder, kInitiator));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ EXPECT_EQ(1U, responder->session_created_count);
+ EXPECT_EQ(kSessionId, responder->session->id());
+ EXPECT_EQ(responder->session->local_name(), kResponder);
+ EXPECT_EQ(responder->session->remote_name(), kInitiator);
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ EXPECT_TRUE(responder->HasTransport(content_name));
+ EXPECT_TRUE(responder->HasChannel(content_name, channel_name_a));
+ EXPECT_TRUE(responder->HasTransport(content_name));
+ EXPECT_TRUE(responder->HasChannel(content_name, channel_name_b));
+
+ // Expect transport-info message from initiator.
+ // But don't send candidates until initiate ack is received.
+ initiator->DeliverAckToLastStanza();
+ initiator->PrepareCandidates();
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder,
+ TransportInfo4Xml(protocol, content_name,
+ channel_name_a, 0, 1,
+ channel_name_b, 2, 3)));
+
+ responder->PrepareCandidates();
+ EXPECT_TRUE_WAIT(responder->sent_stanza_count() > 0, kEventTimeout);
+ responder->ExpectSentStanza(
+ IqSet("2", kResponder, kInitiator,
+ TransportInfo4Xml(protocol, content_name,
+ channel_name_a, 4, 5,
+ channel_name_b, 6, 7)));
+
+ // Accept the session and expect accept stanza.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ content_name, content_type);
+ EXPECT_TRUE(responder->session->Accept(answer));
+
+ responder->ExpectSentStanza(
+ IqSet("3", kResponder, kInitiator,
+ AcceptXml(protocol, content_name, content_type)));
+ EXPECT_EQ(0U, responder->sent_stanza_count());
+
+ // Fake the delivery the accept and candidates together.
+ initiator->DeliverStanza(
+ IqSet("B", kResponder, kInitiator,
+ JingleActionXml("session-accept",
+ JingleContentXml(
+ content_name, content_type, kTransportType,
+ P2pCandidateXml(channel_name_a, 4) +
+ P2pCandidateXml(channel_name_a, 5) +
+ P2pCandidateXml(channel_name_b, 6) +
+ P2pCandidateXml(channel_name_b, 7)))));
+ EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
+ initiator->ExpectSentStanza(
+ IqAck("B", kInitiator, kResponder));
+ EXPECT_EQ(0U, initiator->sent_stanza_count());
+
+ // The channels should be able to become writable at this point. This
+ // requires pinging, so it may take a little while.
+ EXPECT_TRUE_WAIT(initiator->chan_a->writable() &&
+ initiator->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(initiator->chan_b->writable() &&
+ initiator->chan_b->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_a->writable() &&
+ responder->chan_a->readable(), kEventTimeout);
+ EXPECT_TRUE_WAIT(responder->chan_b->writable() &&
+ responder->chan_b->readable(), kEventTimeout);
+
+
+ // Both sessions should be in progress and have functioning
+ // channels.
+ EXPECT_EQ(protocol, initiator->session->current_protocol());
+ EXPECT_EQ(protocol, responder->session->current_protocol());
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ initiator->session_state(), kEventTimeout);
+ EXPECT_EQ_WAIT(cricket::BaseSession::STATE_INPROGRESS,
+ responder->session_state(), kEventTimeout);
+ TestSendRecv(initiator->chan_a, initiator->chan_b,
+ responder->chan_a, responder->chan_b);
+ }
+
+ // Tests that when an initiator terminates right after initiate,
+ // everything behaves correctly.
+ void TestEarlyTerminationFromInitiator(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ talk_base::scoped_ptr<TestClient> responder(
+ new TestClient(allocator.get(), &next_message_id,
+ kResponder, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ // Send initiate
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->session->Initiate(
+ kResponder, NewTestSessionDescription(content_name, content_type)));
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("0", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDINITIATE,
+ responder->session_state());
+
+ initiator->session->TerminateWithReason(cricket::STR_TERMINATE_ERROR);
+ initiator->ExpectSentStanza(
+ IqSet("1", kInitiator, kResponder,
+ TerminateXml(protocol, cricket::STR_TERMINATE_ERROR)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTTERMINATE,
+ initiator->session_state());
+
+ responder->DeliverStanza(initiator->stanza());
+ responder->ExpectSentStanza(
+ IqAck("1", kResponder, kInitiator));
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ responder->session_state());
+ }
+
+ // Tests that when the responder rejects, everything behaves
+ // correctly.
+ void TestRejection(SignalingProtocol protocol) {
+ std::string content_name = "main";
+ std::string content_type = "http://oink.splat/session";
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name, "a",
+ content_name, "b"));
+
+ // Send initiate
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->session->Initiate(
+ kResponder, NewTestSessionDescription(content_name, content_type)));
+ initiator->ExpectSentStanza(
+ IqSet("0", kInitiator, kResponder,
+ InitiateXml(protocol, content_name, content_type)));
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+
+ initiator->DeliverStanza(
+ IqSet("1", kResponder, kInitiator,
+ RejectXml(protocol, cricket::STR_TERMINATE_ERROR)));
+ initiator->ExpectSentStanza(
+ IqAck("1", kInitiator, kResponder));
+ if (protocol == PROTOCOL_JINGLE) {
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDTERMINATE,
+ initiator->session_state());
+ } else {
+ EXPECT_EQ(cricket::BaseSession::STATE_RECEIVEDREJECT,
+ initiator->session_state());
+ }
+ }
+
+ void TestTransportMux() {
+ std::string content_type = cricket::NS_JINGLE_RTP;
+ std::string gingle_content_type = cricket::NS_GINGLE_VIDEO;
+ std::string content_name_a = cricket::CN_AUDIO;
+ std::string channel_name_a = "rtp";
+ std::string channel_name_aa = "rtcp";
+ std::string content_name_b = cricket::CN_VIDEO;
+ std::string channel_name_b = "video_rtp";
+ std::string channel_name_bb = "video_rtcp";
+ cricket::SignalingProtocol protocol = PROTOCOL_JINGLE;
+
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(
+ new TestPortAllocator());
+ int next_message_id = 0;
+
+ talk_base::scoped_ptr<TestClient> initiator(
+ new TestClient(allocator.get(), &next_message_id,
+ kInitiator, protocol,
+ content_type,
+ content_name_a, channel_name_a, channel_name_aa,
+ content_name_b, channel_name_b, channel_name_bb));
+
+ // First creating the offer and answer session descriptions required for
+ // testing.
+ cricket::SessionDescription* offer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ // Add group information to the offer
+ cricket::ContentGroup group(cricket::GN_BUNDLE);
+ group.AddContentName(content_name_a);
+ group.AddContentName(content_name_b);
+ EXPECT_TRUE(group.HasContentName(content_name_a));
+ EXPECT_TRUE(group.HasContentName(content_name_b));
+ offer->AddGroup(group);
+
+ // Creating answer for the offer.
+ cricket::SessionDescription* answer = NewTestSessionDescription(
+ gingle_content_type,
+ content_name_a, content_type,
+ content_name_b, content_type);
+ // Check if group "TOGETHER" exists in the offer. If it's present then
+ // remote supports muxing.
+ EXPECT_TRUE(offer->HasGroup(cricket::GN_BUNDLE));
+ const cricket::ContentGroup* group_offer =
+ offer->GetGroupByName(cricket::GN_BUNDLE);
+ // Not creating new copy in answer, for test we can use this for test.
+ answer->AddGroup(*group_offer);
+ EXPECT_TRUE(answer->HasGroup(cricket::GN_BUNDLE));
+
+ initiator->CreateSession();
+ EXPECT_TRUE(initiator->session->Initiate(
+ kResponder, offer));
+
+ EXPECT_TRUE(initiator->HasTransport(content_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_a));
+ EXPECT_TRUE(initiator->HasChannel(content_name_a, channel_name_aa));
+ EXPECT_TRUE(initiator->HasTransport(content_name_b));
+ EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_b));
+ EXPECT_TRUE(initiator->HasChannel(content_name_b, channel_name_bb));
+ // This test will not create initiator and responder. Manually change
+ // session state and invoke methods.
+ initiator->PrepareCandidates();
+ EXPECT_EQ(cricket::BaseSession::STATE_SENTINITIATE,
+ initiator->session_state());
+ // Now apply answer to the session and move session state to
+ // STATE_RECEIVEDACCEPT
+ initiator->session->set_remote_description(answer);
+ initiator->session->SetState(cricket::BaseSession::STATE_RECEIVEDACCEPT);
+ cricket::TransportChannel* chan_a =
+ initiator->GetChannel(content_name_a, channel_name_a);
+ cricket::TransportChannel* chan_b =
+ initiator->GetChannel(content_name_b, channel_name_b);
+ // Since we know these are TransportChannelProxy, type cast it.
+ cricket::TransportChannelProxy* proxy_chan_a =
+ static_cast<cricket::TransportChannelProxy*>(chan_a);
+ cricket::TransportChannelProxy* proxy_chan_b =
+ static_cast<cricket::TransportChannelProxy*>(chan_b);
+ EXPECT_EQ(proxy_chan_a->impl(), proxy_chan_b->impl());
+ cricket::TransportChannel* chan_aa =
+ initiator->GetChannel(content_name_a, channel_name_aa);
+ cricket::TransportChannel* chan_bb =
+ initiator->GetChannel(content_name_b, channel_name_bb);
+ cricket::TransportChannelProxy* proxy_chan_aa =
+ static_cast<cricket::TransportChannelProxy*>(chan_aa);
+ cricket::TransportChannelProxy* proxy_chan_bb =
+ static_cast<cricket::TransportChannelProxy*>(chan_bb);
+ EXPECT_EQ(proxy_chan_aa->impl(), proxy_chan_bb->impl());
+ // TODO - Add test code to send data after mux is enabled.
+ }
+};
+
+// For each of these, "X => Y = Z" means "if a client with protocol X
+// initiates to a client with protocol Y, they end up speaking protocol Z.
+
+// Gingle => Gingle = Gingle (with other content)
+TEST_F(SessionTest, GingleToGingleOtherContent) {
+ TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, GingleToGingleAudioContent) {
+ TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Gingle => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, GingleToGingleVideoContents) {
+ TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+
+// Jingle => Jingle = Jingle (with other content)
+TEST_F(SessionTest, JingleToJingleOtherContent) {
+ TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, JingleToJingleAudioContent) {
+ TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Jingle => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, JingleToJingleVideoContents) {
+ TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+
+// Hybrid => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, HybridToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, HybridToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, HybridToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+
+// Gingle => Hybrid = Gingle (with other content)
+TEST_F(SessionTest, GingleToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with audio content)
+TEST_F(SessionTest, GingleToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+// Gingle => Hybrid = Gingle (with video contents)
+TEST_F(SessionTest, GingleToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_GINGLE, PROTOCOL_HYBRID, PROTOCOL_GINGLE);
+}
+
+
+// Jingle => Hybrid = Jingle (with other content)
+TEST_F(SessionTest, JingleToHybridOtherContent) {
+ TestOtherContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with audio content)
+TEST_F(SessionTest, JingleToHybridAudioContent) {
+ TestAudioContent(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+// Jingle => Hybrid = Jingle (with video contents)
+TEST_F(SessionTest, JingleToHybridVideoContents) {
+ TestVideoContents(PROTOCOL_JINGLE, PROTOCOL_HYBRID, PROTOCOL_JINGLE);
+}
+
+
+// Hybrid => Gingle = Gingle (with other content)
+TEST_F(SessionTest, HybridToGingleOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with audio content)
+TEST_F(SessionTest, HybridToGingleAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+// Hybrid => Gingle = Gingle (with video contents)
+TEST_F(SessionTest, HybridToGingleVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_GINGLE, PROTOCOL_GINGLE);
+}
+
+
+// Hybrid => Jingle = Jingle (with other content)
+TEST_F(SessionTest, HybridToJingleOtherContent) {
+ TestOtherContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with audio content)
+TEST_F(SessionTest, HybridToJingleAudioContent) {
+ TestAudioContent(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+// Hybrid => Jingle = Jingle (with video contents)
+TEST_F(SessionTest, HybridToJingleVideoContents) {
+ TestVideoContents(PROTOCOL_HYBRID, PROTOCOL_JINGLE, PROTOCOL_JINGLE);
+}
+
+
+TEST_F(SessionTest, GingleEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, HybridEarlyTerminationFromInitiator) {
+ TestEarlyTerminationFromInitiator(PROTOCOL_HYBRID);
+}
+
+
+TEST_F(SessionTest, GingleRejection) {
+ TestRejection(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleRejection) {
+ TestRejection(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleGoodRedirect) {
+ TestGoodRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleGoodRedirect) {
+ TestGoodRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, GingleBadRedirect) {
+ TestBadRedirect(PROTOCOL_GINGLE);
+}
+
+TEST_F(SessionTest, JingleBadRedirect) {
+ TestBadRedirect(PROTOCOL_JINGLE);
+}
+
+TEST_F(SessionTest, TestCandidatesInInitiateAndAccept) {
+ TestCandidatesInInitiateAndAccept("Candidates in initiate/accept");
+}
+
+TEST_F(SessionTest, TestTransportMux) {
+ TestTransportMux();
+}
diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc
index 6554f5a..add9236 100644
--- a/talk/p2p/base/sessiondescription.cc
+++ b/talk/p2p/base/sessiondescription.cc
@@ -53,6 +53,29 @@
return NULL;
}
+void ContentGroup::AddContentName(const std::string& content_name) {
+ content_types_.insert(content_name);
+}
+
+bool ContentGroup::RemoveContentName(const std::string& content_name) {
+ bool ret = false;
+ std::set<std::string>::iterator iter;
+ iter = content_types_.find(content_name);
+ if (iter != content_types_.end()) {
+ content_types_.erase(iter);
+ ret = true;
+ }
+ return ret;
+}
+
+bool ContentGroup::HasContentName(const std::string& content_name) const {
+ return (content_types_.find(content_name) != content_types_.end());
+}
+
+const std::string* ContentGroup::FirstContentName() const {
+ return (content_types_.begin() != content_types_.end()) ?
+ &(*content_types_.begin()) : NULL;
+}
const ContentInfo* SessionDescription::GetContentByName(
const std::string& name) const {
return FindContentInfoByName(contents_, name);
@@ -63,6 +86,10 @@
return FindContentInfoByType(contents_, type);
}
+const ContentInfo* SessionDescription::FirstContent() const {
+ return (contents_.empty()) ? NULL : &(*contents_.begin());
+}
+
void SessionDescription::AddContent(const std::string& name,
const std::string& type,
const ContentDescription* description) {
@@ -82,4 +109,34 @@
return false;
}
+void SessionDescription::RemoveGroupByName(const std::string& name) {
+ for (ContentGroups::iterator iter = groups_.begin();
+ iter != groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ groups_.erase(iter);
+ }
+ }
+}
+
+bool SessionDescription::HasGroup(const std::string& name) const {
+ for (ContentGroups::const_iterator iter = groups_.begin();
+ iter != groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const ContentGroup* SessionDescription::GetGroupByName(
+ const std::string& name) const {
+ for (ContentGroups::const_iterator iter = groups_.begin();
+ iter != groups_.end(); ++iter) {
+ if (iter->semantics() == name) {
+ return &(*iter);
+ }
+ }
+ return NULL;
+}
+
} // namespace cricket
diff --git a/talk/p2p/base/sessiondescription.h b/talk/p2p/base/sessiondescription.h
index 38c902b..153fe97 100644
--- a/talk/p2p/base/sessiondescription.h
+++ b/talk/p2p/base/sessiondescription.h
@@ -28,6 +28,7 @@
#ifndef TALK_P2P_BASE_SESSIONDESCRIPTION_H_
#define TALK_P2P_BASE_SESSIONDESCRIPTION_H_
+#include <set>
#include <string>
#include <vector>
@@ -55,7 +56,28 @@
const ContentDescription* description;
};
+// This class provides a mechanism to aggregate different media contents into a
+// group. This group can also be shared with the peers in a pre-defined format.
+// GroupInfo should be populated only with the |content_name| of the
+// MediaDescription.
+class ContentGroup {
+ public:
+ explicit ContentGroup(const std::string& semantics) :
+ semantics_(semantics) {}
+ void AddContentName(const std::string& content_name);
+ bool RemoveContentName(const std::string& content_name);
+ bool HasContentName(const std::string& content_name) const;
+ const std::string* FirstContentName() const;
+ const std::string& semantics() const { return semantics_; }
+
+ private:
+ std::string semantics_;
+ std::set<std::string> content_types_;
+};
+
typedef std::vector<ContentInfo> ContentInfos;
+typedef std::vector<ContentGroup> ContentGroups;
+
const ContentInfo* FindContentInfoByName(
const ContentInfos& contents, const std::string& name);
const ContentInfo* FindContentInfoByType(
@@ -71,6 +93,7 @@
contents_(contents) {}
const ContentInfo* GetContentByName(const std::string& name) const;
const ContentInfo* FirstContentByType(const std::string& type) const;
+ const ContentInfo* FirstContent() const;
// Takes ownership of ContentDescription*.
void AddContent(const std::string& name,
const std::string& type,
@@ -84,9 +107,14 @@
delete content->description;
}
}
+ bool HasGroup(const std::string& name) const;
+ void AddGroup(const ContentGroup& group) { groups_.push_back(group); }
+ void RemoveGroupByName(const std::string& name);
+ const ContentGroup* GetGroupByName(const std::string& name) const;
private:
ContentInfos contents_;
+ ContentGroups groups_;
};
// Indicates whether a ContentDescription was an offer or an answer, as
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
index ae2e656..96f91d1 100644
--- a/talk/p2p/base/sessionmessages.cc
+++ b/talk/p2p/base/sessionmessages.cc
@@ -73,9 +73,9 @@
if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
return ACTION_TRANSPORT_ACCEPT;
if (type == JINGLE_ACTION_DESCRIPTION_INFO)
- return ACTION_UPDATE;
+ return ACTION_DESCRIPTION_INFO;
if (type == GINGLE_ACTION_UPDATE)
- return ACTION_UPDATE;
+ return ACTION_DESCRIPTION_INFO;
return ACTION_UNKNOWN;
}
@@ -362,7 +362,7 @@
}
buzz::XmlElement* NewTransportElement(const std::string& name) {
- return new buzz::XmlElement(buzz::QName(true, name, LN_TRANSPORT), true);
+ return new buzz::XmlElement(buzz::QName(name, LN_TRANSPORT), true);
}
bool WriteCandidates(SignalingProtocol protocol,
@@ -678,6 +678,7 @@
static bool ParseContentMessage(
SignalingProtocol protocol,
const buzz::XmlElement* action_elem,
+ bool expect_transports,
const ContentParserMap& content_parsers,
const TransportParserMap& trans_parsers,
SessionInitiate* init,
@@ -688,7 +689,8 @@
&init->contents, error))
return false;
- if (!ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
+ if (expect_transports &&
+ !ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
&init->transports, error))
return false;
} else {
@@ -696,7 +698,8 @@
&init->contents, error))
return false;
- if (!ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
+ if (expect_transports &&
+ !ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
&init->transports, error))
return false;
}
@@ -735,7 +738,8 @@
const TransportParserMap& trans_parsers,
SessionInitiate* init,
ParseError* error) {
- return ParseContentMessage(protocol, action_elem,
+ bool expect_transports = true;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
content_parsers, trans_parsers,
init, error);
}
@@ -759,7 +763,8 @@
const TransportParserMap& transport_parsers,
SessionAccept* accept,
ParseError* error) {
- return ParseContentMessage(protocol, action_elem,
+ bool expect_transports = true;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
content_parsers, transport_parsers,
accept, error);
}
@@ -807,13 +812,12 @@
const SessionTerminate& term,
XmlElements* elems) {
if (protocol == PROTOCOL_GINGLE) {
- elems->push_back(new buzz::XmlElement(
- buzz::QName(true, NS_GINGLE, term.reason)));
+ elems->push_back(new buzz::XmlElement(buzz::QName(NS_GINGLE, term.reason)));
} else {
if (!term.reason.empty()) {
buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON);
reason_elem->AddElement(new buzz::XmlElement(
- buzz::QName(true, NS_JINGLE, term.reason)));
+ buzz::QName(NS_JINGLE, term.reason)));
elems->push_back(reason_elem);
}
}
@@ -825,7 +829,8 @@
const TransportParserMap& transport_parsers,
DescriptionInfo* description_info,
ParseError* error) {
- return ParseContentMessage(protocol, action_elem,
+ bool expect_transports = false;
+ return ParseContentMessage(protocol, action_elem, expect_transports,
content_parsers, transport_parsers,
description_info, error);
}
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
index 214fa8c..1486f0c 100644
--- a/talk/p2p/base/sessionmessages.h
+++ b/talk/p2p/base/sessionmessages.h
@@ -62,7 +62,7 @@
ACTION_TRANSPORT_INFO,
ACTION_TRANSPORT_ACCEPT,
- ACTION_UPDATE,
+ ACTION_DESCRIPTION_INFO,
};
// Abstraction of a <jingle> element within an <iq> stanza, per XMPP
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
index fab14af..b7f6b52 100644
--- a/talk/p2p/base/stun.cc
+++ b/talk/p2p/base/stun.cc
@@ -42,16 +42,17 @@
const char STUN_ERROR_REASON_SERVER_ERROR[] = "SERVER ERROR";
const char TURN_MAGIC_COOKIE_VALUE[] = { '\x72', '\xC6', '\x4B', '\xC6' };
+const char EMPTY_TRANSACTION_ID[] = "0000000000000000";
StunMessage::StunMessage()
: type_(0), length_(0),
- transaction_id_("000000000000") {
+ transaction_id_(EMPTY_TRANSACTION_ID) {
ASSERT(IsValidTransactionId(transaction_id_));
attrs_ = new std::vector<StunAttribute*>();
}
StunMessage::~StunMessage() {
- for (unsigned i = 0; i < attrs_->size(); i++)
+ for (size_t i = 0; i < attrs_->size(); i++)
delete (*attrs_)[i];
delete attrs_;
}
@@ -63,14 +64,22 @@
return false;
}
-void StunMessage::SetTransactionID(const std::string& str) {
- ASSERT(IsValidTransactionId(str));
+bool StunMessage::SetTransactionID(const std::string& str) {
+ if (!IsValidTransactionId(str)) {
+ return false;
+ }
transaction_id_ = str;
+ return true;
}
void StunMessage::AddAttribute(StunAttribute* attr) {
attrs_->push_back(attr);
- length_ += attr->length() + 4;
+ attr->SetOwner(this);
+ size_t attr_length = attr->length();
+ if (attr_length % 4 != 0) {
+ attr_length += (4 - (attr_length % 4));
+ }
+ length_ += attr_length + 4;
}
const StunAddressAttribute*
@@ -138,7 +147,7 @@
}
const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const {
- for (unsigned i = 0; i < attrs_->size(); i++) {
+ for (size_t i = 0; i < attrs_->size(); i++) {
if ((*attrs_)[i]->type() == type)
return (*attrs_)[i];
}
@@ -176,7 +185,7 @@
ASSERT(IsValidTransactionId(transaction_id));
transaction_id_ = transaction_id;
- if (length_ > buf->Length())
+ if (length_ != buf->Length())
return false;
attrs_->resize(0);
@@ -189,9 +198,13 @@
if (!buf->ReadUInt16(&attr_length))
return false;
- StunAttribute* attr = StunAttribute::Create(attr_type, attr_length);
+ StunAttribute* attr = StunAttribute::Create(attr_type, attr_length,
+ this);
if (!attr) {
// Skip an unknown attribute.
+ if ((attr_length % 4) != 0) {
+ attr_length += (4 - (attr_length % 4));
+ }
if (!buf->Consume(attr_length))
return false;
} else {
@@ -213,7 +226,7 @@
buf->WriteUInt32(kStunMagicCookie);
buf->WriteString(transaction_id_);
- for (unsigned i = 0; i < attrs_->size(); i++) {
+ for (size_t i = 0; i < attrs_->size(); i++) {
buf->WriteUInt16((*attrs_)[i]->type());
buf->WriteUInt16((*attrs_)[i]->length());
(*attrs_)[i]->Write(buf);
@@ -229,17 +242,19 @@
: type_(type), length_(length) {
}
-StunAttribute* StunAttribute::Create(uint16 type, uint16 length) {
+StunAttribute* StunAttribute::Create(uint16 type,
+ uint16 length,
+ StunMessage* owner) {
switch (type) {
case STUN_ATTR_MAPPED_ADDRESS:
case STUN_ATTR_DESTINATION_ADDRESS:
case STUN_ATTR_SOURCE_ADDRESS2:
- // TODO: Addresses may be different size for IPv6
- // addresses, but we don't support IPv6 yet. Fix address parsing
- // when IPv6 support is implemented.
- if (length != StunAddressAttribute::SIZE)
+ if (length != StunAddressAttribute::SIZE_IP4 &&
+ length != StunAddressAttribute::SIZE_IP6) {
+ LOG(LS_WARNING) << "Invalid length specified for address attribute";
return NULL;
- return new StunAddressAttribute(type);
+ }
+ return new StunAddressAttribute(type, length);
case STUN_ATTR_LIFETIME:
case STUN_ATTR_BANDWIDTH:
@@ -250,14 +265,12 @@
case STUN_ATTR_USERNAME:
case STUN_ATTR_MAGIC_COOKIE:
- return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0;
+ case STUN_ATTR_DATA:
+ return new StunByteStringAttribute(type, length);
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 NULL;
@@ -267,27 +280,42 @@
return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0;
case STUN_ATTR_XOR_MAPPED_ADDRESS:
- // TODO: Addresses may be different size for IPv6
- // addresses, but we don't support IPv6 yet. Fix address parsing
- // when IPv6 support is implemented.
- if (length != StunAddressAttribute::SIZE)
+ if (length != StunAddressAttribute::SIZE_IP4 &&
+ length != StunAddressAttribute::SIZE_IP6) {
+ LOG(LS_WARNING) << "Invalid length specified for XOR address attribute";
return NULL;
- return new StunXorAddressAttribute(type);
+ }
+ return new StunXorAddressAttribute(type, length, owner);
default:
return NULL;
}
}
+void StunAttribute::ConsumePadding(talk_base::ByteBuffer* buf) const {
+ int remainder = length_ % 4;
+ if (remainder > 0) {
+ buf->Consume(4 - remainder);
+ }
+}
+
+void StunAttribute::WritePadding(talk_base::ByteBuffer* buf) const {
+ int remainder = length_ % 4;
+ if (remainder > 0) {
+ char zeroes[4] = {0};
+ buf->WriteBytes(zeroes, 4 - remainder);
+ }
+}
+
StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
switch (type) {
case STUN_ATTR_MAPPED_ADDRESS:
case STUN_ATTR_DESTINATION_ADDRESS:
case STUN_ATTR_SOURCE_ADDRESS2:
- return new StunAddressAttribute(type);
+ return new StunAddressAttribute(type, StunAddressAttribute::SIZE_IP4);
case STUN_ATTR_XOR_MAPPED_ADDRESS:
- return new StunXorAddressAttribute(type);
+ return new StunXorAddressAttribute(type, StunAddressAttribute::SIZE_IP4);
default:
ASSERT(false);
@@ -331,68 +359,146 @@
return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
}
-StunAddressAttribute::StunAddressAttribute(uint16 type)
- : StunAttribute(type, SIZE), family_(STUN_ADDRESS_IPV4), port_(0), ip_(0) {
-}
-
-void StunAddressAttribute::SetFamily(StunAddressFamily family) {
- family_ = family;
-}
+StunAddressAttribute::StunAddressAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length) { }
bool StunAddressAttribute::Read(ByteBuffer* buf) {
uint8 dummy;
if (!buf->ReadUInt8(&dummy))
return false;
- uint8 family;
- // We don't expect IPv6 address here because IPv6 addresses would
- // not pass the attribute size check in StunAttribute::Create().
- if (!buf->ReadUInt8(&family) || family != STUN_ADDRESS_IPV4) {
+ uint8 stun_family;
+ if (!buf->ReadUInt8(&stun_family)) {
return false;
}
- family_ = static_cast<StunAddressFamily>(family);
-
- if (!buf->ReadUInt16(&port_))
+ uint16 port;
+ if (!buf->ReadUInt16(&port))
return false;
-
- if (!buf->ReadUInt32(&ip_))
+ if (stun_family == STUN_ADDRESS_IPV4) {
+ in_addr v4addr;
+ if (length() != SIZE_IP4) {
+ return false;
+ }
+ if (!buf->ReadBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr))) {
+ return false;
+ }
+ talk_base::IPAddress ipaddr(v4addr);
+ SetAddress(talk_base::SocketAddress(ipaddr, port));
+ } else if (stun_family == STUN_ADDRESS_IPV6) {
+ in6_addr v6addr;
+ if (length() != SIZE_IP6) {
+ return false;
+ }
+ if (!buf->ReadBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr))) {
+ return false;
+ }
+ talk_base::IPAddress ipaddr(v6addr);
+ SetAddress(talk_base::SocketAddress(ipaddr, port));
+ } else {
return false;
-
+ }
return true;
}
void StunAddressAttribute::Write(ByteBuffer* buf) const {
- // Only IPv4 address family is currently supported.
- ASSERT(family_ == STUN_ADDRESS_IPV4);
-
+ StunAddressFamily address_family = family();
+ if (address_family == STUN_ADDRESS_UNDEF) {
+ LOG(LS_ERROR) << "Error writing address attribute: unknown family.";
+ return;
+ }
buf->WriteUInt8(0);
- buf->WriteUInt8(family_);
- buf->WriteUInt16(port_);
- buf->WriteUInt32(ip_);
+ buf->WriteUInt8(address_family);
+ buf->WriteUInt16(address_.port());
+ switch (address_family) {
+ case STUN_ADDRESS_IPV4: {
+ in_addr v4addr = address_.ipaddr().ipv4_address();
+ buf->WriteBytes(reinterpret_cast<char*>(&v4addr), sizeof(v4addr));
+ break;
+ }
+ case STUN_ADDRESS_IPV6: {
+ in6_addr v6addr = address_.ipaddr().ipv6_address();
+ buf->WriteBytes(reinterpret_cast<char*>(&v6addr), sizeof(v6addr));
+ break;
+ }
+ }
}
-StunXorAddressAttribute::StunXorAddressAttribute(uint16 type)
- : StunAddressAttribute(type) {
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type, uint16 length)
+ : StunAddressAttribute(type, length), owner_(NULL) { }
+
+StunXorAddressAttribute::StunXorAddressAttribute(uint16 type,
+ uint16 length,
+ StunMessage* owner)
+ : StunAddressAttribute(type, length), owner_(owner) { }
+
+talk_base::IPAddress StunXorAddressAttribute::GetXoredIP() const {
+ if (owner_) {
+ talk_base::IPAddress ip = ipaddr();
+ switch (ip.family()) {
+ case AF_INET: {
+ in_addr v4addr = ip.ipv4_address();
+ v4addr.s_addr =
+ (v4addr.s_addr ^ talk_base::HostToNetwork32(kStunMagicCookie));
+ return talk_base::IPAddress(v4addr);
+ break;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = ip.ipv6_address();
+ const std::string& transaction_id = owner_->transaction_id();
+ if (transaction_id.length() == 12) {
+ uint32 transactionid_as_ints[3];
+ memcpy(&transactionid_as_ints[0], transaction_id.c_str(),
+ transaction_id.length());
+ uint32* ip_as_ints = reinterpret_cast<uint32*>(&v6addr.s6_addr);
+ // Transaction ID is in network byte order, but magic cookie
+ // is stored in host byte order.
+ ip_as_ints[0] =
+ (ip_as_ints[0] ^ talk_base::HostToNetwork32(kStunMagicCookie));
+ ip_as_ints[1] = (ip_as_ints[1] ^ transactionid_as_ints[0]);
+ ip_as_ints[2] = (ip_as_ints[2] ^ transactionid_as_ints[1]);
+ ip_as_ints[3] = (ip_as_ints[3] ^ transactionid_as_ints[2]);
+ return talk_base::IPAddress(v6addr);
+ }
+ break;
+ }
+ }
+ }
+ // Invalid ip family or transaction ID, or missing owner.
+ // Return an AF_UNSPEC address.
+ return talk_base::IPAddress();
}
bool StunXorAddressAttribute::Read(ByteBuffer* buf) {
if (!StunAddressAttribute::Read(buf))
return false;
-
- SetPort(port() ^ (kStunMagicCookie >> 16));
- SetIP(ip() ^ kStunMagicCookie);
-
+ uint16 xoredport = port() ^ (kStunMagicCookie >> 16);
+ talk_base::IPAddress xored_ip = GetXoredIP();
+ SetAddress(talk_base::SocketAddress(xored_ip, xoredport));
return true;
}
void StunXorAddressAttribute::Write(ByteBuffer* buf) const {
- // Only IPv4 address family is currently supported.
- ASSERT(family() == STUN_ADDRESS_IPV4);
-
+ StunAddressFamily address_family = family();
+ if (address_family == STUN_ADDRESS_UNDEF) {
+ LOG(LS_ERROR) << "Error writing xor-address attribute: unknown family.";
+ return;
+ }
buf->WriteUInt8(0);
buf->WriteUInt8(family());
buf->WriteUInt16(port() ^ (kStunMagicCookie >> 16));
- buf->WriteUInt32(ip() ^ kStunMagicCookie);
+ talk_base::IPAddress xored_ip = GetXoredIP();
+ switch (xored_ip.family()) {
+ case AF_INET: {
+ in_addr v4addr = xored_ip.ipv4_address();
+ buf->WriteBytes(reinterpret_cast<const char*>(&v4addr), sizeof(v4addr));
+ break;
+ }
+ case AF_INET6: {
+ in6_addr v6addr = xored_ip.ipv6_address();
+ buf->WriteBytes(reinterpret_cast<const char*>(&v6addr), sizeof(v6addr));
+ break;
+ }
+ }
}
StunUInt32Attribute::StunUInt32Attribute(uint16 type)
@@ -458,13 +564,18 @@
bool StunByteStringAttribute::Read(ByteBuffer* buf) {
bytes_ = new char[length()];
- if (!buf->ReadBytes(bytes_, length()))
+ if (!buf->ReadBytes(bytes_, length())) {
return false;
+ }
+
+ ConsumePadding(buf);
+
return true;
}
void StunByteStringAttribute::Write(ByteBuffer* buf) const {
buf->WriteBytes(bytes_, length());
+ WritePadding(buf);
}
StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
@@ -496,6 +607,7 @@
if (!buf->ReadString(&reason_, length() - 4))
return false;
+ ConsumePadding(buf);
return true;
}
@@ -503,6 +615,7 @@
void StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
buf->WriteUInt32(error_code());
buf->WriteString(reason_);
+ WritePadding(buf);
}
StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
@@ -538,12 +651,20 @@
return false;
attr_types_->push_back(attr);
}
+ // Padding of these attributes is done in RFC 5389 style. This is
+ // slightly different from RFC3489, but it shouldn't be important.
+ // RFC3489 pads out to a 32 bit boundary by duplicating one of the
+ // entries in the list (not necessarily the last one - it's unspecified).
+ // RFC5389 pads on the end, and the bytes are always ignored.
+ ConsumePadding(buf);
return true;
}
void StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
- for (unsigned i = 0; i < attr_types_->size(); i++)
+ for (size_t i = 0; i < attr_types_->size(); i++) {
buf->WriteUInt16((*attr_types_)[i]);
+ }
+ WritePadding(buf);
}
StunMessageType GetStunResponseType(StunMessageType request_type) {
@@ -572,4 +693,4 @@
}
}
-} // namespace cricket
+} // namespace cricket
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
index efc5f2c..b8b470f 100644
--- a/talk/p2p/base/stun.h
+++ b/talk/p2p/base/stun.h
@@ -36,6 +36,7 @@
#include "talk/base/basictypes.h"
#include "talk/base/bytebuffer.h"
+#include "talk/base/socketaddress.h"
namespace cricket {
@@ -63,19 +64,19 @@
// implemented yet, particularly REALM, NONCE, SOFTWARE,
// ALTERNATE-SERVE and FINGEPRINT. Implement them.
enum StunAttributeType {
- STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address
- STUN_ATTR_USERNAME = 0x0006, // 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_LIFETIME = 0x000d, // UInt32
- 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_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress
- STUN_ATTR_OPTIONS = 0x8001 // UInt32
+ STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address
+ STUN_ATTR_USERNAME = 0x0006, // 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_LIFETIME = 0x000d, // UInt32
+ 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_XOR_MAPPED_ADDRESS = 0x0020, // XorAddress
+ STUN_ATTR_OPTIONS = 0x8001 // UInt32
};
enum StunErrorCodes {
@@ -91,6 +92,8 @@
};
enum StunAddressFamily {
+ // NB: UNDEF is not part of the STUN spec.
+ STUN_ADDRESS_UNDEF = 0,
STUN_ADDRESS_IPV4 = 1,
STUN_ADDRESS_IPV6 = 2
};
@@ -122,7 +125,7 @@
// appropriate class (see above). The Get* methods will return instances of
// that attribute class.
class StunMessage {
-public:
+ public:
StunMessage();
~StunMessage();
StunMessageType type() const { return static_cast<StunMessageType>(type_); }
@@ -137,7 +140,7 @@
bool IsLegacy() const;
void SetType(StunMessageType type) { type_ = type; }
- void SetTransactionID(const std::string& str);
+ bool SetTransactionID(const std::string& str);
const StunAddressAttribute* GetAddress(StunAttributeType type) const;
const StunUInt32Attribute* GetUInt32(StunAttributeType type) const;
@@ -156,7 +159,7 @@
// successful.
void Write(talk_base::ByteBuffer* buf) const;
-private:
+ private:
uint16 type_;
uint16 length_;
std::string transaction_id_;
@@ -168,7 +171,7 @@
// Base class for all STUN/TURN attributes.
class StunAttribute {
-public:
+ public:
virtual ~StunAttribute() {}
StunAttributeType type() const {
@@ -176,6 +179,9 @@
}
uint16 length() const { return length_; }
+ // Only XorAddressAttribute needs this so far.
+ virtual void SetOwner(StunMessage* owner) { }
+
// 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;
@@ -184,8 +190,9 @@
// 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, length and transaction id.
+ static StunAttribute* Create(uint16 type, uint16 length,
+ StunMessage* owner);
// Creates an attribute object with the given type and smallest length.
static StunAddressAttribute* CreateAddress(uint16 type);
@@ -195,52 +202,94 @@
static StunUInt16ListAttribute* CreateUnknownAttributes();
static StunTransportPrefsAttribute* CreateTransportPrefs();
-protected:
+ protected:
StunAttribute(uint16 type, uint16 length);
-
void SetLength(uint16 length) { length_ = length; }
+ void WritePadding(talk_base::ByteBuffer* buf) const;
+ void ConsumePadding(talk_base::ByteBuffer* buf) const;
-private:
+ private:
uint16 type_;
uint16 length_;
};
// Implements STUN/TURN attributes that record an Internet address.
class StunAddressAttribute : public StunAttribute {
-public:
- explicit StunAddressAttribute(uint16 type);
+ public:
+ StunAddressAttribute(uint16 type, uint16 length);
- static const uint16 SIZE = 8;
+ static const uint16 SIZE_UNDEF = 0;
+ static const uint16 SIZE_IP4 = 8;
+ static const uint16 SIZE_IP6 = 20;
- StunAddressFamily family() const { return family_; }
- uint16 port() const { return port_; }
- uint32 ip() const { return ip_; }
+ StunAddressFamily family() const {
+ switch (address_.ipaddr().family()) {
+ case AF_INET:
+ return STUN_ADDRESS_IPV4;
+ case AF_INET6:
+ return STUN_ADDRESS_IPV6;
+ }
+ return STUN_ADDRESS_UNDEF;
+ }
- void SetFamily(StunAddressFamily family);
- void SetIP(uint32 ip) { ip_ = ip; }
- void SetPort(uint16 port) { port_ = port; }
+ uint16 port() const { return address_.port(); }
+ const talk_base::IPAddress& ipaddr() const { return address_.ipaddr(); }
+ void SetAddress(const talk_base::SocketAddress& addr) {
+ address_ = addr;
+ EnsureAddressLength();
+ }
+ const talk_base::SocketAddress& GetAddress() const { return address_; }
+ void SetIP(const talk_base::IPAddress& ip) {
+ address_.SetIP(ip);
+ EnsureAddressLength();
+ }
+ void SetPort(uint16 port) { address_.SetPort(port); }
virtual bool Read(talk_base::ByteBuffer* buf);
virtual void Write(talk_base::ByteBuffer* buf) const;
-private:
- StunAddressFamily family_;
- uint16 port_;
- uint32 ip_;
+ private:
+ void EnsureAddressLength() {
+ switch (family()) {
+ case STUN_ADDRESS_IPV4: {
+ SetLength(SIZE_IP4);
+ break;
+ }
+ case STUN_ADDRESS_IPV6: {
+ SetLength(SIZE_IP6);
+ break;
+ }
+ default: {
+ SetLength(SIZE_UNDEF);
+ break;
+ }
+ }
+ }
+ talk_base::SocketAddress address_;
};
-// Implements STUN/TURN attributes that record an Internet address.
+// Implements STUN/TURN attributes that record an Internet address. When encoded
+// in a STUN message, the address contained in this attribute is XORed with the
+// transaction ID of the message.
class StunXorAddressAttribute : public StunAddressAttribute {
-public:
- explicit StunXorAddressAttribute(uint16 type);
+ public:
+ StunXorAddressAttribute(uint16 type, uint16 length);
+ StunXorAddressAttribute(uint16 type, uint16 length,
+ StunMessage* owner);
+ virtual void SetOwner(StunMessage* owner) {
+ owner_ = owner;
+ }
virtual bool Read(talk_base::ByteBuffer* buf);
virtual void Write(talk_base::ByteBuffer* buf) const;
+ private:
+ talk_base::IPAddress GetXoredIP() const;
+ StunMessage* owner_;
};
// Implements STUN/TURN attributs that record a 32-bit integer.
class StunUInt32Attribute : public StunAttribute {
-public:
+ public:
explicit StunUInt32Attribute(uint16 type);
static const uint16 SIZE = 4;
@@ -255,13 +304,13 @@
bool Read(talk_base::ByteBuffer* buf);
void Write(talk_base::ByteBuffer* buf) const;
-private:
+ private:
uint32 bits_;
};
-// Implements STUN/TURN attributs that record an arbitrary byte string
+// Implements STUN/TURN attributes that record an arbitrary byte string
class StunByteStringAttribute : public StunAttribute {
-public:
+ public:
StunByteStringAttribute(uint16 type, uint16 length);
~StunByteStringAttribute();
@@ -269,7 +318,7 @@
void SetBytes(char* bytes, uint16 length);
- void CopyBytes(const char* bytes); // uses strlen
+ void CopyBytes(const char* bytes); // uses strlen
void CopyBytes(const void* bytes, uint16 length);
uint8 GetByte(int index) const;
@@ -278,13 +327,13 @@
bool Read(talk_base::ByteBuffer* buf);
void Write(talk_base::ByteBuffer* buf) const;
-private:
+ private:
char* bytes_;
};
-// Implements STUN/TURN attributs that record an error code.
+// Implements STUN/TURN attributes that record an error code.
class StunErrorCodeAttribute : public StunAttribute {
-public:
+ public:
StunErrorCodeAttribute(uint16 type, uint16 length);
~StunErrorCodeAttribute();
@@ -303,15 +352,15 @@
bool Read(talk_base::ByteBuffer* buf);
void Write(talk_base::ByteBuffer* buf) const;
-private:
+ private:
uint8 class_;
uint8 number_;
std::string reason_;
};
-// Implements STUN/TURN attributs that record a list of attribute names.
+// Implements STUN/TURN attributes that record a list of attribute names.
class StunUInt16ListAttribute : public StunAttribute {
-public:
+ public:
StunUInt16ListAttribute(uint16 type, uint16 length);
~StunUInt16ListAttribute();
@@ -323,7 +372,7 @@
bool Read(talk_base::ByteBuffer* buf);
void Write(talk_base::ByteBuffer* buf) const;
-private:
+ private:
std::vector<uint16>* attr_types_;
};
@@ -339,6 +388,6 @@
// Returns the error response type for the given request type.
StunMessageType GetStunErrorResponseType(StunMessageType request_type);
-} // namespace cricket
+} // namespace cricket
#endif // TALK_P2P_BASE_STUN_H_
diff --git a/talk/p2p/base/stun_unittest.cc b/talk/p2p/base/stun_unittest.cc
new file mode 100644
index 0000000..1152bee
--- /dev/null
+++ b/talk/p2p/base/stun_unittest.cc
@@ -0,0 +1,997 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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/bytebuffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+class StunTest : public ::testing::Test {
+ protected:
+ void CheckStunHeader(const StunMessage& msg, StunMessageType expected_type,
+ size_t expected_length) {
+ ASSERT_EQ(expected_type, msg.type());
+ ASSERT_EQ(expected_length, msg.length());
+ }
+
+ void CheckStunTransactionID(const StunMessage& msg,
+ const unsigned char* expectedID, size_t length) {
+ ASSERT_EQ(0, std::memcmp(msg.transaction_id().c_str(),
+ expectedID, length));
+ }
+
+ void CheckStunAddressAttribute(const StunAddressAttribute* addr,
+ StunAddressFamily expected_family,
+ int expected_port,
+ talk_base::IPAddress expected_address) {
+ ASSERT_EQ(expected_family, addr->family());
+ ASSERT_EQ(expected_port, addr->port());
+
+ if (addr->family() == STUN_ADDRESS_IPV4) {
+ in_addr v4_address = expected_address.ipv4_address();
+ in_addr stun_address = addr->ipaddr().ipv4_address();
+ ASSERT_EQ(0, std::memcmp(&v4_address, &stun_address,
+ sizeof(stun_address)));
+ } else if (addr->family() == STUN_ADDRESS_IPV6) {
+ in6_addr v6_address = expected_address.ipv6_address();
+ in6_addr stun_address = addr->ipaddr().ipv6_address();
+ ASSERT_EQ(0, std::memcmp(&v6_address, &stun_address,
+ sizeof(stun_address)));
+ } else {
+ ASSERT_TRUE(addr->family() == STUN_ADDRESS_IPV6 ||
+ addr->family() == STUN_ADDRESS_IPV4);
+ }
+ }
+
+ size_t ReadStunMessageTestCase(StunMessage* msg,
+ const unsigned char* testcase,
+ size_t size) {
+ const char* input = reinterpret_cast<const char*>(testcase);
+ talk_base::ByteBuffer buf(input, size);
+ if (msg->Read(&buf)) {
+ // Returns the size the stun message should report itself as being
+ return (size - 20);
+ } else {
+ return 0;
+ }
+ }
+};
+
+
+// Sample STUN packets with various attributes
+// Gathered by wiresharking pjproject's pjnath test programs
+// pjproject available at www.pjsip.org
+
+static const unsigned char kStunMessageWithIPv6MappedAddress[] = {
+ 0x00, 0x01, 0x00, 0x18, // message header
+ 0x21, 0x12, 0xa4, 0x42, // transaction id
+ 0x29, 0x1f, 0xcd, 0x7c,
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x14, // Address type (mapped), length
+ 0x00, 0x02, 0xb8, 0x81, // family (IPv6), port
+ 0x24, 0x01, 0xfa, 0x00, // an IPv6 address
+ 0x00, 0x04, 0x10, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4MappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x0c, // binding response, length 12
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x08, // Mapped, 8 byte length
+ 0x00, 0x01, 0x9d, 0xfc, // AF_INET, unxor-ed port
+ 0xac, 0x17, 0x44, 0xe6 // IPv4 address
+};
+
+// Test XOR-mapped IP addresses:
+static const unsigned char kStunMessageWithIPv6XorMappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x18, // message header (binding response)
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie (rfc5389)
+ 0xe3, 0xa9, 0x46, 0xe1, // transaction ID
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x20, 0x00, 0x14, // Address Type (XOR), length
+ 0x00, 0x02, 0xcb, 0x5b, // family, XOR-ed port
+ 0x05, 0x13, 0x5e, 0x42, // XOR-ed IPv6 address
+ 0xe3, 0xad, 0x56, 0xe1,
+ 0xc2, 0x30, 0x99, 0x9d,
+ 0xaa, 0xed, 0x01, 0xc3
+};
+
+static const unsigned char kStunMessageWithIPv4XorMappedAddress[] = {
+ 0x01, 0x01, 0x00, 0x0c, // message header (binding response)
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x20, 0x00, 0x08, // address type (xor), length
+ 0x00, 0x01, 0xfc, 0xb5, // family (AF_INET), XOR-ed port
+ 0x8d, 0x05, 0xe0, 0xa4 // IPv4 address
+};
+
+// ByteString Attribute (username)
+static const unsigned char kStunMessageWithByteStringAttribute[] = {
+ 0x00, 0x01, 0x00, 0x0c,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x06, 0x00, 0x08, // username attribute (length 8)
+ 0x61, 0x62, 0x63, 0x64, // abcdefgh
+ 0x65, 0x66, 0x67, 0x68
+};
+
+// Message with an unknown but comprehensible optional attribute.
+// Parsing should succeed despite this unknown attribute.
+static const unsigned char kStunMessageWithUnknownAttribute[] = {
+ 0x00, 0x01, 0x00, 0x14,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0xaa, 0x00, 0x07, // Unknown attribute, length 7 (needs padding!)
+ 0x61, 0x62, 0x63, 0x64, // abcdefg + padding
+ 0x65, 0x66, 0x67, 0x00,
+ 0x00, 0x0d, 0x00, 0x04, // Followed by a known attribute we can
+ 0x00, 0x00, 0x00, 0x0b // check for.
+};
+
+// ByteString Attribute (username) with padding byte
+static const unsigned char kStunMessageWithPaddedByteStringAttribute[] = {
+ 0x00, 0x01, 0x00, 0x08,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x06, 0x00, 0x03, // username attribute (length 3)
+ 0x61, 0x62, 0x63, 0xcc // abc
+};
+
+// Message with an Unknown Attributes (uint16 list) attribute.
+static const unsigned char kStunMessageWithUInt16ListAttribute[] = {
+ 0x00, 0x01, 0x00, 0x0c,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0xe3, 0xa9, 0x46, 0xe1,
+ 0x7c, 0x00, 0xc2, 0x62,
+ 0x54, 0x08, 0x01, 0x00,
+ 0x00, 0x0a, 0x00, 0x06, // username attribute (length 6)
+ 0x00, 0x01, 0x10, 0x00, // three attributes plus padding
+ 0xAB, 0xCU, 0xBE, 0xEF
+};
+
+// Error response message (unauthorized)
+static const unsigned char kStunMessageWithErrorAttribute[] = {
+ 0x01, 0x13, 0x00, 0x14,
+ 0x21, 0x12, 0xa4, 0x42,
+ 0x29, 0x1f, 0xcd, 0x7c,
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x09, 0x00, 0x10,
+ 0x00, 0x00, 0x04, 0x01,
+ 0x55, 0x6e, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x7a, 0x65, 0x64
+};
+
+// Message with an address attribute with an unknown address family,
+// and a byte string attribute. Check that we quit reading after the
+// bogus address family and don't read the username attribute.
+static const unsigned char kStunMessageWithInvalidAddressFamily[] = {
+ 0x01, 0x01, 0x00, 0x18, // binding response, length 24
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x08, // Mapped address, 4 byte length
+ 0x00, 0x09, 0xfe, 0xed, // Bogus address family (port unimportant).
+ 0xac, 0x17, 0x44, 0xe6, // Should be skipped.
+ 0x00, 0x06, 0x00, 0x08, // Username attribute (length 8)
+ 0x61, 0x62, 0x63, 0x64, // abcdefgh
+ 0x65, 0x66, 0x67, 0x68
+};
+
+// Message with an address attribute with an invalid address length.
+// Should fail to be read.
+static const unsigned char kStunMessageWithInvalidAddressLength[] = {
+ 0x01, 0x01, 0x00, 0x18, // binding response, length 24
+ 0x21, 0x12, 0xa4, 0x42, // magic cookie
+ 0x29, 0x1f, 0xcd, 0x7c, // transaction ID
+ 0xba, 0x58, 0xab, 0xd7,
+ 0xf2, 0x41, 0x01, 0x00,
+ 0x00, 0x01, 0x00, 0x0c, // Mapped address, 12 byte length
+ 0x00, 0x01, 0xfe, 0xed, // Claims to be AF_INET.
+ 0xac, 0x17, 0x44, 0xe6,
+ 0x00, 0x06, 0x00, 0x08
+};
+
+// Sample messages with an invalid length Field
+
+// The actual length in bytes of the invalid messages (including STUN header)
+static const int kRealLengthOfInvalidLengthTestCases = 32;
+
+static const unsigned char kStunMessageWithZeroLength[] = {
+ 0x00, 0x01, 0x00, 0x00, // length of 0 (last 2 bytes)
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithExcessLength[] = {
+ 0x00, 0x01, 0x00, 0x55, // length of 85
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+static const unsigned char kStunMessageWithSmallLength[] = {
+ 0x00, 0x01, 0x00, 0x03, // length of 3
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x20, 0x00, 0x08, // xor mapped address
+ 0x00, 0x01, 0x21, 0x1F,
+ 0x21, 0x12, 0xA4, 0x53,
+};
+
+// Legacy STUN tests.
+// Included for completeness, but it's not recommended to change these.
+static const unsigned char kStunMessageWithManyAttributes[] = {
+ 0x00, 0x01, 0x00, 136, // message header
+ 0x21, 0x12, 0xA4, 0x42, // magic cookie
+ '0', '1', '2', '3', // transaction id
+ '4', '5', '6', '7',
+ '8', '9', 'a', 'b',
+ 0x00, 0x01, 0x00, 8, // mapped address
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x06, 0x00, 12, // username
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 0x00, 0x08, 0x00, 20, // message integrity
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 0x00, 0x09, 0x00, 12, // error code
+ 0x00, 0x00, 2, 10,
+ 'f', 'o', 'o', ' ', 'b', 'a', 'r', '!',
+ 0x00, 0x0a, 0x00, 4, // unknown attributes
+ 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x0d, 0x00, 4, // lifetime
+ 0x00, 0x00, 0x00, 11,
+ 0x00, 0x0f, 0x00, 4, // magic cookie
+ 0x72, 0xc6, 0x4b, 0xc6,
+ 0x00, 0x10, 0x00, 4, // bandwidth
+ 0x00, 0x00, 0x00, 6,
+ 0x00, 0x11, 0x00, 8, // destination address
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x12, 0x00, 8, // source address 2
+ 0x00, 0x01, 0x00, 13,
+ 0x00, 0x00, 0x00, 17,
+ 0x00, 0x13, 0x00, 7, // data
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 0 // DATA must be padded per rfc5766.
+};
+
+// RTCP packet, for testing we correctly ignore non stun packet types.
+// V=2, P=false, RC=0, Type=200, Len=6, Sender-SSRC=85, etc
+static const unsigned char kRtcpPacket[] = {
+ 0x80, 0xc8, 0x00, 0x06, 0x00, 0x00, 0x00, 0x55,
+ 0xce, 0xa5, 0x18, 0x3a, 0x39, 0xcc, 0x7d, 0x09,
+ 0x23, 0xed, 0x19, 0x07, 0x00, 0x00, 0x01, 0x56,
+ 0x00, 0x03, 0x73, 0x50,
+};
+
+// A transaction ID without the 'magic cookie' portion
+// pjnat's test programs use this transaction ID a lot.
+const unsigned char kTestTransactionId1[] = { 0x029, 0x01f, 0x0cd, 0x07c,
+ 0x0ba, 0x058, 0x0ab, 0x0d7,
+ 0x0f2, 0x041, 0x001, 0x000 };
+
+// They use this one sometimes too.
+const unsigned char kTestTransactionId2[] = { 0x0e3, 0x0a9, 0x046, 0x0e1,
+ 0x07c, 0x000, 0x0c2, 0x062,
+ 0x054, 0x008, 0x001, 0x000 };
+
+const in6_addr kIPv6TestAddress1 = { { { 0x24, 0x01, 0xfa, 0x00,
+ 0x00, 0x04, 0x10, 0x00,
+ 0xbe, 0x30, 0x5b, 0xff,
+ 0xfe, 0xe5, 0x00, 0xc3 } } };
+const in6_addr kIPv6TestAddress2 = { { { 0x24, 0x01, 0xfa, 0x00,
+ 0x00, 0x04, 0x10, 0x12,
+ 0x06, 0x0c, 0xce, 0xff,
+ 0xfe, 0x1f, 0x61, 0xa4 } } };
+
+// This is kIPv6TestAddress1 xor-ed with kTestTransactionID2.
+const in6_addr kIPv6XoredTestAddress = { { { 0x05, 0x13, 0x5e, 0x42,
+ 0xe3, 0xad, 0x56, 0xe1,
+ 0xc2, 0x30, 0x99, 0x9d,
+ 0xaa, 0xed, 0x01, 0xc3 } } };
+
+#ifdef POSIX
+const in_addr kIPv4TestAddress1 = { 0xe64417ac };
+// This is kIPv4TestAddress xored with the STUN magic cookie.
+const in_addr kIPv4XoredTestAddress = { 0x8d05e0a4 };
+#elif defined WIN32
+// Windows in_addr has a union with a uchar[] array first.
+const in_addr kIPv4XoredTestAddress = { { 0x8d, 0x05, 0xe0, 0xa4 } };
+const in_addr kIPv4TestAddress1 = { { 0x0ac, 0x017, 0x044, 0x0e6 } };
+#endif
+const char kTestUserName1[] = "abcdefgh";
+const char kTestUserName2[] = "abc";
+const char kTestErrorReason[] = "Unauthorized";
+const int kTestErrorClass = 4;
+const int kTestErrorNumber = 1;
+
+const int kTestMessagePort1 = 59977;
+const int kTestMessagePort2 = 47233;
+const int kTestMessagePort3 = 56743;
+const int kTestMessagePort4 = 40444;
+
+#define ReadStunMessage(X, Y) ReadStunMessageTestCase(X, Y, sizeof(Y));
+
+TEST_F(StunTest, ReadMessageWithIPv4AddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ talk_base::IPAddress test_address(kIPv4TestAddress1);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort4, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv4XorAddressAttribute) {
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ talk_base::IPAddress test_address(kIPv4TestAddress1);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort3, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6AddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithInvalidAddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_address);
+}
+
+TEST_F(StunTest, ReadMessageWithIPv6XorAddressAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+ talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort1, test_address);
+}
+
+TEST_F(StunTest, SetIPv6XorAddressAttributeOwner) {
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv6XorMappedAddress);
+
+ talk_base::IPAddress test_address(kIPv6TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort1, test_address);
+
+ // Owner with a different transaction ID.
+ msg2.SetTransactionID("ABCDABCDABCD");
+ StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20);
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ addr2.SetOwner(&msg2);
+ // The internal IP address shouldn't change.
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+ talk_base::ByteBuffer correct_buf;
+ talk_base::ByteBuffer wrong_buf;
+ addr->Write(&correct_buf);
+ addr2.Write(&wrong_buf);
+ // But when written out, the buffers should look different.
+ ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+ wrong_buf.Data(),
+ wrong_buf.Length()));
+ // And when reading a known good value, the address should be wrong
+ addr2.Read(&correct_buf);
+ ASSERT_NE(addr->ipaddr(), addr2.ipaddr());
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ // Try writing with no owner at all. Should write 4 bytes (1 byte reserved,
+ // 2 bytes port, 1 byte address family, 0 bytes address). This is an invalid
+ // but well-formed address attribute.
+ addr2.SetOwner(NULL);
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+ wrong_buf.Shift(wrong_buf.Length());
+ addr2.Write(&wrong_buf);
+ ASSERT_EQ(4U, wrong_buf.Length());
+ ASSERT_NE(wrong_buf.Length(), correct_buf.Length());
+ ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+ wrong_buf.Data(),
+ wrong_buf.Length()));
+}
+
+TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) {
+ // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes
+ // should _not_ be affected by a change in owner. IPv4 XOR address uses the
+ // magic cookie value which is fixed.
+ StunMessage msg;
+ StunMessage msg2;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
+
+ talk_base::IPAddress test_address(kIPv4TestAddress1);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ const StunAddressAttribute* addr =
+ msg.GetAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort3, test_address);
+
+ // Owner with a different transaction ID.
+ msg2.SetTransactionID("ABCDABCDABCD");
+ StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20);
+ addr2.SetIP(addr->ipaddr());
+ addr2.SetPort(addr->port());
+ addr2.SetOwner(&msg2);
+ // The internal IP address shouldn't change.
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+
+ talk_base::ByteBuffer correct_buf;
+ talk_base::ByteBuffer wrong_buf;
+ addr->Write(&correct_buf);
+ addr2.Write(&wrong_buf);
+ // The same address data should be written.
+ ASSERT_EQ(0, std::memcmp(correct_buf.Data(),
+ wrong_buf.Data(),
+ wrong_buf.Length()));
+ // And an attribute should be able to un-XOR an address belonging to a message
+ // with a different transaction ID.
+ addr2.Read(&correct_buf);
+ ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
+
+ // However, no owner is still an error. Write 4 bytes and a 0 length address.
+ addr2.SetOwner(NULL);
+ ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
+ wrong_buf.Shift(wrong_buf.Length());
+ addr2.Write(&wrong_buf);
+ ASSERT_NE(wrong_buf.Length(), correct_buf.Length());
+ ASSERT_NE(0, std::memcmp(correct_buf.Data(),
+ wrong_buf.Data(),
+ wrong_buf.Length()));
+}
+
+TEST_F(StunTest, CreateIPv6AddressAttribute) {
+ talk_base::IPAddress test_ip(kIPv6TestAddress2);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV6,
+ kTestMessagePort2, test_ip);
+ delete addr;
+}
+
+TEST_F(StunTest, CreateIPv4AddressAttribute) {
+ struct in_addr test_in_addr;
+ test_in_addr.s_addr = 0xBEB0B0BE;
+ talk_base::IPAddress test_ip(test_in_addr);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+
+ CheckStunAddressAttribute(addr, STUN_ADDRESS_IPV4,
+ kTestMessagePort2, test_ip);
+ delete addr;
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6AddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv6MappedAddress);
+
+ talk_base::IPAddress test_ip(kIPv6TestAddress1);
+
+ msg.SetType(STUN_BINDING_REQUEST);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort2);
+ addr->SetAddress(test_addr);
+ msg.AddAttribute(addr);
+
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6MappedAddress));
+ int len1 = out.Length();
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+ kStunMessageWithIPv6MappedAddress,
+ len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4AddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv4MappedAddress);
+
+ talk_base::IPAddress test_ip(kIPv4TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort4);
+ addr->SetAddress(test_addr);
+ msg.AddAttribute(addr);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4MappedAddress));
+ int len1 = out.Length();
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+ kStunMessageWithIPv4MappedAddress,
+ len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv6XorAddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv6XorMappedAddress);
+
+ talk_base::IPAddress test_ip(kIPv6TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort1);
+ addr->SetAddress(test_addr);
+ msg.AddAttribute(addr);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv6XorMappedAddress));
+ int len1 = out.Length();
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+ kStunMessageWithIPv6XorMappedAddress,
+ len1));
+}
+
+TEST_F(StunTest, WriteMessageWithIPv4XoreAddressAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithIPv4XorMappedAddress);
+
+ talk_base::IPAddress test_ip(kIPv4TestAddress1);
+
+ msg.SetType(STUN_BINDING_RESPONSE);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId1),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
+ talk_base::SocketAddress test_addr(test_ip, kTestMessagePort3);
+ addr->SetAddress(test_addr);
+ msg.AddAttribute(addr);
+
+ CheckStunHeader(msg, STUN_BINDING_RESPONSE, (size - 20));
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ ASSERT_EQ(out.Length(), sizeof(kStunMessageWithIPv4XorMappedAddress));
+ int len1 = out.Length();
+ std::string bytes;
+ out.ReadString(&bytes, len1);
+ ASSERT_EQ(0, std::memcmp(bytes.c_str(),
+ kStunMessageWithIPv4XorMappedAddress,
+ len1));
+}
+
+TEST_F(StunTest, ReadByteStringAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithByteStringAttribute);
+
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_EQ(0, std::memcmp(kTestUserName1, username->bytes(),
+ username->length()));
+}
+
+TEST_F(StunTest, ReadPaddedByteStringAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg,
+ kStunMessageWithPaddedByteStringAttribute);
+ ASSERT_NE(0U, size);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ const StunByteStringAttribute* username =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_EQ(0, std::memcmp(kTestUserName2, username->bytes(),
+ username->length()));
+}
+
+TEST_F(StunTest, ReadErrorCodeAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithErrorAttribute);
+
+ CheckStunHeader(msg, STUN_ALLOCATE_ERROR_RESPONSE, size);
+ CheckStunTransactionID(msg, kTestTransactionId1, kStunTransactionIdLength);
+ const StunErrorCodeAttribute* errorcode = msg.GetErrorCode();
+ ASSERT_EQ(kTestErrorClass, errorcode->error_class());
+ ASSERT_EQ(kTestErrorNumber, errorcode->number());
+ std::string reason = errorcode->reason();
+ ASSERT_EQ(0, strcmp(reason.c_str(), kTestErrorReason));
+}
+
+TEST_F(StunTest, ReadMessageWithAnUnknownAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithUnknownAttribute);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+
+ // Parsing should have succeeded and there should be a lifetime attribute
+ const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME);
+ EXPECT_TRUE(uval != NULL);
+ if (uval != NULL) {
+ EXPECT_EQ(11U, uval->value());
+ }
+}
+
+TEST_F(StunTest, ReadMessageWithAUInt16ListAttribute) {
+ StunMessage msg;
+ size_t size = ReadStunMessage(&msg, kStunMessageWithUInt16ListAttribute);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
+ const StunUInt16ListAttribute* types = msg.GetUnknownAttributes();
+ EXPECT_EQ(3U, types->Size());
+ EXPECT_EQ(0x1U, types->GetType(0));
+ EXPECT_EQ(0x1000U, types->GetType(1));
+ EXPECT_EQ(0xAB0CU, types->GetType(2));
+}
+
+TEST_F(StunTest, WriteMessageWithAUInt16ListAttribute) {
+ StunMessage msg;
+ size_t size = sizeof(kStunMessageWithUInt16ListAttribute);
+
+ msg.SetType(STUN_BINDING_REQUEST);
+ msg.SetTransactionID(
+ std::string(reinterpret_cast<const char*>(kTestTransactionId2),
+ kStunTransactionIdLength));
+ CheckStunTransactionID(msg, kTestTransactionId2, kStunTransactionIdLength);
+ StunUInt16ListAttribute* list = StunAttribute::CreateUnknownAttributes();
+ list->AddType(0x1U);
+ list->AddType(0x1000U);
+ list->AddType(0xAB0CU);
+ msg.AddAttribute(list);
+ CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ ASSERT_EQ(size, out.Length());
+ // Check everything up to the padding.
+ ASSERT_EQ(0, std::memcmp(out.Data(), kStunMessageWithUInt16ListAttribute,
+ size - 2));
+}
+
+void CheckFailureToRead(const unsigned char* testcase, size_t length) {
+ StunMessage msg;
+ const char* input = reinterpret_cast<const char*>(testcase);
+ talk_base::ByteBuffer buf(input, length);
+ ASSERT_FALSE(msg.Read(&buf));
+}
+
+TEST_F(StunTest, FailToReadInvalidMessages) {
+ CheckFailureToRead(kStunMessageWithZeroLength,
+ kRealLengthOfInvalidLengthTestCases);
+ CheckFailureToRead(kStunMessageWithSmallLength,
+ kRealLengthOfInvalidLengthTestCases);
+ CheckFailureToRead(kStunMessageWithExcessLength,
+ kRealLengthOfInvalidLengthTestCases);
+}
+
+#undef ReadStunMessage
+
+// Test that we don't care what order we set the parts of an address
+TEST_F(StunTest, CreateAddressInArbitraryOrder) {
+ StunAddressAttribute* addr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ // Port first
+ addr->SetPort(kTestMessagePort1);
+ addr->SetIP(talk_base::IPAddress(kIPv4TestAddress1));
+ ASSERT_EQ(kTestMessagePort1, addr->port());
+ ASSERT_EQ(talk_base::IPAddress(kIPv4TestAddress1), addr->ipaddr());
+
+ StunAddressAttribute* addr2 =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ // IP first
+ addr2->SetIP(talk_base::IPAddress(kIPv4TestAddress1));
+ addr2->SetPort(kTestMessagePort2);
+ ASSERT_EQ(kTestMessagePort2, addr2->port());
+ ASSERT_EQ(talk_base::IPAddress(kIPv4TestAddress1), addr2->ipaddr());
+
+ delete addr;
+ delete addr2;
+}
+
+// Legacy test bodies
+static void DoTest(const char* input, size_t size, const char* transaction_id) {
+ StunMessage msg, msg2;
+ in_addr legacy_in_addr;
+ legacy_in_addr.s_addr = htonl(17U);
+ talk_base::IPAddress legacy_ip(legacy_in_addr);
+
+ talk_base::ByteBuffer buf(input, size);
+ EXPECT_TRUE(msg.Read(&buf));
+
+ EXPECT_EQ(STUN_BINDING_REQUEST, msg.type());
+ EXPECT_EQ(size - 20, msg.length());
+ EXPECT_EQ(transaction_id, msg.transaction_id());
+
+ msg2.SetType(STUN_BINDING_REQUEST);
+ msg2.SetTransactionID(transaction_id);
+
+ const StunAddressAttribute* addr = msg.GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ StunAddressAttribute* addr2 =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ msg2.AddAttribute(addr2);
+
+ const StunByteStringAttribute* bytes = msg.GetByteString(STUN_ATTR_USERNAME);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(12, bytes->length());
+ EXPECT_EQ(0, std::memcmp(bytes->bytes(), "abcdefghijkl", bytes->length()));
+
+ StunByteStringAttribute* bytes2 =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ bytes2->CopyBytes("abcdefghijkl");
+ msg2.AddAttribute(bytes2);
+
+ bytes = msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(20, bytes->length());
+ EXPECT_EQ(0, std::memcmp(bytes->bytes(),
+ "abcdefghijklmnopqrst",
+ bytes->length()));
+
+ bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MESSAGE_INTEGRITY);
+ bytes2->CopyBytes("abcdefghijklmnopqrst");
+ msg2.AddAttribute(bytes2);
+
+ const StunErrorCodeAttribute* ecode = msg.GetErrorCode();
+ ASSERT_TRUE(ecode != NULL);
+ EXPECT_EQ(2, ecode->error_class());
+ EXPECT_EQ(10, ecode->number());
+ EXPECT_EQ("foo bar!", ecode->reason());
+
+ StunErrorCodeAttribute* ecode2 = StunAttribute::CreateErrorCode();
+ ecode2->SetErrorClass(2);
+ ecode2->SetNumber(10);
+ ecode2->SetReason("foo bar!");
+ msg2.AddAttribute(ecode2);
+
+ const StunUInt16ListAttribute* unknown = msg.GetUnknownAttributes();
+ ASSERT_TRUE(unknown != NULL);
+ EXPECT_EQ(2U, unknown->Size());
+ EXPECT_EQ(1U, unknown->GetType(0));
+ EXPECT_EQ(2U, unknown->GetType(1));
+
+ StunUInt16ListAttribute* unknown2 = StunAttribute::CreateUnknownAttributes();
+ unknown2->AddType(1);
+ unknown2->AddType(2);
+ msg2.AddAttribute(unknown2);
+
+ const StunUInt32Attribute* uval = msg.GetUInt32(STUN_ATTR_LIFETIME);
+ ASSERT_TRUE(uval != NULL);
+ EXPECT_EQ(11U, uval->value());
+
+ StunUInt32Attribute* uval2 = StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ uval2->SetValue(11);
+ msg2.AddAttribute(uval2);
+
+ bytes = msg.GetByteString(STUN_ATTR_MAGIC_COOKIE);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(4, bytes->length());
+ EXPECT_EQ(0, std::memcmp(bytes->bytes(), TURN_MAGIC_COOKIE_VALUE,
+ sizeof(TURN_MAGIC_COOKIE_VALUE)));
+
+ bytes2 = StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ bytes2->CopyBytes(reinterpret_cast<const char*>(TURN_MAGIC_COOKIE_VALUE),
+ sizeof(TURN_MAGIC_COOKIE_VALUE));
+ msg2.AddAttribute(bytes2);
+
+ uval = msg.GetUInt32(STUN_ATTR_BANDWIDTH);
+ ASSERT_TRUE(uval != NULL);
+ EXPECT_EQ(6U, uval->value());
+
+ uval2 = StunAttribute::CreateUInt32(STUN_ATTR_BANDWIDTH);
+ uval2->SetValue(6);
+ msg2.AddAttribute(uval2);
+
+ addr = msg.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ addr2 = StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ msg2.AddAttribute(addr2);
+
+ addr = msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ ASSERT_TRUE(addr != NULL);
+ EXPECT_EQ(1, addr->family());
+ EXPECT_EQ(13, addr->port());
+ EXPECT_EQ(legacy_ip, addr->ipaddr());
+
+ addr2 = StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ addr2->SetPort(13);
+ addr2->SetIP(legacy_ip);
+ msg2.AddAttribute(addr2);
+
+ bytes = msg.GetByteString(STUN_ATTR_DATA);
+ ASSERT_TRUE(bytes != NULL);
+ EXPECT_EQ(7, bytes->length());
+ EXPECT_EQ(0, std::memcmp(bytes->bytes(), "abcdefg", bytes->length()));
+
+ bytes2 = StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ bytes2->CopyBytes("abcdefg");
+ msg2.AddAttribute(bytes2);
+
+ talk_base::ByteBuffer out;
+ msg.Write(&out);
+ EXPECT_EQ(size, out.Length());
+ size_t len1 = out.Length();
+ std::string outstring;
+ out.ReadString(&outstring, len1);
+ EXPECT_EQ(0, std::memcmp(outstring.c_str(), input, len1));
+
+ talk_base::ByteBuffer out2;
+ msg2.Write(&out2);
+ EXPECT_EQ(size, out2.Length());
+ size_t len2 = out2.Length();
+ std::string outstring2;
+ out2.ReadString(&outstring2, len2);
+ EXPECT_EQ(0, std::memcmp(outstring2.c_str(), input, len2));
+}
+
+TEST_F(StunTest, TestStunPacket) {
+ DoTest(reinterpret_cast<const char*>(kStunMessageWithManyAttributes),
+ sizeof(kStunMessageWithManyAttributes),
+ "0123456789ab");
+}
+
+TEST_F(StunTest, TestRejectsRtcpPacket) {
+ StunMessage msg;
+
+ talk_base::ByteBuffer buf(
+ reinterpret_cast<const char*>(kRtcpPacket), sizeof(kRtcpPacket));
+ EXPECT_FALSE(msg.Read(&buf));
+}
+
+TEST_F(StunTest, TestLegacyPacket) {
+ // The RFC3489 packet in this test is the same as
+ // kStunMessageWithManyAttributes, but with a different value where the
+ // magic cookie was.
+ talk_base::scoped_array<char>
+ rfc3489_packet(new char[sizeof(kStunMessageWithManyAttributes)]);
+ memcpy(rfc3489_packet.get(), kStunMessageWithManyAttributes,
+ sizeof(kStunMessageWithManyAttributes));
+ // Overwrites the magic cookie here.
+ memcpy(&rfc3489_packet[4], &kStunMessageWithManyAttributes[8], 4);
+
+ DoTest(reinterpret_cast<const char*>(rfc3489_packet.get()),
+ sizeof(kStunMessageWithManyAttributes), "01230123456789ab");
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
index ba797ff..7328b99 100644
--- a/talk/p2p/base/stunport.cc
+++ b/talk/p2p/base/stunport.cc
@@ -66,7 +66,7 @@
} else if (addr_attr->family() != 1) {
LOG(LS_ERROR) << "Binding address has bad family";
} else {
- talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+ talk_base::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
port_->AddAddress(addr, "udp", true);
}
@@ -127,7 +127,7 @@
StunPort::StunPort(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
- uint32 ip, int min_port, int max_port,
+ const talk_base::IPAddress& ip, int min_port, int max_port,
const talk_base::SocketAddress& server_addr)
: Port(thread, STUN_PORT_TYPE, factory, network, ip, min_port, max_port),
server_addr_(server_addr),
diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h
index 26f46a5..c74ad4d 100644
--- a/talk/p2p/base/stunport.h
+++ b/talk/p2p/base/stunport.h
@@ -49,7 +49,8 @@
static StunPort* Create(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
- uint32 ip, int min_port, int max_port,
+ const talk_base::IPAddress& ip,
+ int min_port, int max_port,
const talk_base::SocketAddress& server_addr) {
StunPort* port = new StunPort(thread, factory, network,
ip, min_port, max_port, server_addr);
@@ -88,7 +89,8 @@
protected:
StunPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip, int min_port, int max_port,
+ talk_base::Network* network, const talk_base::IPAddress& ip,
+ int min_port, int max_port,
const talk_base::SocketAddress& server_addr);
bool Init();
diff --git a/talk/p2p/base/stunport_unittest.cc b/talk/p2p/base/stunport_unittest.cc
new file mode 100644
index 0000000..3d830ba
--- /dev/null
+++ b/talk/p2p/base/stunport_unittest.cc
@@ -0,0 +1,141 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/teststunserver.h"
+
+using talk_base::SocketAddress;
+
+static const SocketAddress kLocalAddr("127.0.0.1", 0);
+static const SocketAddress kStunAddr("127.0.0.1", 5000);
+static const SocketAddress kBadAddr("0.0.0.1", 5000);
+static const SocketAddress kStunHostnameAddr("localhost", 5000);
+static const SocketAddress kBadHostnameAddr("not-a-real-hostname", 5000);
+static const int kTimeoutMs = 10000;
+
+// Tests connecting a StunPort to a fake STUN server (cricket::StunServer)
+// TODO: Use a VirtualSocketServer here. We have to use a
+// PhysicalSocketServer right now since DNS is not part of SocketServer yet.
+class StunPortTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ StunPortTest()
+ : network_("unittest", "unittest", talk_base::IPAddress(INADDR_ANY)),
+ socket_factory_(talk_base::Thread::Current()),
+ stun_server_(new cricket::TestStunServer(
+ talk_base::Thread::Current(), kStunAddr)),
+ done_(false), error_(false) {
+ }
+
+ const cricket::Port* port() const { return stun_port_.get(); }
+ bool done() const { return done_; }
+ bool error() const { return error_; }
+
+ void CreateStunPort(const talk_base::SocketAddress& server_addr) {
+ stun_port_.reset(cricket::StunPort::Create(
+ talk_base::Thread::Current(), &socket_factory_, &network_,
+ kLocalAddr.ipaddr(), 0, 0, server_addr));
+ stun_port_->SignalAddressReady.connect(this,
+ &StunPortTest::OnAddressReady);
+ stun_port_->SignalAddressError.connect(this,
+ &StunPortTest::OnAddressError);
+ }
+
+ void PrepareAddress() {
+ stun_port_->PrepareAddress();
+ }
+
+ protected:
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ talk_base::InitRandom(NULL, 0);
+ }
+
+ void OnAddressReady(cricket::Port* port) {
+ done_ = true;
+ error_ = false;
+ }
+ void OnAddressError(cricket::Port* port) {
+ done_ = true;
+ error_ = true;
+ }
+
+ private:
+ talk_base::Network network_;
+ talk_base::BasicPacketSocketFactory socket_factory_;
+ talk_base::scoped_ptr<cricket::StunPort> stun_port_;
+ talk_base::scoped_ptr<cricket::TestStunServer> stun_server_;
+ bool done_;
+ bool error_;
+};
+
+// Test that we can create a STUN port
+TEST_F(StunPortTest, TestBasic) {
+ CreateStunPort(kStunAddr);
+ EXPECT_EQ("stun", port()->type());
+ EXPECT_EQ(0U, port()->candidates().size());
+}
+
+// Test that we can get an address from a STUN server.
+TEST_F(StunPortTest, TestPrepareAddress) {
+ CreateStunPort(kStunAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->candidates()[0].address()));
+}
+
+// Test that we fail properly if we can't get an address.
+TEST_F(StunPortTest, TestPrepareAddressFail) {
+ CreateStunPort(kBadAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->candidates().size());
+}
+
+// Test that we can get an address from a STUN server specified by a hostname.
+TEST_F(StunPortTest, TestPrepareAddressHostname) {
+ CreateStunPort(kStunHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ ASSERT_EQ(1U, port()->candidates().size());
+ EXPECT_TRUE(kLocalAddr.EqualIPs(port()->candidates()[0].address()));
+}
+
+// Test that we handle hostname lookup failures properly.
+TEST_F(StunPortTest, TestPrepareAddressHostnameFail) {
+ CreateStunPort(kBadHostnameAddr);
+ PrepareAddress();
+ EXPECT_TRUE_WAIT(done(), kTimeoutMs);
+ EXPECT_TRUE(error());
+ EXPECT_EQ(0U, port()->candidates().size());
+}
diff --git a/talk/p2p/base/stunrequest_unittest.cc b/talk/p2p/base/stunrequest_unittest.cc
new file mode 100644
index 0000000..524c5e6
--- /dev/null
+++ b/talk/p2p/base/stunrequest_unittest.cc
@@ -0,0 +1,225 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/stunrequest.h"
+
+using namespace cricket;
+
+class StunRequestTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ static void SetUpTestCase() {
+ talk_base::InitRandom(NULL, 0);
+ }
+ StunRequestTest()
+ : manager_(talk_base::Thread::Current()),
+ request_count_(0), response_(NULL),
+ success_(false), failure_(false), timeout_(false) {
+ manager_.SignalSendPacket.connect(this, &StunRequestTest::OnSendPacket);
+ }
+
+ void OnSendPacket(const void* data, size_t size, StunRequest* req) {
+ request_count_++;
+ }
+
+ void OnResponse(StunMessage* res) {
+ response_ = res;
+ success_ = true;
+ }
+ void OnErrorResponse(StunMessage* res) {
+ response_ = res;
+ failure_ = true;
+ }
+ void OnTimeout() {
+ timeout_ = true;
+ }
+
+ protected:
+ static StunMessage* CreateStunMessage(StunMessageType type,
+ StunMessage* req) {
+ StunMessage* msg = new StunMessage();
+ msg->SetType(type);
+ if (req) {
+ msg->SetTransactionID(req->transaction_id());
+ } else {
+ msg->SetTransactionID(
+ talk_base::CreateRandomString(kStunTransactionIdLength));
+ }
+ return msg;
+ }
+ static int TotalDelay(int sends) {
+ int total = 0;
+ for (int i = 0; i < sends; i++) {
+ if (i < 4)
+ total += 100 << i;
+ else
+ total += 1600;
+ }
+ return total;
+ }
+
+ StunRequestManager manager_;
+ int request_count_;
+ StunMessage* response_;
+ bool success_;
+ bool failure_;
+ bool timeout_;
+};
+
+// Forwards results to the test class.
+class StunRequestThunker : public StunRequest {
+ public:
+ StunRequestThunker(StunMessage* msg, StunRequestTest* test)
+ : StunRequest(msg), test_(test) {}
+ explicit StunRequestThunker(StunRequestTest* test) : test_(test) {}
+ private:
+ virtual void OnResponse(StunMessage* res) {
+ test_->OnResponse(res);
+ }
+ virtual void OnErrorResponse(StunMessage* res) {
+ test_->OnErrorResponse(res);
+ }
+ virtual void OnTimeout() {
+ test_->OnTimeout();
+ }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ }
+
+ StunRequestTest* test_;
+};
+
+// Test handling of a normal binding response.
+TEST_F(StunRequestTest, TestSuccess) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test handling of an error binding response.
+TEST_F(StunRequestTest, TestError) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_ERROR_RESPONSE, req);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_FALSE(success_);
+ EXPECT_TRUE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test handling of a binding response with the wrong transaction id.
+TEST_F(StunRequestTest, TestUnexpected) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, NULL);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ EXPECT_FALSE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test that requests are sent at the right times, and that the 9th request
+// (sent at 7900 ms) can be properly replied to.
+TEST_F(StunRequestTest, TestBackoff) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+ uint32 start = talk_base::Time();
+ manager_.Send(new StunRequestThunker(req, this));
+ for (int i = 0; i < 9; ++i) {
+ while (request_count_ == i)
+ talk_base::Thread::Current()->ProcessMessages(1);
+ int32 elapsed = talk_base::TimeSince(start);
+ LOG(LS_INFO) << "STUN request #" << (i + 1)
+ << " sent at " << elapsed << " ms";
+ EXPECT_GE(TotalDelay(i + 1), elapsed);
+ }
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
+
+// Test that we timeout properly if no response is received in 9500 ms.
+TEST_F(StunRequestTest, TestTimeout) {
+ StunMessage* req = CreateStunMessage(STUN_BINDING_REQUEST, NULL);
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, req);
+
+ manager_.Send(new StunRequestThunker(req, this));
+ talk_base::Thread::Current()->ProcessMessages(10000); // > STUN timeout
+ EXPECT_FALSE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == NULL);
+ EXPECT_FALSE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_TRUE(timeout_);
+ delete res;
+}
+
+// Regression test for specific crash where we receive a response with the
+// same id as a request that doesn't have an underlying StunMessage yet.
+TEST_F(StunRequestTest, TestNoEmptyRequest) {
+ StunRequestThunker* request = new StunRequestThunker(this);
+
+ manager_.SendDelayed(request, 100);
+
+ StunMessage dummy_req;
+ dummy_req.SetTransactionID(request->id());
+ StunMessage* res = CreateStunMessage(STUN_BINDING_RESPONSE, &dummy_req);
+
+ EXPECT_TRUE(manager_.CheckResponse(res));
+
+ EXPECT_TRUE(response_ == res);
+ EXPECT_TRUE(success_);
+ EXPECT_FALSE(failure_);
+ EXPECT_FALSE(timeout_);
+ delete res;
+}
diff --git a/talk/p2p/base/stunserver.cc b/talk/p2p/base/stunserver.cc
index f34fd2d..8a5d447 100644
--- a/talk/p2p/base/stunserver.cc
+++ b/talk/p2p/base/stunserver.cc
@@ -96,7 +96,7 @@
mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
}
mapped_addr->SetPort(remote_addr.port());
- mapped_addr->SetIP(remote_addr.ip());
+ mapped_addr->SetIP(remote_addr.ipaddr());
response.AddAttribute(mapped_addr);
// TODO: Add username and message-integrity.
diff --git a/talk/p2p/base/stunserver_unittest.cc b/talk/p2p/base/stunserver_unittest.cc
new file mode 100644
index 0000000..b7f2f00
--- /dev/null
+++ b/talk/p2p/base/stunserver_unittest.cc
@@ -0,0 +1,129 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/base/testclient.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+using namespace cricket;
+
+static const talk_base::SocketAddress server_addr("99.99.99.1", 3478);
+static const talk_base::SocketAddress client_addr("1.2.3.4", 1234);
+
+class StunServerTest : public testing::Test {
+ public:
+ StunServerTest()
+ : pss_(new talk_base::PhysicalSocketServer),
+ ss_(new talk_base::VirtualSocketServer(pss_.get())),
+ worker_(ss_.get()) {
+ }
+ virtual void SetUp() {
+ server_.reset(new StunServer(
+ talk_base::AsyncUDPSocket::Create(ss_.get(), server_addr)));
+ client_.reset(new talk_base::TestClient(
+ talk_base::AsyncUDPSocket::Create(ss_.get(), client_addr)));
+
+ worker_.Start();
+ }
+ void Send(const StunMessage& msg) {
+ talk_base::ByteBuffer buf;
+ msg.Write(&buf);
+ Send(buf.Data(), buf.Length());
+ }
+ void Send(const char* buf, int len) {
+ client_->SendTo(buf, len, server_addr);
+ }
+ StunMessage* Receive() {
+ StunMessage* msg = NULL;
+ talk_base::TestClient::Packet* packet = client_->NextPacket();
+ if (packet) {
+ talk_base::ByteBuffer buf(packet->buf, packet->size);
+ msg = new StunMessage();
+ msg->Read(&buf);
+ delete packet;
+ }
+ return msg;
+ }
+ private:
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> ss_;
+ talk_base::Thread worker_;
+ talk_base::scoped_ptr<StunServer> server_;
+ talk_base::scoped_ptr<talk_base::TestClient> client_;
+};
+
+TEST_F(StunServerTest, TestGood) {
+ StunMessage req;
+ std::string transaction_id = "0123456789ab";
+ req.SetType(STUN_BINDING_REQUEST);
+ req.SetTransactionID(transaction_id);
+ Send(req);
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_RESPONSE, msg->type());
+ EXPECT_EQ(req.transaction_id(), msg->transaction_id());
+
+ const StunAddressAttribute* mapped_addr =
+ msg->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ EXPECT_TRUE(mapped_addr != NULL);
+ EXPECT_EQ(1, mapped_addr->family());
+ EXPECT_EQ(client_addr.port(), mapped_addr->port());
+ if (mapped_addr->ipaddr() != client_addr.ipaddr()) {
+ LOG(LS_WARNING) << "Warning: mapped IP ("
+ << mapped_addr->ipaddr()
+ << ") != local IP (" << client_addr.ipaddr()
+ << ")";
+ }
+
+ delete msg;
+}
+
+TEST_F(StunServerTest, TestBad) {
+ 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";
+ Send(bad, std::strlen(bad));
+
+ StunMessage* msg = Receive();
+ ASSERT_TRUE(msg != NULL);
+ EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE, msg->type());
+
+ const StunErrorCodeAttribute* err = msg->GetErrorCode();
+ EXPECT_TRUE(err != NULL);
+ EXPECT_EQ(4, err->error_class());
+ EXPECT_EQ(0, err->number());
+ EXPECT_EQ("Bad Request", err->reason());
+
+ delete msg;
+}
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
index 555ba73..4db4761 100644
--- a/talk/p2p/base/tcpport.cc
+++ b/talk/p2p/base/tcpport.cc
@@ -35,7 +35,7 @@
TCPPort::TCPPort(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip,
+ talk_base::Network* network, const talk_base::IPAddress& ip,
int min_port, int max_port, bool allow_listen)
: Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
incoming_only_(false),
@@ -218,7 +218,7 @@
}
} else {
// Incoming connections should match the network address.
- ASSERT(socket_->GetLocalAddress().ip() == port->ip_);
+ ASSERT(socket_->GetLocalAddress().ipaddr() == port->ip_);
}
if (socket_) {
diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h
index 8345b12..5ce917d 100644
--- a/talk/p2p/base/tcpport.h
+++ b/talk/p2p/base/tcpport.h
@@ -50,7 +50,8 @@
static TCPPort* Create(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
- uint32 ip, int min_port, int max_port,
+ const talk_base::IPAddress& ip,
+ int min_port, int max_port,
bool allow_listen) {
TCPPort* port = new TCPPort(thread, factory, network,
ip, min_port, max_port, allow_listen);
@@ -73,8 +74,8 @@
protected:
TCPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip, int min_port, int max_port,
- bool allow_listen);
+ talk_base::Network* network, const talk_base::IPAddress& ip,
+ int min_port, int max_port, bool allow_listen);
bool Init();
// Handles sending using the local TCP socket.
diff --git a/talk/p2p/base/testrelayserver.h b/talk/p2p/base/testrelayserver.h
new file mode 100644
index 0000000..45c2b7c
--- /dev/null
+++ b/talk/p2p/base/testrelayserver.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TESTRELAYSERVER_H_
+#define TALK_P2P_BASE_TESTRELAYSERVER_H_
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/relayserver.h"
+
+namespace cricket {
+
+// A test relay server. Useful for unit tests.
+class TestRelayServer : public sigslot::has_slots<> {
+ public:
+ TestRelayServer(talk_base::Thread* thread,
+ const talk_base::SocketAddress& udp_int_addr,
+ const talk_base::SocketAddress& udp_ext_addr,
+ const talk_base::SocketAddress& tcp_int_addr,
+ const talk_base::SocketAddress& tcp_ext_addr,
+ const talk_base::SocketAddress& ssl_int_addr,
+ const talk_base::SocketAddress& ssl_ext_addr)
+ : server_(thread) {
+ server_.AddInternalSocket(talk_base::AsyncUDPSocket::Create(
+ thread->socketserver(), udp_int_addr));
+ server_.AddExternalSocket(talk_base::AsyncUDPSocket::Create(
+ thread->socketserver(), udp_ext_addr));
+
+ tcp_int_socket_.reset(CreateListenSocket(thread, tcp_int_addr));
+ tcp_ext_socket_.reset(CreateListenSocket(thread, tcp_ext_addr));
+ ssl_int_socket_.reset(CreateListenSocket(thread, ssl_int_addr));
+ ssl_ext_socket_.reset(CreateListenSocket(thread, ssl_ext_addr));
+ }
+ int GetConnectionCount() const {
+ return server_.GetConnectionCount();
+ }
+ talk_base::SocketAddressPair GetConnection(int connection) const {
+ return server_.GetConnection(connection);
+ }
+ bool HasConnection(const talk_base::SocketAddress& address) const {
+ return server_.HasConnection(address);
+ }
+
+ private:
+ talk_base::AsyncSocket* CreateListenSocket(talk_base::Thread* thread,
+ const talk_base::SocketAddress& addr) {
+ talk_base::AsyncSocket* socket =
+ thread->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+ socket->Bind(addr);
+ socket->Listen(5);
+ socket->SignalReadEvent.connect(this, &TestRelayServer::OnAccept);
+ return socket;
+ }
+ void OnAccept(talk_base::AsyncSocket* socket) {
+ bool external = (socket == tcp_ext_socket_.get() ||
+ socket == ssl_ext_socket_.get());
+ bool ssl = (socket == ssl_int_socket_.get() ||
+ socket == ssl_ext_socket_.get());
+ talk_base::AsyncSocket* raw_socket = socket->Accept(NULL);
+ if (raw_socket) {
+ talk_base::AsyncTCPSocket* packet_socket = new talk_base::AsyncTCPSocket(
+ (!ssl) ? raw_socket :
+ new talk_base::AsyncSSLServerSocket(raw_socket), false);
+ if (!external) {
+ packet_socket->SignalClose.connect(this,
+ &TestRelayServer::OnInternalClose);
+ server_.AddInternalSocket(packet_socket);
+ } else {
+ packet_socket->SignalClose.connect(this,
+ &TestRelayServer::OnExternalClose);
+ server_.AddExternalSocket(packet_socket);
+ }
+ }
+ }
+ void OnInternalClose(talk_base::AsyncPacketSocket* socket, int error) {
+ server_.RemoveInternalSocket(socket);
+ }
+ void OnExternalClose(talk_base::AsyncPacketSocket* socket, int error) {
+ server_.RemoveExternalSocket(socket);
+ }
+ private:
+ cricket::RelayServer server_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_int_socket_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> tcp_ext_socket_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> ssl_int_socket_;
+ talk_base::scoped_ptr<talk_base::AsyncSocket> ssl_ext_socket_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TESTRELAYSERVER_H_
diff --git a/talk/p2p/base/teststunserver.h b/talk/p2p/base/teststunserver.h
new file mode 100644
index 0000000..4574bf8
--- /dev/null
+++ b/talk/p2p/base/teststunserver.h
@@ -0,0 +1,54 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_TESTSTUNSERVER_H_
+#define TALK_P2P_BASE_TESTSTUNSERVER_H_
+
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+namespace cricket {
+
+// A test STUN server. Useful for unit tests.
+class TestStunServer {
+ public:
+ TestStunServer(talk_base::Thread* thread,
+ const talk_base::SocketAddress& addr)
+ : socket_(thread->socketserver()->CreateAsyncSocket(SOCK_DGRAM)),
+ udp_socket_(talk_base::AsyncUDPSocket::Create(socket_, addr)),
+ server_(udp_socket_) {
+ }
+ private:
+ talk_base::AsyncSocket* socket_;
+ talk_base::AsyncUDPSocket* udp_socket_;
+ cricket::StunServer server_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TESTSTUNSERVER_H_
diff --git a/talk/p2p/base/transport_unittest.cc b/talk/p2p/base/transport_unittest.cc
new file mode 100644
index 0000000..7718484
--- /dev/null
+++ b/talk/p2p/base/transport_unittest.cc
@@ -0,0 +1,69 @@
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/p2ptransport.h"
+
+class TransportTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ TransportTest()
+ : thread_(talk_base::Thread::Current()),
+ transport_(new cricket::P2PTransport(thread_, thread_, NULL)),
+ connecting_signalled_(false) {
+ transport_->SignalConnecting.connect(this, &TransportTest::OnConnecting);
+ }
+
+ protected:
+ void OnConnecting(cricket::Transport* transport) {
+ connecting_signalled_ = true;
+ }
+
+ talk_base::Thread* thread_;
+ talk_base::scoped_ptr<cricket::P2PTransport> transport_;
+ bool connecting_signalled_;
+};
+
+TEST_F(TransportTest, TestDestroyAllClearsPosts) {
+ EXPECT_TRUE(transport_->CreateChannel("test", "media") != NULL);
+
+ transport_->ConnectChannels();
+ transport_->DestroyAllChannels();
+
+ thread_->ProcessMessages(0);
+ EXPECT_FALSE(connecting_signalled_);
+}
+
+TEST_F(TransportTest, TestConnectChannelsDoesSignal) {
+ EXPECT_TRUE(transport_->CreateChannel("test", "media") != NULL);
+ transport_->ConnectChannels();
+ EXPECT_FALSE(connecting_signalled_);
+
+ EXPECT_TRUE_WAIT(connecting_signalled_, 100);
+}
+
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
index 2672e80..74d0107 100644
--- a/talk/p2p/base/transportchannel.h
+++ b/talk/p2p/base/transportchannel.h
@@ -47,6 +47,8 @@
readable_(false), writable_(false) {}
virtual ~TransportChannel() {}
+ // Returns the session id of this channel.
+ const std::string& session_id() const { return session_id_; }
// Returns the name of this channel.
const std::string& name() const { return name_; }
const std::string& content_type() const { return content_type_; }
@@ -66,6 +68,12 @@
// supported by all transport types.
virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+ // Sets session id which created this transport channel.
+ // This is called from TransportProxy::GetOrCreateImpl.
+ void set_session_id(const std::string& session_id) {
+ session_id_ = session_id;
+ }
+
// Returns the most recent error that occurred on this channel.
virtual int GetError() = 0;
@@ -95,7 +103,9 @@
// Sets the writable state, signaling if necessary.
void set_writable(bool writable);
+
private:
+ std::string session_id_;
std::string name_;
std::string content_type_;
bool readable_;
diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc
index f87383e..4e035b1 100644
--- a/talk/p2p/base/transportchannelproxy.cc
+++ b/talk/p2p/base/transportchannelproxy.cc
@@ -34,15 +34,16 @@
TransportChannelProxy::TransportChannelProxy(const std::string& name,
const std::string& content_type)
- : TransportChannel(name, content_type), impl_(NULL) {
+ : TransportChannel(name, content_type), impl_(NULL), owner_(false) {
}
TransportChannelProxy::~TransportChannelProxy() {
- if (impl_)
+ if (owner_ && impl_)
impl_->GetTransport()->DestroyChannel(impl_->name());
}
-void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl,
+ bool owner) {
impl_ = impl;
impl_->SignalReadableState.connect(
this, &TransportChannelProxy::OnReadableState);
@@ -56,6 +57,7 @@
impl_->SetOption(it->first, it->second);
}
pending_options_.clear();
+ owner_ = owner;
}
int TransportChannelProxy::SendPacket(const char* data, size_t len) {
diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h
index 957c304..6923429 100644
--- a/talk/p2p/base/transportchannelproxy.h
+++ b/talk/p2p/base/transportchannelproxy.h
@@ -49,7 +49,10 @@
TransportChannelImpl* impl() { return impl_; }
// Sets the implementation to which we will proxy.
- void SetImplementation(TransportChannelImpl* impl);
+ // Impl can be belong to some other TransportChannelProxy,
+ // in that case it should't try to delete. TODO - Remove this hack
+ // when ref count support is available.
+ void SetImplementation(TransportChannelImpl* impl, bool owner);
// Implementation of the TransportChannel interface. These simply forward to
// the implementation.
@@ -62,6 +65,7 @@
typedef std::pair<talk_base::Socket::Option, int> OptionPair;
typedef std::vector<OptionPair> OptionList;
TransportChannelImpl* impl_;
+ bool owner_;
OptionList pending_options_;
// Catch signals from the implementation channel. These just forward to the
diff --git a/talk/p2p/base/udpport.cc b/talk/p2p/base/udpport.cc
index 8355b00..311fb45 100644
--- a/talk/p2p/base/udpport.cc
+++ b/talk/p2p/base/udpport.cc
@@ -38,7 +38,7 @@
UDPPort::UDPPort(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
- uint32 ip, int min_port, int max_port)
+ const talk_base::IPAddress& ip, int min_port, int max_port)
: Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
socket_(NULL),
error_(0) {
diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h
index d74d4a3..5d767bb 100644
--- a/talk/p2p/base/udpport.h
+++ b/talk/p2p/base/udpport.h
@@ -48,7 +48,8 @@
static UDPPort* Create(talk_base::Thread* thread,
talk_base::PacketSocketFactory* factory,
talk_base::Network* network,
- uint32 ip, int min_port, int max_port) {
+ const talk_base::IPAddress& ip,
+ int min_port, int max_port) {
UDPPort* port = new UDPPort(thread, factory, network,
ip, min_port, max_port);
if (!port->Init()) {
@@ -68,7 +69,8 @@
protected:
UDPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
- talk_base::Network* network, uint32 ip, int min_port, int max_port);
+ talk_base::Network* network, const talk_base::IPAddress& ip,
+ int min_port, int max_port);
bool Init();
// Handles sending using the local UDP socket.
diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
index 51d069e..b77d5ed 100644
--- a/talk/p2p/client/basicportallocator.cc
+++ b/talk/p2p/client/basicportallocator.cc
@@ -154,7 +154,7 @@
BasicPortAllocatorSession* session_;
talk_base::Network* network_;
- uint32 ip_;
+ talk_base::IPAddress ip_;
PortConfiguration* config_;
bool running_;
int step_;
@@ -229,8 +229,8 @@
BasicPortAllocator *allocator,
const std::string &name,
const std::string &session_type)
- : PortAllocatorSession(allocator->flags()), allocator_(allocator),
- name_(name), session_type_(session_type), network_thread_(NULL),
+ : PortAllocatorSession(name, session_type, allocator->flags()),
+ allocator_(allocator), network_thread_(NULL),
socket_factory_(allocator->socket_factory()), allocation_started_(false),
network_manager_started_(false),
running_(false) {
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
index 0e1d00a..f38bac6 100644
--- a/talk/p2p/client/basicportallocator.h
+++ b/talk/p2p/client/basicportallocator.h
@@ -43,7 +43,7 @@
public:
BasicPortAllocator(talk_base::NetworkManager* network_manager,
talk_base::PacketSocketFactory* socket_factory);
- BasicPortAllocator(talk_base::NetworkManager* network_manager);
+ explicit BasicPortAllocator(talk_base::NetworkManager* network_manager);
BasicPortAllocator(talk_base::NetworkManager* network_manager,
const talk_base::SocketAddress& stun_server,
const talk_base::SocketAddress& relay_server_udp,
@@ -114,8 +114,6 @@
~BasicPortAllocatorSession();
virtual BasicPortAllocator* allocator() { return allocator_; }
- const std::string& name() const { return name_; }
- const std::string& session_type() const { return session_type_; }
talk_base::Thread* network_thread() { return network_thread_; }
talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
@@ -130,7 +128,7 @@
// 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);
+ virtual void ConfigReady(PortConfiguration* config);
// MessageHandler. Can be overriden if message IDs do not conflict.
virtual void OnMessage(talk_base::Message *message);
@@ -154,8 +152,6 @@
void OnShake();
BasicPortAllocator* allocator_;
- std::string name_;
- std::string session_type_;
talk_base::Thread* network_thread_;
talk_base::scoped_ptr<talk_base::PacketSocketFactory> owned_socket_factory_;
talk_base::PacketSocketFactory* socket_factory_;
diff --git a/talk/p2p/client/connectivitychecker.cc b/talk/p2p/client/connectivitychecker.cc
new file mode 100644
index 0000000..de97924
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.cc
@@ -0,0 +1,513 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include <string>
+
+#include "talk/p2p/client/connectivitychecker.h"
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/autodetectproxy.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/logging.h"
+#include "talk/base/proxydetect.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/candidate.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"
+
+namespace cricket {
+
+static const char kSessionTypeVideo[] =
+ "http://www.google.com/session/video";
+static const char kSessionNameRtp[] = "rtp";
+
+static const char kDefaultStunHostname[] = "stun.l.google.com";
+static const int kDefaultStunPort = 19302;
+
+// Default maximum time in milliseconds we will wait for connections.
+static const uint32 kDefaultTimeoutMs = 3000;
+
+enum {
+ MSG_START = 1,
+ MSG_STOP = 2,
+ MSG_TIMEOUT = 3,
+ MSG_SIGNAL_RESULTS = 4
+};
+
+class TestHttpPortAllocator : public HttpPortAllocator {
+ public:
+ TestHttpPortAllocator(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent,
+ const std::string& relay_token) :
+ HttpPortAllocator(network_manager, user_agent) {
+ SetRelayToken(relay_token);
+ }
+ PortAllocatorSession* CreateSession(
+ const std::string& name, const std::string& session_type) {
+ return new TestHttpPortAllocatorSession(this, name, session_type,
+ stun_hosts(), relay_hosts(),
+ relay_token(), user_agent());
+ }
+};
+
+void TestHttpPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+ SignalConfigReady(config, proxy_);
+}
+
+void TestHttpPortAllocatorSession::OnRequestDone(
+ talk_base::SignalThread* data) {
+ talk_base::AsyncHttpRequest* request =
+ static_cast<talk_base::AsyncHttpRequest*>(data);
+
+ // Tell the checker that the request is complete.
+ SignalRequestDone(request);
+
+ // Pass on the response to super class.
+ HttpPortAllocatorSession::OnRequestDone(data);
+}
+
+ConnectivityChecker::ConnectivityChecker(
+ talk_base::Thread* worker,
+ const std::string& jid,
+ const std::string& session_id,
+ const std::string& user_agent,
+ const std::string& relay_token,
+ const std::string& connection)
+ : worker_(worker),
+ jid_(jid),
+ session_id_(session_id),
+ user_agent_(user_agent),
+ relay_token_(relay_token),
+ connection_(connection),
+ proxy_detect_(NULL),
+ timeout_ms_(kDefaultTimeoutMs),
+ stun_address_(kDefaultStunHostname, kDefaultStunPort) {
+}
+
+ConnectivityChecker::~ConnectivityChecker() {
+ Stop();
+ nics_.clear();
+}
+
+bool ConnectivityChecker::Initialize() {
+ network_manager_.reset(CreateNetworkManager());
+ socket_factory_.reset(CreateSocketFactory(worker_));
+ port_allocator_.reset(CreatePortAllocator(network_manager_.get(),
+ user_agent_, relay_token_));
+ return true;
+}
+
+void ConnectivityChecker::Start() {
+ main_ = talk_base::Thread::Current();
+ worker_->Post(this, MSG_START);
+}
+
+void ConnectivityChecker::Stop() {
+ worker_->Post(this, MSG_STOP);
+}
+
+void ConnectivityChecker::CleanUp() {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ worker_->Clear(this, MSG_TIMEOUT);
+ if (proxy_detect_) {
+ proxy_detect_->Release();
+ proxy_detect_ = NULL;
+ }
+
+ for (uint32 i = 0; i < sessions_.size(); ++i) {
+ delete sessions_[i];
+ }
+ sessions_.clear();
+ for (uint32 i = 0; i < ports_.size(); ++i) {
+ delete ports_[i];
+ }
+ ports_.clear();
+}
+
+bool ConnectivityChecker::AddNic(const talk_base::IPAddress& ip,
+ const talk_base::SocketAddress& proxy_addr) {
+ NicMap::iterator i = nics_.find(NicId(ip, proxy_addr));
+ if (i != nics_.end()) {
+ // Already have it.
+ return false;
+ }
+ uint32 now = talk_base::Time();
+ NicInfo info;
+ info.ip = ip;
+ info.proxy_info = GetProxyInfo();
+ info.stun.start_time_ms = now;
+ nics_.insert(std::pair<NicId, NicInfo>(NicId(ip, proxy_addr), info));
+ return true;
+}
+
+void ConnectivityChecker::SetProxyInfo(const talk_base::ProxyInfo& proxy_info) {
+ port_allocator_->set_proxy(user_agent_, proxy_info);
+ AllocatePorts();
+}
+
+talk_base::ProxyInfo ConnectivityChecker::GetProxyInfo() const {
+ talk_base::ProxyInfo proxy_info;
+ if (proxy_detect_) {
+ proxy_info = proxy_detect_->proxy();
+ }
+ return proxy_info;
+}
+
+void ConnectivityChecker::CheckNetworks() {
+ network_manager_->SignalNetworksChanged.connect(
+ this, &ConnectivityChecker::OnNetworksChanged);
+ network_manager_->StartUpdating();
+}
+
+void ConnectivityChecker::OnMessage(talk_base::Message *msg) {
+ switch (msg->message_id) {
+ case MSG_START:
+ ASSERT(worker_ == talk_base::Thread::Current());
+ worker_->PostDelayed(timeout_ms_, this, MSG_TIMEOUT);
+ CheckNetworks();
+ break;
+ case MSG_STOP:
+ // We were stopped, just close down without signaling.
+ OnCheckDone(false);
+ break;
+ case MSG_TIMEOUT:
+ // Close down and signal results.
+ OnCheckDone(true);
+ break;
+ case MSG_SIGNAL_RESULTS:
+ ASSERT(main_ == talk_base::Thread::Current());
+ SignalCheckDone(this);
+ break;
+ default:
+ LOG(LS_ERROR) << "Unknown message: " << msg->message_id;
+ }
+}
+
+void ConnectivityChecker::OnCheckDone(bool signal_results) {
+ // Clean up memory allocated by the worker thread.
+ CleanUp();
+
+ if (signal_results) {
+ main_->Post(this, MSG_SIGNAL_RESULTS);
+ }
+}
+
+void ConnectivityChecker::OnProxyDetect(talk_base::SignalThread* thread) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ if (proxy_detect_->proxy().type != talk_base::PROXY_NONE) {
+ SetProxyInfo(proxy_detect_->proxy());
+ }
+}
+
+void ConnectivityChecker::OnRequestDone(talk_base::AsyncHttpRequest* request) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ // Since we don't know what nic were actually used for the http request,
+ // for now, just use the first one.
+ std::vector<talk_base::Network*> networks;
+ network_manager_->GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_ERROR) << "No networks while registering http start.";
+ return;
+ }
+ talk_base::ProxyInfo proxy_info = request->proxy();
+ NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address));
+ if (i != nics_.end()) {
+ int port = request->port();
+ uint32 now = talk_base::Time();
+ NicInfo* nic_info = &i->second;
+ if (port == talk_base::HTTP_DEFAULT_PORT) {
+ nic_info->http.rtt = now - nic_info->http.start_time_ms;
+ } else if (port == talk_base::HTTP_SECURE_PORT) {
+ nic_info->https.rtt = now - nic_info->https.start_time_ms;
+ } else {
+ LOG(LS_ERROR) << "Got response with unknown port: " << port;
+ }
+ } else {
+ LOG(LS_ERROR) << "No nic info found while receiving response.";
+ }
+}
+
+void ConnectivityChecker::OnConfigReady(
+ const PortConfiguration* config,
+ const talk_base::ProxyInfo& proxy_info) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+
+ // Since we send requests on both HTTP and HTTPS we will get two
+ // configs per nic. Results from the second will overwrite the
+ // result from the first.
+ // TODO: Handle multiple pings on one nic.
+ CreateRelayPorts(config, proxy_info);
+}
+
+void ConnectivityChecker::OnRelayAddressReady(Port* port) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ RelayPort* relay_port = reinterpret_cast<RelayPort*>(port);
+ const ProtocolAddress* address = relay_port->ServerAddress(0);
+ talk_base::IPAddress ip = port->network()->ip();
+ NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+ if (i != nics_.end()) {
+ // We have it already, add the new information.
+ NicInfo* nic_info = &i->second;
+ ConnectInfo* connect_info = NULL;
+ if (address) {
+ switch (address->proto) {
+ case PROTO_UDP:
+ connect_info = &nic_info->udp;
+ break;
+ case PROTO_TCP:
+ connect_info = &nic_info->tcp;
+ break;
+ case PROTO_SSLTCP:
+ connect_info = &nic_info->ssltcp;
+ break;
+ default:
+ LOG(LS_ERROR) << " relay address with bad protocol added";
+ }
+ if (connect_info) {
+ connect_info->rtt =
+ talk_base::TimeSince(connect_info->start_time_ms);
+ }
+ }
+ } else {
+ LOG(LS_ERROR) << " got relay address for non-existing nic";
+ }
+}
+
+void ConnectivityChecker::OnStunAddressReady(Port* port) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ const std::vector<Candidate> candidates = port->candidates();
+ Candidate c = candidates[0];
+ talk_base::IPAddress ip = port->network()->ip();
+ NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+ if (i != nics_.end()) {
+ // We have it already, add the new information.
+ uint32 now = talk_base::Time();
+ NicInfo* nic_info = &i->second;
+ nic_info->external_address = c.address();
+ nic_info->stun_server_address = static_cast<StunPort*>(port)->server_addr();
+ nic_info->stun.rtt = now - nic_info->stun.start_time_ms;
+ } else {
+ LOG(LS_ERROR) << "Got stun address for non-existing nic";
+ }
+}
+
+void ConnectivityChecker::OnStunAddressError(Port* port) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ LOG(LS_ERROR) << "Stun address error.";
+ talk_base::IPAddress ip = port->network()->ip();
+ NicMap::iterator i = nics_.find(NicId(ip, port->proxy().address));
+ if (i != nics_.end()) {
+ // We have it already, add the new information.
+ NicInfo* nic_info = &i->second;
+ nic_info->stun_server_address = static_cast<StunPort*>(port)->server_addr();
+ }
+}
+
+void ConnectivityChecker::OnRelayAddressError(Port* port) {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ LOG(LS_ERROR) << "Relay address error.";
+}
+
+void ConnectivityChecker::OnNetworksChanged() {
+ ASSERT(worker_ == talk_base::Thread::Current());
+ std::vector<talk_base::Network*> networks;
+ network_manager_->GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_ERROR) << "Machine has no networks; nothing to do";
+ return;
+ }
+ AllocatePorts();
+}
+
+HttpPortAllocator* ConnectivityChecker::CreatePortAllocator(
+ talk_base::NetworkManager* network_manager,
+ const std::string& user_agent,
+ const std::string& relay_token) {
+ return new TestHttpPortAllocator(network_manager, user_agent, relay_token);
+}
+
+StunPort* ConnectivityChecker::CreateStunPort(
+ const PortConfiguration* config, talk_base::Network* network) {
+ return StunPort::Create(worker_,
+ socket_factory_.get(),
+ network,
+ network->ip(),
+ 0,
+ 0,
+ config->stun_address);
+}
+
+RelayPort* ConnectivityChecker::CreateRelayPort(
+ const PortConfiguration* config, talk_base::Network* network) {
+ return RelayPort::Create(worker_,
+ socket_factory_.get(),
+ network,
+ network->ip(),
+ port_allocator_->min_port(),
+ port_allocator_->max_port(),
+ config->username,
+ config->password,
+ config->magic_cookie);
+}
+
+void ConnectivityChecker::CreateRelayPorts(
+ const PortConfiguration* config,
+ const talk_base::ProxyInfo& proxy_info) {
+ PortConfiguration::RelayList::const_iterator relay;
+ std::vector<talk_base::Network*> networks;
+ network_manager_->GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_ERROR) << "Machine has no networks; no relay ports created.";
+ return;
+ }
+ for (relay = config->relays.begin();
+ relay != config->relays.end(); ++relay) {
+ for (uint32 i = 0; i < networks.size(); ++i) {
+ NicMap::iterator iter = nics_.find(NicId(networks[i]->ip(),
+ proxy_info.address));
+ if (iter != nics_.end()) {
+ // TODO: Now setting the same start time for all protocols.
+ // This might affect accuracy, but since we are mainly looking for
+ // connect failures or number that stick out, this is good enough.
+ uint32 now = talk_base::Time();
+ NicInfo* nic_info = &iter->second;
+ nic_info->udp.start_time_ms = now;
+ nic_info->tcp.start_time_ms = now;
+ nic_info->ssltcp.start_time_ms = now;
+
+ // 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) {
+ RelayPort* port = CreateRelayPort(config, networks[i]);
+ port->AddServerAddress(*relay_port);
+ port->AddExternalAddress(*relay_port);
+
+ nic_info->media_server_address = port->ServerAddress(0)->address;
+
+ // Listen to network events.
+ port->SignalAddressReady.connect(
+ this, &ConnectivityChecker::OnRelayAddressReady);
+ port->SignalAddressError.connect(
+ this, &ConnectivityChecker::OnRelayAddressError);
+
+ port->set_proxy(user_agent_, proxy_info);
+
+ // Start fetching an address for this port.
+ port->PrepareAddress();
+ ports_.push_back(port);
+ }
+ } else {
+ LOG(LS_ERROR) << "Failed to find nic info when creating relay ports.";
+ }
+ }
+ }
+}
+
+void ConnectivityChecker::AllocatePorts() {
+ PortConfiguration config(stun_address_,
+ talk_base::CreateRandomString(16),
+ talk_base::CreateRandomString(16),
+ "");
+ std::vector<talk_base::Network*> networks;
+ network_manager_->GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_ERROR) << "Machine has no networks; no ports will be allocated";
+ return;
+ }
+ talk_base::ProxyInfo proxy_info = GetProxyInfo();
+ bool allocate_relay_ports = false;
+ for (uint32 i = 0; i < networks.size(); ++i) {
+ if (AddNic(networks[i]->ip(), proxy_info.address)) {
+ Port* port = CreateStunPort(&config, networks[i]);
+
+ // Listen to network events.
+ port->SignalAddressReady.connect(
+ this, &ConnectivityChecker::OnStunAddressReady);
+ port->SignalAddressError.connect(
+ this, &ConnectivityChecker::OnStunAddressError);
+
+ port->set_proxy(user_agent_, proxy_info);
+ port->PrepareAddress();
+ ports_.push_back(port);
+ allocate_relay_ports = true;
+ }
+ }
+
+ // If any new ip/proxy combinations were added, send a relay allocate.
+ if (allocate_relay_ports) {
+ AllocateRelayPorts();
+ }
+
+ // Initiate proxy detection.
+ InitiateProxyDetection();
+}
+
+void ConnectivityChecker::InitiateProxyDetection() {
+ // Only start if we haven't been started before.
+ if (!proxy_detect_) {
+ proxy_detect_ = new talk_base::AutoDetectProxy(user_agent_);
+ talk_base::Url<char> host_url("/", "relay.google.com",
+ talk_base::HTTP_DEFAULT_PORT);
+ host_url.set_secure(true);
+ proxy_detect_->set_server_url(host_url.url());
+ proxy_detect_->SignalWorkDone.connect(
+ this, &ConnectivityChecker::OnProxyDetect);
+ proxy_detect_->Start();
+ }
+}
+
+void ConnectivityChecker::AllocateRelayPorts() {
+ // Currently we are using the 'default' nic for http(s) requests.
+ TestHttpPortAllocatorSession* allocator_session =
+ reinterpret_cast<TestHttpPortAllocatorSession*>(
+ port_allocator_->CreateSession(kSessionNameRtp, kSessionTypeVideo));
+ allocator_session->set_proxy(port_allocator_->proxy());
+ allocator_session->SignalConfigReady.connect(
+ this, &ConnectivityChecker::OnConfigReady);
+ allocator_session->SignalRequestDone.connect(
+ this, &ConnectivityChecker::OnRequestDone);
+
+ // Try both http and https.
+ RegisterHttpStart(talk_base::HTTP_SECURE_PORT);
+ allocator_session->SendSessionRequest("relay.l.google.com",
+ talk_base::HTTP_SECURE_PORT);
+ RegisterHttpStart(talk_base::HTTP_DEFAULT_PORT);
+ allocator_session->SendSessionRequest("relay.l.google.com",
+ talk_base::HTTP_DEFAULT_PORT);
+
+ sessions_.push_back(allocator_session);
+}
+
+void ConnectivityChecker::RegisterHttpStart(int port) {
+ // Since we don't know what nic were actually used for the http request,
+ // for now, just use the first one.
+ std::vector<talk_base::Network*> networks;
+ network_manager_->GetNetworks(&networks);
+ if (networks.empty()) {
+ LOG(LS_ERROR) << "No networks while registering http start.";
+ return;
+ }
+ talk_base::ProxyInfo proxy_info = GetProxyInfo();
+ NicMap::iterator i = nics_.find(NicId(networks[0]->ip(), proxy_info.address));
+ if (i != nics_.end()) {
+ uint32 now = talk_base::Time();
+ NicInfo* nic_info = &i->second;
+ if (port == talk_base::HTTP_DEFAULT_PORT) {
+ nic_info->http.start_time_ms = now;
+ } else if (port == talk_base::HTTP_SECURE_PORT) {
+ nic_info->https.start_time_ms = now;
+ } else {
+ LOG(LS_ERROR) << "Registering start time for unknown port: " << port;
+ }
+ } else {
+ LOG(LS_ERROR) << "Error, no nic info found while registering http start.";
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/p2p/client/connectivitychecker.h b/talk/p2p/client/connectivitychecker.h
new file mode 100644
index 0000000..18a1c7f
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.h
@@ -0,0 +1,260 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
+#define TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/network.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+namespace talk_base {
+class AsyncHttpRequest;
+class AutoDetectProxy;
+class BasicPacketSocketFactory;
+class NetworkManager;
+class PacketSocketFactory;
+class SignalThread;
+class TestHttpPortAllocatorSession;
+class Thread;
+}
+
+namespace cricket {
+class HttpPortAllocator;
+class Port;
+class PortAllocatorSession;
+struct PortConfiguration;
+class RelayPort;
+class StunPort;
+
+// Contains details about a discovered firewall that are of interest
+// when debugging call failures.
+struct FirewallInfo {
+ std::string brand;
+ std::string model;
+
+ // TODO: List of current port mappings.
+};
+
+// Contains details about a specific connect attempt.
+struct ConnectInfo {
+ ConnectInfo()
+ : rtt(-1), error(0) {}
+ // Time when the connection was initiated. Needed for calculating
+ // the round trip time.
+ uint32 start_time_ms;
+ // Round trip time in milliseconds or -1 for failed connection.
+ int32 rtt;
+ // Error code representing low level errors like socket errors.
+ int error;
+};
+
+// Identifier for a network interface and proxy address pair.
+struct NicId {
+ NicId(const talk_base::IPAddress& ip,
+ const talk_base::SocketAddress& proxy_address)
+ : ip(ip),
+ proxy_address(proxy_address) {
+ }
+ talk_base::IPAddress ip;
+ talk_base::SocketAddress proxy_address;
+};
+
+// Comparator implementation identifying unique network interface and
+// proxy address pairs.
+class NicIdComparator {
+ public:
+ int compare(const NicId &first, const NicId &second) const {
+ if (first.ip == second.ip) {
+ // Compare proxy address.
+ if (first.proxy_address == second.proxy_address) {
+ return 0;
+ } else {
+ return first.proxy_address < second.proxy_address? -1 : 1;
+ }
+ }
+ return first.ip < second.ip ? -1 : 1;
+ }
+
+ bool operator()(const NicId &first, const NicId &second) const {
+ return (compare(first, second) < 0);
+ }
+};
+
+// Contains information of a network interface and proxy address pair.
+struct NicInfo {
+ NicInfo() {}
+ talk_base::IPAddress ip;
+ talk_base::ProxyInfo proxy_info;
+ talk_base::SocketAddress external_address;
+ talk_base::SocketAddress stun_server_address;
+ talk_base::SocketAddress media_server_address;
+ ConnectInfo stun;
+ ConnectInfo http;
+ ConnectInfo https;
+ ConnectInfo udp;
+ ConnectInfo tcp;
+ ConnectInfo ssltcp;
+ FirewallInfo firewall;
+};
+
+// Holds the result of the connectivity check.
+class NicMap : public std::map<NicId, NicInfo, NicIdComparator> {
+};
+
+class TestHttpPortAllocatorSession : public HttpPortAllocatorSession {
+ public:
+ TestHttpPortAllocatorSession(
+ 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)
+ : HttpPortAllocatorSession(allocator, name, session_type, stun_hosts,
+ relay_hosts, relay_token, user_agent) {
+ }
+ void set_proxy(const talk_base::ProxyInfo& proxy) {
+ proxy_ = proxy;
+ }
+
+ void ConfigReady(PortConfiguration* config);
+
+ void OnRequestDone(talk_base::SignalThread* data);
+
+ sigslot::signal2<const PortConfiguration*,
+ const talk_base::ProxyInfo&> SignalConfigReady;
+ sigslot::signal1<talk_base::AsyncHttpRequest*> SignalRequestDone;
+
+ private:
+ talk_base::ProxyInfo proxy_;
+};
+
+// Runs a request/response check on all network interface and proxy
+// address combinations. The check is considered done either when all
+// checks has been successful or when the check times out.
+class ConnectivityChecker
+ : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+ ConnectivityChecker(talk_base::Thread* worker,
+ const std::string& jid,
+ const std::string& session_id,
+ const std::string& user_agent,
+ const std::string& relay_token,
+ const std::string& connection);
+ virtual ~ConnectivityChecker();
+
+ // Virtual for gMock.
+ virtual bool Initialize();
+ virtual void Start();
+ virtual void Stop();
+
+ // MessageHandler implementation.
+ virtual void OnMessage(talk_base::Message *msg);
+
+ const NicMap& GetResults() const {
+ return nics_;
+ }
+
+ void set_timeout_ms(uint32 timeout) {
+ timeout_ms_ = timeout;
+ }
+
+ void set_stun_address(const talk_base::SocketAddress& stun_address) {
+ stun_address_ = stun_address;
+ }
+
+ const std::string& connection() const {
+ return connection_;
+ }
+
+ talk_base::Thread* worker() {
+ return worker_;
+ }
+
+ const std::string& jid() const {
+ return jid_;
+ }
+
+ const std::string& session_id() const {
+ return session_id_;
+ }
+
+ // Context: Main Thread. Signalled when the connectivity check is complete.
+ sigslot::signal1<ConnectivityChecker*> SignalCheckDone;
+
+ protected:
+ // Can be overridden for test.
+ virtual talk_base::NetworkManager* CreateNetworkManager() {
+ return new talk_base::BasicNetworkManager();
+ }
+ virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
+ talk_base::Thread* thread) {
+ return new talk_base::BasicPacketSocketFactory(thread);
+ }
+ virtual HttpPortAllocator* CreatePortAllocator(
+ talk_base::NetworkManager* network_manager,
+ const std::string& user_agent,
+ const std::string& relay_token);
+ virtual StunPort* CreateStunPort(const PortConfiguration* config,
+ talk_base::Network* network);
+ virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
+ talk_base::Network* network);
+ virtual void InitiateProxyDetection();
+ virtual void SetProxyInfo(const talk_base::ProxyInfo& info);
+ virtual talk_base::ProxyInfo GetProxyInfo() const;
+
+ private:
+ bool AddNic(const talk_base::IPAddress& ip,
+ const talk_base::SocketAddress& proxy_address);
+ void AllocatePorts();
+ void AllocateRelayPorts();
+ void CheckNetworks();
+ void CreateRelayPorts(const PortConfiguration* config,
+ const talk_base::ProxyInfo& proxy_info);
+
+ // Must be called by the worker thread.
+ void CleanUp();
+
+ void OnCheckDone(bool signal_results);
+ void OnRequestDone(talk_base::AsyncHttpRequest* request);
+ void OnRelayAddressReady(Port* port);
+ void OnStunAddressReady(Port* port);
+ void OnRelayAddressError(Port* port);
+ void OnStunAddressError(Port* port);
+ void OnNetworksChanged();
+ void OnProxyDetect(talk_base::SignalThread* thread);
+ void OnConfigReady(const PortConfiguration*,
+ const talk_base::ProxyInfo& proxy);
+ void OnConfigWithProxyReady(const PortConfiguration*);
+ void RegisterHttpStart(int port);
+ talk_base::Thread* worker_;
+ std::string jid_;
+ std::string session_id_;
+ std::string user_agent_;
+ std::string relay_token_;
+ std::string connection_;
+ talk_base::AutoDetectProxy* proxy_detect_;
+ talk_base::scoped_ptr<talk_base::NetworkManager> network_manager_;
+ talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> socket_factory_;
+ talk_base::scoped_ptr<HttpPortAllocator> port_allocator_;
+ NicMap nics_;
+ std::vector<Port*> ports_;
+ std::vector<PortAllocatorSession*> sessions_;
+ uint32 timeout_ms_;
+ talk_base::SocketAddress stun_address_;
+ talk_base::Thread* main_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
diff --git a/talk/p2p/client/connectivitychecker_unittest.cc b/talk/p2p/client/connectivitychecker_unittest.cc
new file mode 100644
index 0000000..d63704a
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker_unittest.cc
@@ -0,0 +1,367 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+
+#include <string>
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/gunit.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/client/connectivitychecker.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+namespace cricket {
+
+static const talk_base::SocketAddress kClientAddr1("11.11.11.11", 0);
+static const talk_base::SocketAddress kClientAddr2("22.22.22.22", 0);
+static const talk_base::SocketAddress kExternalAddr("33.33.33.33", 3333);
+static const talk_base::SocketAddress kStunAddr("44.44.44.44", 4444);
+static const talk_base::SocketAddress kRelayAddr("55.55.55.55", 5555);
+static const talk_base::SocketAddress kProxyAddr("66.66.66.66", 6666);
+static const talk_base::ProxyType kProxyType = talk_base::PROXY_HTTPS;
+static const char kSessionName[] = "rtp_test";
+static const char kSessionType[] = "http://www.google.com/session/test";
+static const char kRelayHost[] = "relay.google.com";
+static const char kRelayToken[] =
+ "CAESFwoOb2phQGdvb2dsZS5jb20Q043h47MmGhBTB1rbfIXkhuarDCZe+xF6";
+static const char kBrowserAgent[] = "browser_test";
+static const char kJid[] = "a.b@c";
+static const char kUserName[] = "testuser";
+static const char kPassword[] = "testpassword";
+static const char kMagicCookie[] = "testcookie";
+static const char kRelayUdpPort[] = "4444";
+static const char kRelayTcpPort[] = "5555";
+static const char kRelaySsltcpPort[] = "6666";
+static const char kSessionId[] = "testsession";
+static const char kConnection[] = "testconnection";
+static const int kMinPort = 1000;
+static const int kMaxPort = 2000;
+
+// Fake implementation to mock away real network usage.
+class FakeRelayPort : public RelayPort {
+ public:
+ FakeRelayPort(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ const talk_base::IPAddress& ip,
+ int min_port,
+ int max_port,
+ const std::string& username,
+ const std::string& password,
+ const std::string& magic_cookie)
+ : RelayPort(thread,
+ factory,
+ network,
+ ip,
+ min_port,
+ max_port,
+ username,
+ password,
+ magic_cookie) {
+ }
+
+ // Just signal that we are done.
+ virtual void PrepareAddress() {
+ SignalAddressReady(this);
+ }
+};
+
+// Fake implementation to mock away real network usage.
+class FakeStunPort : public StunPort {
+ public:
+ FakeStunPort(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ const talk_base::IPAddress& ip,
+ int min_port,
+ int max_port,
+ const talk_base::SocketAddress& server_addr)
+ : StunPort(thread,
+ factory,
+ network,
+ ip,
+ min_port,
+ max_port,
+ server_addr) {
+ }
+
+ // Just set external address and signal that we are done.
+ virtual void PrepareAddress() {
+ AddAddress(kExternalAddr, "udp", true);
+ SignalAddressReady(this);
+ }
+};
+
+// Fake implementation to mock away real network usage by responding
+// to http requests immediately.
+class FakeHttpPortAllocatorSession : public TestHttpPortAllocatorSession {
+ public:
+ FakeHttpPortAllocatorSession(
+ 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& agent)
+ : TestHttpPortAllocatorSession(allocator,
+ name,
+ session_type,
+ stun_hosts,
+ relay_hosts,
+ relay_token,
+ agent) {
+ }
+ virtual void SendSessionRequest(const std::string& host, int port) {
+ FakeReceiveSessionResponse(host, port);
+ }
+
+ // Pass results to the real implementation.
+ void FakeReceiveSessionResponse(const std::string& host, int port) {
+ talk_base::AsyncHttpRequest* response = CreateAsyncHttpResponse(port);
+ TestHttpPortAllocatorSession::OnRequestDone(response);
+ response->Destroy(true);
+ }
+
+ private:
+ // Helper method for creating a response to a relay session request.
+ talk_base::AsyncHttpRequest* CreateAsyncHttpResponse(int port) {
+ talk_base::AsyncHttpRequest* request =
+ new talk_base::AsyncHttpRequest(kBrowserAgent);
+ std::stringstream ss;
+ ss << "username=" << kUserName << std::endl
+ << "password=" << kPassword << std::endl
+ << "magic_cookie=" << kMagicCookie << std::endl
+ << "relay.ip=" << kRelayAddr.IPAsString() << std::endl
+ << "relay.udp_port=" << kRelayUdpPort << std::endl
+ << "relay.tcp_port=" << kRelayTcpPort << std::endl
+ << "relay.ssltcp_port=" << kRelaySsltcpPort << std::endl;
+ request->response().document.reset(
+ new talk_base::MemoryStream(ss.str().c_str()));
+ request->response().set_success();
+ request->set_port(port);
+ request->set_secure(port == talk_base::HTTP_SECURE_PORT);
+ return request;
+ }
+};
+
+// Fake implementation for creating fake http sessions.
+class FakeHttpPortAllocator : public HttpPortAllocator {
+ public:
+ FakeHttpPortAllocator(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent)
+ : HttpPortAllocator(network_manager, user_agent) {
+ }
+
+ virtual PortAllocatorSession* CreateSession(const std::string& name,
+ const std::string& session_type) {
+ std::vector<talk_base::SocketAddress> stun_hosts;
+ stun_hosts.push_back(kStunAddr);
+ std::vector<std::string> relay_hosts;
+ relay_hosts.push_back(kRelayHost);
+ return new FakeHttpPortAllocatorSession(this,
+ kSessionName,
+ kSessionType,
+ stun_hosts,
+ relay_hosts,
+ kRelayToken,
+ kBrowserAgent);
+ }
+};
+
+class ConnectivityCheckerForTest : public ConnectivityChecker {
+ public:
+ ConnectivityCheckerForTest(talk_base::Thread* worker,
+ const std::string& jid,
+ const std::string& session_id,
+ const std::string& user_agent,
+ const std::string& relay_token,
+ const std::string& connection)
+ : ConnectivityChecker(worker,
+ jid,
+ session_id,
+ user_agent,
+ relay_token,
+ connection),
+ proxy_initiated_(false) {
+ }
+
+ talk_base::FakeNetworkManager* network_manager() const {
+ return network_manager_;
+ }
+
+ FakeHttpPortAllocator* port_allocator() const {
+ return fake_port_allocator_;
+ }
+
+ protected:
+ // Overridden methods for faking a real network.
+ virtual talk_base::NetworkManager* CreateNetworkManager() {
+ network_manager_ = new talk_base::FakeNetworkManager();
+ return network_manager_;
+ }
+ virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
+ talk_base::Thread* thread) {
+ // Create socket factory, for simplicity, let it run on the current thread.
+ socket_factory_ =
+ new talk_base::BasicPacketSocketFactory(talk_base::Thread::Current());
+ return socket_factory_;
+ }
+ virtual HttpPortAllocator* CreatePortAllocator(
+ talk_base::NetworkManager* network_manager,
+ const std::string& user_agent,
+ const std::string& relay_token) {
+ fake_port_allocator_ =
+ new FakeHttpPortAllocator(network_manager, user_agent);
+ return fake_port_allocator_;
+ }
+ virtual StunPort* CreateStunPort(const PortConfiguration* config,
+ talk_base::Network* network) {
+ return new FakeStunPort(worker(),
+ socket_factory_,
+ network,
+ network->ip(),
+ kMinPort,
+ kMaxPort,
+ config->stun_address);
+ }
+ virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
+ talk_base::Network* network) {
+ return new FakeRelayPort(worker(),
+ socket_factory_,
+ network,
+ network->ip(),
+ kMinPort,
+ kMaxPort,
+ config->username,
+ config->password,
+ config->magic_cookie);
+ }
+ virtual void InitiateProxyDetection() {
+ if (!proxy_initiated_) {
+ proxy_initiated_ = true;
+ proxy_info_.address = kProxyAddr;
+ proxy_info_.type = kProxyType;
+ SetProxyInfo(proxy_info_);
+ }
+ }
+
+ virtual talk_base::ProxyInfo GetProxyInfo() const {
+ return proxy_info_;
+ }
+
+ private:
+ talk_base::BasicPacketSocketFactory* socket_factory_;
+ FakeHttpPortAllocator* fake_port_allocator_;
+ talk_base::FakeNetworkManager* network_manager_;
+ talk_base::ProxyInfo proxy_info_;
+ bool proxy_initiated_;
+};
+
+class ConnectivityCheckerTest : public testing::Test {
+ protected:
+ void VerifyNic(const NicInfo& info,
+ const talk_base::SocketAddress& local_address) {
+ // Verify that the external address has been set.
+ EXPECT_EQ(kExternalAddr, info.external_address);
+
+ // Verify that the stun server address has been set.
+ EXPECT_EQ(kStunAddr, info.stun_server_address);
+
+ // Verify that the media server address has been set. Don't care
+ // about port since it is different for different protocols.
+ EXPECT_EQ(kRelayAddr.ipaddr(), info.media_server_address.ipaddr());
+
+ // Verify that local ip matches.
+ EXPECT_EQ(local_address.ipaddr(), info.ip);
+
+ // Verify that we have received responses for our
+ // pings. Unsuccessful ping has rtt value -1, successful >= 0.
+ EXPECT_GE(info.stun.rtt, 0);
+ EXPECT_GE(info.udp.rtt, 0);
+ EXPECT_GE(info.tcp.rtt, 0);
+ EXPECT_GE(info.ssltcp.rtt, 0);
+
+ // If proxy has been set, verify address and type.
+ if (!info.proxy_info.address.IsNil()) {
+ EXPECT_EQ(kProxyAddr, info.proxy_info.address);
+ EXPECT_EQ(kProxyType, info.proxy_info.type);
+ }
+ }
+};
+
+// Tests a configuration with two network interfaces. Verifies that 4
+// combinations of ip/proxy are created and that all protocols are
+// tested on each combination.
+TEST_F(ConnectivityCheckerTest, TestStart) {
+ ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
+ kJid,
+ kSessionId,
+ kBrowserAgent,
+ kRelayToken,
+ kConnection);
+ connectivity_checker.Initialize();
+ connectivity_checker.set_stun_address(kStunAddr);
+ connectivity_checker.network_manager()->AddInterface(kClientAddr1);
+ connectivity_checker.network_manager()->AddInterface(kClientAddr2);
+
+ connectivity_checker.Start();
+ talk_base::Thread::Current()->ProcessMessages(1000);
+
+ NicMap nics = connectivity_checker.GetResults();
+
+ // There should be 4 nics in our map. 2 for each interface added,
+ // one with proxy set and one without.
+ EXPECT_EQ(4U, nics.size());
+
+ // First verify interfaces without proxy.
+ talk_base::SocketAddress nilAddress;
+
+ // First lookup the address of the first nic combined with no proxy.
+ NicMap::iterator i = nics.find(NicId(kClientAddr1.ipaddr(), nilAddress));
+ ASSERT(i != nics.end());
+ NicInfo info = i->second;
+ VerifyNic(info, kClientAddr1);
+
+ // Then make sure the second device has been tested without proxy.
+ i = nics.find(NicId(kClientAddr2.ipaddr(), nilAddress));
+ ASSERT(i != nics.end());
+ info = i->second;
+ VerifyNic(info, kClientAddr2);
+
+ // Now verify both interfaces with proxy.
+ i = nics.find(NicId(kClientAddr1.ipaddr(), kProxyAddr));
+ ASSERT(i != nics.end());
+ info = i->second;
+ VerifyNic(info, kClientAddr1);
+
+ i = nics.find(NicId(kClientAddr2.ipaddr(), kProxyAddr));
+ ASSERT(i != nics.end());
+ info = i->second;
+ VerifyNic(info, kClientAddr2);
+};
+
+// Tests that nothing bad happens if thera are no network interfaces
+// available to check.
+TEST_F(ConnectivityCheckerTest, TestStartNoNetwork) {
+ ConnectivityCheckerForTest connectivity_checker(talk_base::Thread::Current(),
+ kJid,
+ kSessionId,
+ kBrowserAgent,
+ kRelayToken,
+ kConnection);
+ connectivity_checker.Initialize();
+ connectivity_checker.Start();
+ talk_base::Thread::Current()->ProcessMessages(1000);
+
+ NicMap nics = connectivity_checker.GetResults();
+
+ // Verify that no nics where checked.
+ EXPECT_EQ(0U, nics.size());
+}
+
+} // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc
index 1a25287..e231fab 100644
--- a/talk/p2p/client/httpportallocator.cc
+++ b/talk/p2p/client/httpportallocator.cc
@@ -27,6 +27,7 @@
#include "talk/p2p/client/httpportallocator.h"
+#include <algorithm>
#include <map>
#include "talk/base/asynchttprequest.h"
@@ -89,14 +90,13 @@
namespace cricket {
-// HttpPortAllocator
+// HttpPortAllocatorBase
-const int HttpPortAllocator::kHostPort = 80;
-const int HttpPortAllocator::kNumRetries = 5;
+const int HttpPortAllocatorBase::kNumRetries = 5;
-const char HttpPortAllocator::kCreateSessionURL[] = "/create_session";
+const char HttpPortAllocatorBase::kCreateSessionURL[] = "/create_session";
-HttpPortAllocator::HttpPortAllocator(
+HttpPortAllocatorBase::HttpPortAllocatorBase(
talk_base::NetworkManager* network_manager,
talk_base::PacketSocketFactory* socket_factory,
const std::string &user_agent)
@@ -106,7 +106,7 @@
talk_base::SocketAddress("stun.l.google.com", 19302));
}
-HttpPortAllocator::HttpPortAllocator(
+HttpPortAllocatorBase::HttpPortAllocatorBase(
talk_base::NetworkManager* network_manager,
const std::string &user_agent)
: BasicPortAllocator(network_manager), agent_(user_agent) {
@@ -115,19 +115,13 @@
talk_base::SocketAddress("stun.l.google.com", 19302));
}
-HttpPortAllocator::~HttpPortAllocator() {
+HttpPortAllocatorBase::~HttpPortAllocatorBase() {
}
-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_);
-}
+// HttpPortAllocatorSessionBase
-// HttpPortAllocatorSession
-
-HttpPortAllocatorSession::HttpPortAllocatorSession(
- HttpPortAllocator* allocator, const std::string &name,
+HttpPortAllocatorSessionBase::HttpPortAllocatorSessionBase(
+ HttpPortAllocatorBase* 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,
@@ -138,7 +132,9 @@
relay_token_(relay_token), agent_(user_agent), attempts_(0) {
}
-void HttpPortAllocatorSession::GetPortConfigurations() {
+HttpPortAllocatorSessionBase::~HttpPortAllocatorSessionBase() {}
+
+void HttpPortAllocatorSessionBase::GetPortConfigurations() {
// Creating relay sessions can take time and is done asynchronously.
// Creating stun sessions could also take time and could be done aysnc also,
// but for now is done here and added to the initial config. Note any later
@@ -149,7 +145,7 @@
TryCreateRelaySession();
}
-void HttpPortAllocatorSession::TryCreateRelaySession() {
+void HttpPortAllocatorSessionBase::TryCreateRelaySession() {
if (allocator()->flags() & PORTALLOCATOR_DISABLE_RELAY) {
LOG(LS_VERBOSE) << "HttpPortAllocator: Relay ports disabled, skipping.";
return;
@@ -174,52 +170,10 @@
LOG(LS_WARNING) << "No relay auth token found.";
}
- SendSessionRequest(host, HttpPortAllocator::kHostPort);
+ SendSessionRequest(host, talk_base::HTTP_SECURE_PORT);
}
-void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
- int port) {
- // Initiate an HTTP request to create a session through the chosen host.
- talk_base::AsyncHttpRequest* request =
- new talk_base::AsyncHttpRequest(agent_);
- request->SignalWorkDone.connect(this,
- &HttpPortAllocatorSession::OnRequestDone);
-
- request->set_proxy(allocator()->proxy());
- request->response().document.reset(new talk_base::MemoryStream);
- request->request().verb = talk_base::HV_GET;
- request->request().path = HttpPortAllocator::kCreateSessionURL;
- request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token_, true);
- request->request().addHeader("X-Google-Relay-Auth", relay_token_, true);
- request->request().addHeader("X-Session-Type", session_type(), true);
- request->request().addHeader("X-Stream-Type", name(), true);
- request->set_host(host);
- request->set_port(port);
- request->Start();
- request->Release();
-}
-
-void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
- talk_base::AsyncHttpRequest* request =
- static_cast<talk_base::AsyncHttpRequest*>(data);
- if (request->response().scode != 200) {
- LOG(LS_WARNING) << "HTTPPortAllocator: request "
- << " received error " << request->response().scode;
- TryCreateRelaySession();
- return;
- }
- LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
-
- talk_base::MemoryStream* stream =
- static_cast<talk_base::MemoryStream*>(request->response().document.get());
- stream->Rewind();
- size_t length;
- stream->GetSize(&length);
- std::string resp = std::string(stream->GetBuffer(), length);
- ReceiveSessionResponse(resp);
-}
-
-void HttpPortAllocatorSession::ReceiveSessionResponse(
+void HttpPortAllocatorSessionBase::ReceiveSessionResponse(
const std::string& response) {
StringMap map;
@@ -256,4 +210,99 @@
ConfigReady(config);
}
+// HttpPortAllocator
+
+HttpPortAllocator::HttpPortAllocator(
+ talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const std::string &user_agent)
+ : HttpPortAllocatorBase(network_manager, socket_factory, user_agent) {
+}
+
+HttpPortAllocator::HttpPortAllocator(
+ talk_base::NetworkManager* network_manager,
+ const std::string &user_agent)
+ : HttpPortAllocatorBase(network_manager, user_agent) {
+}
+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(), user_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,
+ const std::string& agent)
+ : HttpPortAllocatorSessionBase(allocator, name, session_type, stun_hosts,
+ relay_hosts, relay, agent) {
+}
+
+HttpPortAllocatorSession::~HttpPortAllocatorSession() {
+ for (std::list<talk_base::AsyncHttpRequest*>::iterator it = requests_.begin();
+ it != requests_.end(); ++it) {
+ (*it)->Destroy(true);
+ }
+}
+
+void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
+ int port) {
+ // Initiate an HTTP request to create a session through the chosen host.
+ talk_base::AsyncHttpRequest* request =
+ new talk_base::AsyncHttpRequest(user_agent());
+ request->SignalWorkDone.connect(this,
+ &HttpPortAllocatorSession::OnRequestDone);
+
+ request->set_secure(port == talk_base::HTTP_SECURE_PORT);
+ request->set_proxy(allocator()->proxy());
+ request->response().document.reset(new talk_base::MemoryStream);
+ request->request().verb = talk_base::HV_GET;
+ request->request().path = HttpPortAllocator::kCreateSessionURL;
+ request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token(), true);
+ request->request().addHeader("X-Google-Relay-Auth", relay_token(), true);
+ request->request().addHeader("X-Session-Type", session_type(), true);
+ request->request().addHeader("X-Stream-Type", name(), true);
+ request->set_host(host);
+ request->set_port(port);
+ request->Start();
+ request->Release();
+ requests_.push_back(request);
+}
+
+void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
+ talk_base::AsyncHttpRequest* request =
+ static_cast<talk_base::AsyncHttpRequest*>(data);
+
+ // Remove the request from the list of active requests.
+ std::list<talk_base::AsyncHttpRequest*>::iterator it =
+ std::find(requests_.begin(), requests_.end(), request);
+ if (it != requests_.end()) {
+ requests_.erase(it);
+ }
+
+ if (request->response().scode != 200) {
+ LOG(LS_WARNING) << "HTTPPortAllocator: request "
+ << " received error " << request->response().scode;
+ TryCreateRelaySession();
+ return;
+ }
+ LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
+
+ talk_base::MemoryStream* stream =
+ static_cast<talk_base::MemoryStream*>(request->response().document.get());
+ stream->Rewind();
+ size_t length;
+ stream->GetSize(&length);
+ std::string resp = std::string(stream->GetBuffer(), length);
+ ReceiveSessionResponse(resp);
+}
+
} // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.h b/talk/p2p/client/httpportallocator.h
index e7211c5..e47a69e 100644
--- a/talk/p2p/client/httpportallocator.h
+++ b/talk/p2p/client/httpportallocator.h
@@ -28,36 +28,39 @@
#ifndef TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
#define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+#include <list>
#include <string>
#include <vector>
#include "talk/p2p/client/basicportallocator.h"
namespace talk_base {
+class AsyncHttpRequest;
class SignalThread;
}
namespace cricket {
-class HttpPortAllocator : public BasicPortAllocator {
+class HttpPortAllocatorBase : public BasicPortAllocator {
public:
- // Records the port on the hosts that will receive HTTP requests.
- static const int kHostPort;
-
// The number of HTTP requests we should attempt before giving up.
static const int kNumRetries;
// Records the URL that we will GET in order to create a session.
static const char kCreateSessionURL[];
- HttpPortAllocator(talk_base::NetworkManager* network_manager,
- const std::string& user_agent);
- HttpPortAllocator(talk_base::NetworkManager* network_manager,
- talk_base::PacketSocketFactory* socket_factory,
- const std::string& user_agent);
- virtual ~HttpPortAllocator();
+ HttpPortAllocatorBase(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent);
+ HttpPortAllocatorBase(talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const std::string& user_agent);
+ virtual ~HttpPortAllocatorBase();
- virtual PortAllocatorSession* CreateSession(const std::string& name,
- const std::string& session_type);
+ // CreateSession is defined in BasicPortAllocator but is
+ // redefined here as pure virtual.
+ virtual PortAllocatorSession* CreateSession(
+ const std::string& name,
+ const std::string& session_type) = 0;
+
void SetStunHosts(const std::vector<talk_base::SocketAddress>& hosts) {
if (!hosts.empty()) {
stun_hosts_ = hosts;
@@ -95,7 +98,58 @@
class RequestData;
-class HttpPortAllocatorSession : public BasicPortAllocatorSession {
+class HttpPortAllocatorSessionBase : public BasicPortAllocatorSession {
+ public:
+ HttpPortAllocatorSessionBase(
+ HttpPortAllocatorBase* allocator,
+ const std::string& name,
+ const std::string& session_type,
+ const std::vector<talk_base::SocketAddress>& stun_hosts,
+ const std::vector<std::string>& relay_hosts,
+ const std::string& relay,
+ const std::string& agent);
+ virtual ~HttpPortAllocatorSessionBase();
+
+ const std::string& relay_token() const {
+ return relay_token_;
+ }
+
+ const std::string& user_agent() const {
+ return agent_;
+ }
+
+ virtual void SendSessionRequest(const std::string& host, int port) = 0;
+ virtual void ReceiveSessionResponse(const std::string& response);
+
+ protected:
+ virtual void GetPortConfigurations();
+ void TryCreateRelaySession();
+ virtual HttpPortAllocatorBase* allocator() {
+ return static_cast<HttpPortAllocatorBase*>(
+ BasicPortAllocatorSession::allocator());
+ }
+
+ private:
+ std::vector<std::string> relay_hosts_;
+ std::vector<talk_base::SocketAddress> stun_hosts_;
+ std::string relay_token_;
+ std::string agent_;
+ int attempts_;
+};
+
+class HttpPortAllocator : public HttpPortAllocatorBase {
+ public:
+ HttpPortAllocator(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent);
+ HttpPortAllocator(talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const std::string& user_agent);
+ virtual ~HttpPortAllocator();
+ virtual PortAllocatorSession* CreateSession(const std::string& name,
+ const std::string& session_type);
+};
+
+class HttpPortAllocatorSession : public HttpPortAllocatorSessionBase {
public:
HttpPortAllocatorSession(
HttpPortAllocator* allocator,
@@ -105,31 +159,16 @@
const std::vector<std::string>& relay_hosts,
const std::string& relay,
const std::string& agent);
- virtual ~HttpPortAllocatorSession() {}
+ virtual ~HttpPortAllocatorSession();
- const std::string& relay_token() const {
- return relay_token_;
- }
virtual void SendSessionRequest(const std::string& host, int port);
- virtual void ReceiveSessionResponse(const std::string& response);
protected:
- virtual void GetPortConfigurations();
- void TryCreateRelaySession();
+ // Protected for diagnostics.
+ virtual void OnRequestDone(talk_base::SignalThread* request);
private:
- virtual HttpPortAllocator* allocator() {
- return static_cast<HttpPortAllocator*>(
- BasicPortAllocatorSession::allocator());
- }
-
- void OnRequestDone(talk_base::SignalThread* request);
-
- std::vector<std::string> relay_hosts_;
- std::vector<talk_base::SocketAddress> stun_hosts_;
- std::string relay_token_;
- std::string agent_;
- int attempts_;
+ std::list<talk_base::AsyncHttpRequest*> requests_;
};
} // namespace cricket
diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc
new file mode 100644
index 0000000..f5bb37c
--- /dev/null
+++ b/talk/p2p/client/portallocator_unittest.cc
@@ -0,0 +1,396 @@
+/*
+ * libjingle
+ * Copyright 2009 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/fakenetwork.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+#include "talk/p2p/base/testrelayserver.h"
+#include "talk/p2p/base/teststunserver.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+using talk_base::SocketAddress;
+using talk_base::Thread;
+
+static const SocketAddress kClientAddr("11.11.11.11", 0);
+static const SocketAddress kRemoteClientAddr("22.22.22.22", 0);
+static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
+static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
+static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
+static const SocketAddress kRelayTcpIntAddr("99.99.99.2", 5002);
+static const SocketAddress kRelayTcpExtAddr("99.99.99.3", 5003);
+static const SocketAddress kRelaySslTcpIntAddr("99.99.99.2", 5004);
+static const SocketAddress kRelaySslTcpExtAddr("99.99.99.3", 5005);
+
+// Minimum and maximum port for port range tests.
+static const int kMinPort = 10000;
+static const int kMaxPort = 10099;
+
+// Helper for dumping candidates
+std::ostream& operator<<(std::ostream& os, const cricket::Candidate& c) {
+ os << c.ToString();
+ return os;
+}
+
+class PortAllocatorTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ static void SetUpTestCase() {
+ // Ensure the RNG is inited.
+ talk_base::InitRandom(NULL, 0);
+ }
+ PortAllocatorTest()
+ : pss_(new talk_base::PhysicalSocketServer),
+ vss_(new talk_base::VirtualSocketServer(pss_.get())),
+ fss_(new talk_base::FirewallSocketServer(vss_.get())),
+ ss_scope_(fss_.get()),
+ stun_server_(Thread::Current(), kStunAddr),
+ relay_server_(Thread::Current(), kRelayUdpIntAddr, kRelayUdpExtAddr,
+ kRelayTcpIntAddr, kRelayTcpExtAddr,
+ kRelaySslTcpIntAddr, kRelaySslTcpExtAddr),
+ allocator_(&network_manager_, kStunAddr,
+ kRelayUdpIntAddr, kRelayTcpIntAddr, kRelaySslTcpIntAddr) {
+ }
+
+ void AddInterface(const SocketAddress& addr) {
+ network_manager_.AddInterface(addr);
+ }
+ bool SetPortRange(int min_port, int max_port) {
+ return allocator_.SetPortRange(min_port, max_port);
+ }
+ bool CreateSession(const std::string& name, const std::string& type) {
+ session_.reset(allocator_.CreateSession(name, type));
+ if (!session_.get())
+ return false;
+
+ session_->SignalPortReady.connect(this,
+ &PortAllocatorTest::OnPortReady);
+ session_->SignalCandidatesReady.connect(this,
+ &PortAllocatorTest::OnCandidatesReady);
+ return true;
+ }
+
+ cricket::PortAllocatorSession* CreateSession(
+ const std::string& sid, const std::string& name,
+ const std::string& type, cricket::PortAllocator* allocator) {
+ cricket::PortAllocatorSession* session =
+ allocator->CreateSession(sid, name, type);
+ session->SignalPortReady.connect(this,
+ &PortAllocatorTest::OnPortReady);
+ session->SignalCandidatesReady.connect(this,
+ &PortAllocatorTest::OnCandidatesReady);
+ return session;
+ }
+
+ static bool CheckCandidate(const cricket::Candidate& c,
+ const std::string& name, const std::string& type,
+ const std::string& proto,
+ const SocketAddress& addr) {
+ return (c.name() == name && c.type() == type &&
+ c.protocol() == proto && c.address().ipaddr() == addr.ipaddr() &&
+ (addr.port() == 0 || (c.address().port() == addr.port())));
+ }
+ static bool CheckPort(const talk_base::SocketAddress& addr,
+ int min_port, int max_port) {
+ return (addr.port() >= min_port && addr.port() <= max_port);
+ }
+
+ cricket::PortAllocator* CreateAllocator() {
+ return new cricket::BasicPortAllocator(
+ &network_manager_, kStunAddr, SocketAddress(),
+ SocketAddress(), SocketAddress());
+ }
+
+ cricket::P2PTransportChannel* CreateTransportChannel(
+ const std::string& name, cricket::PortAllocator* allocator) {
+ return new cricket::P2PTransportChannel(name, "unittest", NULL, allocator);
+ }
+
+ protected:
+ cricket::BasicPortAllocator& allocator() { return allocator_; }
+
+ void OnPortReady(cricket::PortAllocatorSession* ses, cricket::Port* port) {
+ LOG(LS_INFO) << "OnPortReady: " << port->ToString();
+ ports_.push_back(port);
+ }
+ void OnCandidatesReady(cricket::PortAllocatorSession* ses,
+ const std::vector<cricket::Candidate>& candidates) {
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ LOG(LS_INFO) << "OnCandidatesReady: " << candidates[i].ToString();
+ candidates_.push_back(candidates[i]);
+ }
+ }
+
+ talk_base::scoped_ptr<talk_base::PhysicalSocketServer> pss_;
+ talk_base::scoped_ptr<talk_base::VirtualSocketServer> vss_;
+ talk_base::scoped_ptr<talk_base::FirewallSocketServer> fss_;
+ talk_base::SocketServerScope ss_scope_;
+ cricket::TestStunServer stun_server_;
+ cricket::TestRelayServer relay_server_;
+ talk_base::FakeNetworkManager network_manager_;
+ cricket::BasicPortAllocator allocator_;
+ talk_base::scoped_ptr<cricket::PortAllocatorSession> session_;
+ std::vector<cricket::Port*> ports_;
+ std::vector<cricket::Candidate> candidates_;
+};
+
+// Tests that we can init the port allocator and create a session.
+TEST_F(PortAllocatorTest, TestBasic) {
+ EXPECT_EQ(&network_manager_, allocator().network_manager());
+ EXPECT_EQ(kStunAddr, allocator().stun_address());
+ EXPECT_EQ(kRelayUdpIntAddr, allocator().relay_address_udp());
+ EXPECT_EQ(kRelayTcpIntAddr, allocator().relay_address_tcp());
+ EXPECT_EQ(kRelaySslTcpIntAddr, allocator().relay_address_ssl());
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+}
+
+// Tests that we can get the local and STUN addresses successfully.
+TEST_F(PortAllocatorTest, TestGetInitialPorts) {
+ AddInterface(kClientAddr);
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+ EXPECT_PRED5(CheckCandidate, candidates_[0],
+ "rtp", "local", "udp", kClientAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[1],
+ "rtp", "stun", "udp", kClientAddr);
+ EXPECT_EQ(2U, ports_.size());
+}
+
+// Tests that we can get all the desired addresses successfully.
+TEST_F(PortAllocatorTest, TestGetAllPorts) {
+ AddInterface(kClientAddr);
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+ EXPECT_EQ(2U, ports_.size());
+ ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+ EXPECT_EQ(3U, ports_.size());
+ EXPECT_PRED5(CheckCandidate, candidates_[2],
+ "rtp", "relay", "udp", kRelayUdpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[3],
+ "rtp", "relay", "udp", kRelayUdpExtAddr);
+ ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
+ EXPECT_PRED5(CheckCandidate, candidates_[4],
+ "rtp", "relay", "tcp", kRelayTcpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[5],
+ "rtp", "local", "tcp", kClientAddr);
+ EXPECT_EQ(4U, ports_.size());
+ ASSERT_EQ_WAIT(7U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[6],
+ "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+ EXPECT_EQ(4U, ports_.size());
+}
+
+// Test that we restrict client ports appropriately when a port range is set.
+// We check the candidates for udp/stun/tcp ports, and the from address
+// for relay ports.
+TEST_F(PortAllocatorTest, TestGetAllPortsPortRange) {
+ AddInterface(kClientAddr);
+ // Check that an invalid port range fails.
+ EXPECT_FALSE(SetPortRange(kMaxPort, kMinPort));
+ // Check that a null port range succeeds.
+ EXPECT_TRUE(SetPortRange(0, 0));
+ // Check that a valid port range succeeds.
+ EXPECT_TRUE(SetPortRange(kMinPort, kMaxPort));
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ ASSERT_EQ_WAIT(2U, candidates_.size(), 1000);
+ EXPECT_EQ(2U, ports_.size());
+ // Check the port number for the UDP port object.
+ EXPECT_PRED3(CheckPort, candidates_[0].address(), kMinPort, kMaxPort);
+ // Check the port number for the STUN port object.
+ EXPECT_PRED3(CheckPort, candidates_[1].address(), kMinPort, kMaxPort);
+ ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+ EXPECT_EQ(3U, ports_.size());
+ // Check the port number used to connect to the relay server.
+ EXPECT_PRED3(CheckPort, relay_server_.GetConnection(0).source(),
+ kMinPort, kMaxPort);
+ ASSERT_EQ_WAIT(6U, candidates_.size(), 1500);
+ EXPECT_EQ(4U, ports_.size());
+ // Check the port number for the TCP port object.
+ EXPECT_PRED3(CheckPort, candidates_[5].address(), kMinPort, kMaxPort);
+}
+
+// Test that we don't crash or malfunction if we have no network adapters.
+// TODO: Find a way to exit early here.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoAdapters) {
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ WAIT(candidates_.size() > 0, 2000);
+}
+
+// Test that we don't crash or malfunction if we can't create UDP sockets.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSockets) {
+ AddInterface(kClientAddr);
+ fss_->set_udp_sockets_enabled(false);
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ ASSERT_EQ_WAIT(2U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[0],
+ "rtp", "relay", "udp", kRelayUdpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[1],
+ "rtp", "relay", "udp", kRelayUdpExtAddr);
+ ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[2],
+ "rtp", "relay", "tcp", kRelayTcpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[3],
+ "rtp", "local", "tcp", kClientAddr);
+ EXPECT_EQ(2U, ports_.size());
+ ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[4],
+ "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+ EXPECT_EQ(2U, ports_.size());
+}
+
+// Test that we don't crash or malfunction if we can't create UDP sockets or
+// listen on TCP sockets. We still give out a local TCP address, since
+// apparently this is needed for the remote side to accept our connection.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoUdpSocketsNoTcpListen) {
+ AddInterface(kClientAddr);
+ fss_->set_udp_sockets_enabled(false);
+ fss_->set_tcp_listen_enabled(false);
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ ASSERT_EQ_WAIT(2U, candidates_.size(), 3000);
+ EXPECT_PRED5(CheckCandidate, candidates_[0],
+ "rtp", "relay", "udp", kRelayUdpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[1],
+ "rtp", "relay", "udp", kRelayUdpExtAddr);
+ ASSERT_EQ_WAIT(4U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[2],
+ "rtp", "relay", "tcp", kRelayTcpIntAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[3],
+ "rtp", "local", "tcp", kClientAddr);
+ EXPECT_EQ(2U, ports_.size());
+ ASSERT_EQ_WAIT(5U, candidates_.size(), 2000);
+ EXPECT_PRED5(CheckCandidate, candidates_[4],
+ "rtp", "relay", "ssltcp", kRelaySslTcpIntAddr);
+ EXPECT_EQ(2U, ports_.size());
+}
+
+// Test that we don't crash or malfunction if we can't create any sockets.
+// TODO: Find a way to exit early here.
+TEST_F(PortAllocatorTest, TestGetAllPortsNoSockets) {
+ AddInterface(kClientAddr);
+ fss_->set_tcp_sockets_enabled(false);
+ fss_->set_udp_sockets_enabled(false);
+ EXPECT_TRUE(CreateSession("rtp", "unittest"));
+ session_->GetInitialPorts();
+ session_->StartGetAllPorts();
+ WAIT(candidates_.size() > 0, 2000);
+}
+
+// Test that the httpportallocator correctly maintains its lists of stun and
+// relay servers, by never allowing an empty list.
+TEST(HttpPortAllocatorTest, TestHttpPortAllocatorHostLists) {
+ talk_base::FakeNetworkManager network_manager;
+ cricket::HttpPortAllocator alloc(&network_manager, "unit test agent");
+ EXPECT_EQ(1U, alloc.relay_hosts().size());
+ EXPECT_EQ(1U, alloc.stun_hosts().size());
+
+ std::vector<std::string> relay_servers;
+ std::vector<talk_base::SocketAddress> stun_servers;
+
+ alloc.SetRelayHosts(relay_servers);
+ alloc.SetStunHosts(stun_servers);
+ EXPECT_EQ(1U, alloc.relay_hosts().size());
+ EXPECT_EQ(1U, alloc.stun_hosts().size());
+
+ relay_servers.push_back("1.unittest.corp.google.com");
+ relay_servers.push_back("2.unittest.corp.google.com");
+ stun_servers.push_back(
+ talk_base::SocketAddress("1.unittest.corp.google.com", 0));
+ stun_servers.push_back(
+ talk_base::SocketAddress("2.unittest.corp.google.com", 0));
+ alloc.SetRelayHosts(relay_servers);
+ alloc.SetStunHosts(stun_servers);
+ EXPECT_EQ(2U, alloc.relay_hosts().size());
+ EXPECT_EQ(2U, alloc.stun_hosts().size());
+}
+
+TEST_F(PortAllocatorTest, TestBasicMuxFeatures) {
+ talk_base::scoped_ptr<cricket::PortAllocator> allocator(CreateAllocator());
+ allocator->set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+ // Session ID - session1.
+ talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
+ CreateSession("session1", "rtp", "audio", allocator.get()));
+ talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
+ CreateSession("session1", "rtcp", "audio", allocator.get()));
+ // We know that PortAllocator is creating a proxy session when bundle flag
+ // is enabled, it's safe to type cast session objects.
+ cricket::PortAllocatorSessionProxy* proxy1 =
+ static_cast<cricket::PortAllocatorSessionProxy*>(session1.get());
+ ASSERT_TRUE(proxy1 != NULL);
+ cricket::PortAllocatorSessionProxy* proxy2 =
+ static_cast<cricket::PortAllocatorSessionProxy*>(session2.get());
+ ASSERT_TRUE(proxy2 != NULL);
+ EXPECT_EQ(proxy1->impl(), proxy2->impl());
+ AddInterface(kClientAddr);
+ session1->GetInitialPorts();
+ session2->GetInitialPorts();
+ // Each session should receive two proxy ports of local and stun.
+ ASSERT_EQ_WAIT(4U, ports_.size(), 1000);
+ EXPECT_EQ(4U, candidates_.size());
+ EXPECT_PRED5(CheckCandidate, candidates_[0],
+ "rtp", "local", "udp", kClientAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[1],
+ "rtcp", "local", "udp", kClientAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[2],
+ "rtp", "stun", "udp", kClientAddr);
+ EXPECT_PRED5(CheckCandidate, candidates_[3],
+ "rtcp", "stun", "udp", kClientAddr);
+ talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
+ CreateSession("session1", "video_rtp", "video", allocator.get()));
+ // ListenToEvents(session3.get());
+ session3->GetInitialPorts();
+ // Since real ports and sessions are already allocated and signal sent, no
+ // new ports will be allocated when new proxy session created.
+ talk_base::Thread::Current()->ProcessMessages(1000);
+ EXPECT_NE(6U, ports_.size());
+ // Creating a PortAllocatorSession with different session name from above.
+ // In this case proxy PAS should have a different PAS.
+ // Session ID - session2.
+ talk_base::scoped_ptr<cricket::PortAllocatorSession> session4(
+ CreateSession("session2", "video_rtp", "video", allocator.get()));
+ cricket::PortAllocatorSessionProxy* proxy4 =
+ static_cast<cricket::PortAllocatorSessionProxy*>(session4.get());
+ EXPECT_NE(proxy4->impl(), proxy1->impl());
+}
diff --git a/talk/session/phone/audioframe.h b/talk/session/phone/audioframe.h
new file mode 100644
index 0000000..bcd8410
--- /dev/null
+++ b/talk/session/phone/audioframe.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_AUDIOFRAME_H_
+#define TALK_SESSION_PHONE_AUDIOFRAME_H_
+
+namespace cricket {
+
+class AudioFrame {
+ public:
+ AudioFrame()
+ : audio10ms_(NULL),
+ length_(0),
+ sampling_frequency_(8000),
+ stereo_(false) {
+ }
+ AudioFrame(int16* audio, size_t audio_length, int sample_freq, bool stereo)
+ : audio10ms_(audio),
+ length_(audio_length),
+ sampling_frequency_(sample_freq),
+ stereo_(stereo) {
+ }
+
+ int16* GetData() { return audio10ms_; }
+ size_t GetSize() const { return length_; }
+ int GetSamplingFrequency() const { return sampling_frequency_; }
+ bool GetStereo() const { return stereo_; }
+
+ private:
+ int16* audio10ms_;
+ size_t length_;
+ int sampling_frequency_;
+ bool stereo_;
+};
+
+} // namespace cricket
+#endif // TALK_SESSION_PHONE_AUDIOFRAME_H_
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index e01e937..0273353 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -29,6 +29,7 @@
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/thread.h"
+#include "talk/p2p/base/parsing.h"
#include "talk/session/phone/call.h"
#include "talk/session/phone/mediasessionclient.h"
@@ -140,8 +141,8 @@
StaticVideoViews::const_iterator it;
for (it = view_request.static_video_views.begin();
it != view_request.static_video_views.end(); ++it) {
- NamedSource found_source;
- bool found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
+ StreamParams found_stream;
+ bool found = recv_streams_.GetVideoStreamBySsrc(it->ssrc, &found_stream);
if (!found) {
LOG(LS_WARNING) <<
"Tried sending view request for bad ssrc: " << it->ssrc;
@@ -151,7 +152,7 @@
XmlElements elems;
WriteError error;
- if (!WriteViewRequest(CN_VIDEO, view_request, &elems, &error)) {
+ if (!WriteJingleViewRequest(CN_VIDEO, view_request, &elems, &error)) {
LOG(LS_ERROR) << "Couldn't write out view request: " << error.text;
return false;
}
@@ -178,34 +179,41 @@
}
}
-void Call::AddVoiceStream(Session *session, uint32 voice_ssrc) {
+
+
+
+void Call::AddAudioRecvStream(Session *session, const StreamParams& stream) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
- if (voice_channel && voice_ssrc) {
- voice_channel->AddStream(voice_ssrc);
+ if (voice_channel && stream.has_ssrcs()) {
+ voice_channel->AddRecvStream(stream);
}
+ recv_streams_.AddAudioStream(stream);
}
-void Call::AddVideoStream(Session *session, uint32 video_ssrc) {
+void Call::AddVideoRecvStream(Session *session, const StreamParams& stream) {
VideoChannel *video_channel = GetVideoChannel(session);
- if (video_channel && video_ssrc) {
- // TODO: Do we need the audio_ssrc here?
- // It doesn't seem to be used.
- video_channel->AddStream(video_ssrc, 0U);
+ if (video_channel && stream.has_ssrcs()) {
+ video_channel->AddRecvStream(stream);
}
+ recv_streams_.AddVideoStream(stream);
}
-void Call::RemoveVoiceStream(Session *session, uint32 voice_ssrc) {
+void Call::RemoveAudioRecvStream(Session *session, const StreamParams& stream) {
VoiceChannel *voice_channel = GetVoiceChannel(session);
- if (voice_channel && voice_ssrc) {
- voice_channel->RemoveStream(voice_ssrc);
+ // TODO: Change RemoveRecvStream to take a stream argument.
+ if (voice_channel && stream.has_ssrcs()) {
+ voice_channel->RemoveRecvStream(stream.first_ssrc());
}
+ recv_streams_.RemoveAudioStreamByNickAndName(stream.nick, stream.name);
}
-void Call::RemoveVideoStream(Session *session, uint32 video_ssrc) {
+void Call::RemoveVideoRecvStream(Session *session, const StreamParams& stream) {
VideoChannel *video_channel = GetVideoChannel(session);
- if (video_channel && video_ssrc) {
- video_channel->RemoveStream(video_ssrc);
+ // TODO: Change RemoveRecvStream to take a stream argument.
+ if (video_channel && stream.has_ssrcs()) {
+ video_channel->RemoveRecvStream(stream.first_ssrc());
}
+ recv_streams_.RemoveVideoStreamByNickAndName(stream.nick, stream.name);
}
void Call::OnMessage(talk_base::Message *message) {
@@ -239,13 +247,6 @@
VoiceChannel *voice_channel = NULL;
VideoChannel *video_channel = NULL;
- // Generate a random string for the RTCP CNAME, as stated in RFC 6222.
- // This string is only used for synchronization, and therefore is opaque.
- std::string rtcp_cname;
- if (!talk_base::CreateRandomString(16, &rtcp_cname)) {
- return false;
- }
-
const ContentInfo* audio_offer = GetFirstAudioContent(offer);
const ContentInfo* video_offer = GetFirstVideoContent(offer);
video_ = (video_offer != NULL);
@@ -257,7 +258,6 @@
// voice_channel can be NULL in case of NullVoiceEngine.
if (voice_channel) {
voice_channel_map_[session->id()] = voice_channel;
- voice_channel->SetRtcpCName(rtcp_cname);
voice_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
voice_channel->StartMediaMonitor(kMediaMonitorInterval);
} else {
@@ -271,7 +271,6 @@
// video_channel can be NULL in case of NullVideoEngine.
if (video_channel) {
video_channel_map_[session->id()] = video_channel;
- video_channel->SetRtcpCName(rtcp_cname);
video_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
video_channel->StartMediaMonitor(kMediaMonitorInterval);
} else {
@@ -284,7 +283,10 @@
sessions_.push_back(session);
session->SignalState.connect(this, &Call::OnSessionState);
session->SignalError.connect(this, &Call::OnSessionError);
- session->SignalInfoMessage.connect(this, &Call::OnSessionInfo);
+ session->SignalInfoMessage.connect(
+ this, &Call::OnSessionInfoMessage);
+ session->SignalRemoteDescriptionUpdate.connect(
+ this, &Call::OnRemoteDescriptionUpdate);
session->SignalReceivedTerminateReason
.connect(this, &Call::OnReceivedTerminateReason);
@@ -553,11 +555,10 @@
}
void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) {
- NamedSource source;
- source.ssrc = ssrc;
- media_sources_.GetAudioSourceBySsrc(ssrc, &source);
+ StreamParams stream;
+ recv_streams_.GetAudioStreamBySsrc(ssrc, &stream);
SignalSpeakerMonitor(this, static_cast<Session *>(monitor->session()),
- source);
+ stream);
}
void Call::OnConnectionMonitor(VideoChannel *channel,
@@ -593,92 +594,136 @@
SignalSessionError(this, static_cast<Session *>(session), error);
}
-void Call::OnSessionInfo(Session *session,
- const buzz::XmlElement* action_elem) {
- // We have a different list of "updates" because we only want to
- // signal the sources that were added or removed. We want to filter
- // out un-changed sources.
- cricket::MediaSources updates;
+void Call::OnSessionInfoMessage(Session *session,
+ const buzz::XmlElement* action_elem) {
+ if (!IsJingleViewRequest(action_elem)) {
+ return;
+ }
- if (IsSourcesNotify(action_elem)) {
- MediaSources sources;
- ParseError error;
- if (!ParseSourcesNotify(action_elem, session->remote_description(),
- &sources, &error)) {
- // TODO: Is there a way we can signal an IQ error
- // back to the sender?
- LOG(LS_WARNING) << "Invalid sources notify message: " << error.text;
- return;
- }
+ ViewRequest view_request;
+ ParseError error;
+ if (!ParseJingleViewRequest(action_elem, &view_request, &error)) {
+ LOG(LS_WARNING) << "Failed to parse view request: " << error.text;
+ return;
+ }
- NamedSources::iterator it;
- for (it = sources.mutable_audio()->begin();
- it != sources.mutable_audio()->end(); ++it) {
- bool found = false;
- NamedSource found_source;
- if (it->ssrc_set) {
- found = media_sources_.GetAudioSourceBySsrc(it->ssrc, &found_source);
- } else {
- // For backwards compatibility, we remove by nick.
- // TODO: Remove once all senders use explicit remove by ssrc.
- found = media_sources_.GetFirstAudioSourceByNick(it->nick,
- &found_source);
- if (found) {
- it->SetSsrc(found_source.ssrc);
- it->removed = true;
- } else {
- continue; // No ssrc to remove.
- }
- }
- if (it->removed && found) {
- RemoveVoiceStream(session, found_source.ssrc);
- media_sources_.RemoveAudioSourceBySsrc(it->ssrc);
- updates.mutable_audio()->push_back(*it);
- LOG(LS_INFO) << "Removed voice stream: " << found_source.ssrc;
- } else if (!it->removed && !found) {
- AddVoiceStream(session, it->ssrc);
- media_sources_.AddAudioSource(*it);
- updates.mutable_audio()->push_back(*it);
- LOG(LS_INFO) << "Added voice stream: " << it->ssrc;
- }
- }
- for (it = sources.mutable_video()->begin();
- it != sources.mutable_video()->end(); ++it) {
- bool found = false;
- NamedSource found_source;
- if (it->ssrc_set) {
- found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
- } else {
- // For backwards compatibility, we remove by nick.
- // TODO: Remove once all senders use explicit remove by ssrc.
- found = media_sources_.GetFirstVideoSourceByNick(it->nick,
- &found_source);
- if (found) {
- it->SetSsrc(found_source.ssrc);
- it->removed = true;
- } else {
- continue; // No ssrc to remove.
- }
- }
- if (it->removed && found) {
- RemoveVideoStream(session, found_source.ssrc);
- media_sources_.RemoveVideoSourceBySsrc(it->ssrc);
- updates.mutable_video()->push_back(*it);
- LOG(LS_INFO) << "Removed video stream: " << found_source.ssrc;
- } else if (!it->removed && !found) {
- AddVideoStream(session, it->ssrc);
- media_sources_.AddVideoSource(*it);
- updates.mutable_video()->push_back(*it);
- LOG(LS_INFO) << "Added video stream: " << it->ssrc;
- }
- }
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (video_channel == NULL) {
+ LOG(LS_WARNING) << "Ignore view request since we have no video channel.";
+ return;
+ }
- if (!updates.audio().empty() || !updates.video().empty()) {
- SignalMediaSourcesUpdate(this, session, updates);
+ if (!video_channel->ApplyViewRequest(view_request)) {
+ LOG(LS_WARNING) << "Failed to ApplyViewRequest.";
+ }
+}
+
+void FindStreamChanges(const std::vector<StreamParams>& streams,
+ const std::vector<StreamParams>& updates,
+ std::vector<StreamParams>* added_streams,
+ std::vector<StreamParams>* removed_streams) {
+ for (std::vector<StreamParams>::const_iterator update = updates.begin();
+ update != updates.end(); ++update) {
+ StreamParams stream;
+ if (GetStreamByNickAndName(streams, update->nick, update->name, &stream)) {
+ if (!update->has_ssrcs()) {
+ removed_streams->push_back(stream);
+ }
+ } else {
+ // There's a bug on reflector that will send <stream>s even
+ // though there is not ssrc (which means there isn't really a
+ // stream). To work around it, we simply ignore new <stream>s
+ // that don't have any ssrcs.
+ if (update->has_ssrcs()) {
+ added_streams->push_back(*update);
+ }
}
}
}
+void Call::OnRemoteDescriptionUpdate(BaseSession *base_session,
+ const ContentInfos& updated_contents) {
+ Session* session = static_cast<Session *>(base_session);
+
+ cricket::MediaStreams added_streams;
+ cricket::MediaStreams removed_streams;
+ std::vector<StreamParams>::const_iterator stream;
+
+ const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+ if (audio_content) {
+ const AudioContentDescription* audio_update =
+ static_cast<const AudioContentDescription*>(audio_content->description);
+ if (!audio_update->codecs().empty()) {
+ UpdateVoiceChannelRemoteContent(session, audio_update);
+ }
+
+ FindStreamChanges(recv_streams_.audio(),
+ audio_update->streams(),
+ added_streams.mutable_audio(),
+ removed_streams.mutable_audio());
+ for (stream = added_streams.audio().begin();
+ stream != added_streams.audio().end();
+ ++stream) {
+ AddAudioRecvStream(session, *stream);
+ }
+ for (stream = removed_streams.audio().begin();
+ stream != removed_streams.audio().end();
+ ++stream) {
+ RemoveAudioRecvStream(session, *stream);
+ }
+ }
+
+ const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+ if (video_content) {
+ const VideoContentDescription* video_update =
+ static_cast<const VideoContentDescription*>(video_content->description);
+ if (!video_update->codecs().empty()) {
+ UpdateVideoChannelRemoteContent(session, video_update);
+ }
+
+ FindStreamChanges(recv_streams_.video(),
+ video_update->streams(),
+ added_streams.mutable_video(),
+ removed_streams.mutable_video());
+ for (stream = added_streams.video().begin();
+ stream != added_streams.video().end();
+ ++stream) {
+ AddVideoRecvStream(session, *stream);
+ }
+ for (stream = removed_streams.video().begin();
+ stream != removed_streams.video().end();
+ ++stream) {
+ RemoveVideoRecvStream(session, *stream);
+ }
+ }
+
+ if (!added_streams.empty() || !removed_streams.empty()) {
+ SignalMediaStreamsUpdate(this, session, added_streams, removed_streams);
+ }
+}
+
+bool Call::UpdateVoiceChannelRemoteContent(
+ Session* session, const AudioContentDescription* audio) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ if (!voice_channel->SetRemoteContent(audio, CA_UPDATE)) {
+ LOG(LS_ERROR) << "Failure in audio SetRemoteContent with CA_UPDATE";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ return false;
+ }
+ return true;
+}
+
+bool Call::UpdateVideoChannelRemoteContent(
+ Session* session, const VideoContentDescription* video) {
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (!video_channel->SetRemoteContent(video, CA_UPDATE)) {
+ LOG(LS_ERROR) << "Failure in video SetRemoteContent with CA_UPDATE";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ return false;
+ }
+ return true;
+}
+
void Call::OnReceivedTerminateReason(Session *session,
const std::string &reason) {
session_client_->session_manager()->signaling_thread()->Clear(this,
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
index 357f884..e081c55 100644
--- a/talk/session/phone/call.h
+++ b/talk/session/phone/call.h
@@ -40,6 +40,7 @@
#include "talk/session/phone/currentspeakermonitor.h"
#include "talk/session/phone/mediamessages.h"
#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/streamparams.h"
namespace cricket {
@@ -61,7 +62,7 @@
void RejectSession(Session *session);
void TerminateSession(Session *session);
void Terminate();
- bool SendViewRequest(Session* session,
+ bool SendViewRequest(Session *session,
const ViewRequest& view_request);
void SetLocalRenderer(VideoRenderer* renderer);
void SetVideoRenderer(Session *session, uint32 ssrc,
@@ -105,23 +106,30 @@
SignalConnectionMonitor;
sigslot::signal2<Call *, const VoiceMediaInfo&> SignalMediaMonitor;
sigslot::signal2<Call *, const AudioInfo&> SignalAudioMonitor;
- // Empty nick on NamedSource means "unknown".
- // Ssrc of 0 on NamedSource means "no current speaker".
+ // Empty nick on StreamParams means "unknown".
+ // No ssrcs in StreamParams means "no current speaker".
sigslot::signal3<Call *,
Session *,
- const NamedSource&> SignalSpeakerMonitor;
+ const StreamParams&> SignalSpeakerMonitor;
sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
SignalVideoConnectionMonitor;
sigslot::signal2<Call *, const VideoMediaInfo&> SignalVideoMediaMonitor;
- sigslot::signal3<Call *,
+ // Gives added streams and removed streams, in that order.
+ sigslot::signal4<Call *,
Session *,
- const MediaSources&> SignalMediaSourcesUpdate;
+ const MediaStreams&,
+ const MediaStreams&> SignalMediaStreamsUpdate;
private:
void OnMessage(talk_base::Message *message);
void OnSessionState(BaseSession *session, BaseSession::State state);
void OnSessionError(BaseSession *session, Session::Error error);
- void OnSessionInfo(Session *session, const buzz::XmlElement* action_elem);
+ void OnSessionInfoMessage(
+ Session *session, const buzz::XmlElement* action_elem);
+ void OnViewRequest(
+ Session *session, const ViewRequest& view_request);
+ void OnRemoteDescriptionUpdate(
+ BaseSession *session, const ContentInfos& updated_contents);
void OnReceivedTerminateReason(Session *session, const std::string &reason);
void IncomingSession(Session *session, const SessionDescription* offer);
// Returns true on success.
@@ -137,18 +145,24 @@
void OnConnectionMonitor(VideoChannel *channel,
const std::vector<ConnectionInfo> &infos);
void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
- VoiceChannel* GetVoiceChannel(Session* session);
- VideoChannel* GetVideoChannel(Session* session);
- void AddVoiceStream(Session *session, uint32 voice_ssrc);
- void AddVideoStream(Session *session, uint32 video_ssrc);
- void RemoveVoiceStream(Session *session, uint32 voice_ssrc);
- void RemoveVideoStream(Session *session, uint32 video_ssrc);
+ VoiceChannel* GetVoiceChannel(Session *session);
+ VideoChannel* GetVideoChannel(Session *session);
+ bool UpdateVoiceChannelRemoteContent(Session *session,
+ const AudioContentDescription* audio);
+ bool UpdateVideoChannelRemoteContent(Session *session,
+ const VideoContentDescription* video);
+ void AddAudioRecvStream(Session *session, const StreamParams& audio_stream);
+ void AddVideoRecvStream(Session *session, const StreamParams& video_stream);
+ void RemoveAudioRecvStream(
+ Session *session, const StreamParams& audio_stream);
+ void RemoveVideoRecvStream(
+ Session *session, const StreamParams& video_stream);
void ContinuePlayDTMF();
uint32 id_;
MediaSessionClient *session_client_;
std::vector<Session *> sessions_;
- MediaSources media_sources_;
+ MediaStreams recv_streams_;
std::map<std::string, VoiceChannel *> voice_channel_map_;
std::map<std::string, VideoChannel *> video_channel_map_;
std::map<std::string, CurrentSpeakerMonitor *> speaker_monitor_map_;
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
index 9cc88ff..1481887 100644
--- a/talk/session/phone/channel.cc
+++ b/talk/session/phone/channel.cc
@@ -33,16 +33,143 @@
#include "talk/base/logging.h"
#include "talk/p2p/base/transportchannel.h"
#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediamessages.h"
#include "talk/session/phone/mediasessionclient.h"
#include "talk/session/phone/rtcpmuxfilter.h"
#include "talk/session/phone/rtputils.h"
namespace cricket {
+enum {
+ MSG_ENABLE = 1,
+ MSG_DISABLE = 2,
+ MSG_MUTE = 3,
+ MSG_UNMUTE = 4,
+ MSG_SETREMOTECONTENT = 5,
+ MSG_SETLOCALCONTENT = 6,
+ MSG_EARLYMEDIATIMEOUT = 8,
+ MSG_PRESSDTMF = 9,
+ MSG_SETRENDERER = 10,
+ MSG_ADDRECVSTREAM = 11,
+ MSG_REMOVERECVSTREAM = 12,
+ MSG_SETRINGBACKTONE = 13,
+ MSG_PLAYRINGBACKTONE = 14,
+ MSG_SETMAXSENDBANDWIDTH = 15,
+ MSG_ADDSCREENCAST = 16,
+ MSG_REMOVESCREENCAST = 17,
+ // Removed MSG_SETRTCPCNAME = 18. It is no longer used.
+ MSG_SENDINTRAFRAME = 19,
+ MSG_REQUESTINTRAFRAME = 20,
+ MSG_SCREENCASTWINDOWEVENT = 21,
+ MSG_RTPPACKET = 22,
+ MSG_RTCPPACKET = 23,
+ MSG_CHANNEL_ERROR = 24,
+ MSG_ENABLECPUADAPTATION = 25,
+ MSG_DISABLECPUADAPTATION = 26,
+ MSG_SCALEVOLUME = 27,
+ MSG_HANDLEVIEWREQUEST = 28
+};
+
+struct SetContentData : public talk_base::MessageData {
+ SetContentData(const MediaContentDescription* content, ContentAction action)
+ : content(content),
+ action(action),
+ result(false) {
+ }
+ const MediaContentDescription* content;
+ ContentAction action;
+ bool result;
+};
+
+struct SetBandwidthData : public talk_base::MessageData {
+ explicit SetBandwidthData(int value) : value(value), result(false) {}
+ int value;
+ bool result;
+};
+
+struct SetRingbackToneMessageData : public talk_base::MessageData {
+ SetRingbackToneMessageData(const void* b, int l)
+ : buf(b),
+ len(l),
+ result(false) {
+ }
+ const void* buf;
+ int len;
+ bool result;
+};
+
+struct PlayRingbackToneMessageData : public talk_base::MessageData {
+ PlayRingbackToneMessageData(uint32 s, bool p, bool l)
+ : ssrc(s),
+ play(p),
+ loop(l),
+ result(false) {
+ }
+ uint32 ssrc;
+ bool play;
+ bool loop;
+ bool result;
+};
+struct DtmfMessageData : public talk_base::MessageData {
+ DtmfMessageData(int d, bool p)
+ : digit(d),
+ playout(p),
+ result(false) {
+ }
+ int digit;
+ bool playout;
+ bool result;
+};
+struct ScaleVolumeMessageData : public talk_base::MessageData {
+ ScaleVolumeMessageData(uint32 s, double l, double r)
+ : ssrc(s),
+ left(l),
+ right(r),
+ result(false) {
+ }
+ uint32 ssrc;
+ double left;
+ double right;
+ bool result;
+};
+
struct PacketMessageData : public talk_base::MessageData {
talk_base::Buffer packet;
};
+struct RenderMessageData : public talk_base::MessageData {
+ RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+ uint32 ssrc;
+ VideoRenderer* renderer;
+};
+
+struct ScreencastMessageData : public talk_base::MessageData {
+ ScreencastMessageData(uint32 s, const ScreencastId& id)
+ : ssrc(s),
+ window_id(id) {
+ }
+ uint32 ssrc;
+ ScreencastId window_id;
+};
+
+struct ScreencastEventData : public talk_base::MessageData {
+ ScreencastEventData(uint32 s, talk_base::WindowEvent we)
+ : ssrc(s),
+ event(we) {
+ }
+ uint32 ssrc;
+ talk_base::WindowEvent event;
+};
+
+struct ViewRequestMessageData : public talk_base::MessageData {
+ explicit ViewRequestMessageData(const ViewRequest& r)
+ : request(r),
+ result(false) {
+ }
+ ViewRequest request;
+ bool result;
+};
+
struct VoiceChannelErrorMessageData : public talk_base::MessageData {
VoiceChannelErrorMessageData(uint32 in_ssrc,
VoiceMediaChannel::Error in_error)
@@ -61,6 +188,18 @@
VideoMediaChannel::Error error;
};
+struct SsrcMessageData : public talk_base::MessageData {
+ explicit SsrcMessageData(uint32 ssrc) : ssrc(ssrc), result(false) {}
+ uint32 ssrc;
+ bool result;
+};
+
+struct StreamMessageData : public talk_base::MessageData {
+ explicit StreamMessageData(const StreamParams& sp) : sp(sp), result(false) {}
+ StreamParams sp;
+ bool result;
+};
+
static const char* PacketType(bool rtcp) {
return (!rtcp) ? "RTP" : "RTCP";
}
@@ -125,35 +264,34 @@
this, &BaseChannel::OnChannelRead);
session_->SignalState.connect(this, &BaseChannel::OnSessionState);
- session_->SignalRemoteDescriptionUpdate.connect(this,
- &BaseChannel::OnRemoteDescriptionUpdate);
OnSessionState(session(), session()->state());
set_rtcp_transport_channel(rtcp_transport_channel);
return true;
}
+// Can be called from thread other than worker thread
bool BaseChannel::Enable(bool enable) {
- // Can be called from thread other than worker thread
Send(enable ? MSG_ENABLE : MSG_DISABLE);
return true;
}
+// Can be called from thread other than worker thread
bool BaseChannel::Mute(bool mute) {
- // Can be called from thread other than worker thread
+ Clear(MSG_UNMUTE); // Clear any penging auto-unmutes.
Send(mute ? MSG_MUTE : MSG_UNMUTE);
return true;
}
-bool BaseChannel::RemoveStream(uint32 ssrc) {
- StreamMessageData data(ssrc, 0);
- Send(MSG_REMOVESTREAM, &data);
- return true;
+bool BaseChannel::AddRecvStream(const StreamParams& sp) {
+ StreamMessageData data(sp);
+ Send(MSG_ADDRECVSTREAM, &data);
+ return data.result;
}
-bool BaseChannel::SetRtcpCName(const std::string& cname) {
- SetRtcpCNameData data(cname);
- Send(MSG_SETRTCPCNAME, &data);
+bool BaseChannel::RemoveRecvStream(uint32 ssrc) {
+ SsrcMessageData data(ssrc);
+ Send(MSG_REMOVERECVSTREAM, &data);
return data.result;
}
@@ -346,6 +484,14 @@
return;
}
+ // If this channel is suppose to handle RTP data, that is determined by
+ // checking against ssrc filter. This is necessary to do it here to avoid
+ // double decryption.
+ if (ssrc_filter_.IsActive() &&
+ !ssrc_filter_.DemuxPacket(packet->data(), packet->length(), rtcp)) {
+ return;
+ }
+
// Signal to the media sink before unprotecting the packet. TODO:
// Separate APIs to record unprotected media and protected header.
{
@@ -429,16 +575,6 @@
}
}
-void BaseChannel::OnRemoteDescriptionUpdate(BaseSession* session) {
- const MediaContentDescription* content =
- GetFirstContent(session->remote_description());
-
- if (content && !SetRemoteContent(content, CA_UPDATE)) {
- LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_UPDATE";
- session->SetError(BaseSession::ERROR_CONTENT);
- }
-}
-
void BaseChannel::EnableMedia_w() {
ASSERT(worker_thread_ == talk_base::Thread::Current());
if (enabled_)
@@ -509,10 +645,6 @@
return media_channel()->SetSendBandwidth(true, max_bandwidth);
}
-bool BaseChannel::SetRtcpCName_w(const std::string& cname) {
- return media_channel()->SetRtcpCName(cname);
-}
-
bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
ContentAction action, ContentSource src) {
bool ret;
@@ -549,6 +681,177 @@
return ret;
}
+bool BaseChannel::AddRecvStream_w(const StreamParams& sp) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ if (!media_channel()->AddRecvStream(sp))
+ return false;
+
+ return ssrc_filter_.AddStream(sp);
+}
+
+bool BaseChannel::RemoveRecvStream_w(uint32 ssrc) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ ssrc_filter_.RemoveStream(ssrc);
+ return media_channel()->RemoveRecvStream(ssrc);
+}
+
+bool BaseChannel::UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
+ ContentAction action) {
+ if (!VERIFY(action == CA_OFFER || action == CA_ANSWER || action == CA_UPDATE))
+ return false;
+
+ // If this is an update, streams only contain streams that have changed.
+ if (action == CA_UPDATE) {
+ for (StreamParamsVec::const_iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ StreamParams existing_stream;
+ bool stream_exist = GetStreamByNickAndName(local_streams_, it->nick,
+ it->name, &existing_stream);
+ if (!stream_exist && it->has_ssrcs()) {
+ if (media_channel()->AddSendStream(*it)) {
+ local_streams_.push_back(*it);
+ LOG(LS_INFO) << "Add send stream ssrc: " << it->first_ssrc();
+ } else {
+ LOG(LS_INFO) << "Failed to add send stream ssrc: "
+ << it->first_ssrc();
+ return false;
+ }
+ } else if (stream_exist && !it->has_ssrcs()) {
+ if (!media_channel()->RemoveSendStream(existing_stream.first_ssrc())) {
+ LOG(LS_ERROR) << "Failed to remove send stream with ssrc "
+ << it->first_ssrc() << ".";
+ return false;
+ }
+ RemoveStreamBySsrc(&local_streams_, existing_stream.first_ssrc());
+ } else {
+ LOG(LS_WARNING) << "Ignore unsupported stream update";
+ }
+ }
+ return true;
+ }
+ // Else streams are all the streams we want to send.
+
+ // Check for streams that have been removed.
+ bool ret = true;
+ for (StreamParamsVec::const_iterator it = local_streams_.begin();
+ it != local_streams_.end(); ++it) {
+ if (!GetStreamBySsrc(streams, it->first_ssrc(), NULL)) {
+ if (!media_channel()->RemoveSendStream(it->first_ssrc())) {
+ LOG(LS_ERROR) << "Failed to remove send stream with ssrc "
+ << it->first_ssrc() << ".";
+ ret = false;
+ }
+ }
+ }
+ // Check for new streams.
+ for (StreamParamsVec::const_iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ if (!GetStreamBySsrc(local_streams_, it->first_ssrc(), NULL)) {
+ if (media_channel()->AddSendStream(*it)) {
+ LOG(LS_INFO) << "Add send ssrc: " << it->ssrcs[0];
+ } else {
+ LOG(LS_INFO) << "Failed to add send stream ssrc: " << it->first_ssrc();
+ ret = false;
+ }
+ }
+ }
+ local_streams_ = streams;
+ return ret;
+}
+
+bool BaseChannel::UpdateRemoteStreams_w(
+ const std::vector<StreamParams>& streams,
+ ContentAction action) {
+ // If this is an update, streams only contain streams that have changed.
+ if (action == CA_UPDATE) {
+ for (StreamParamsVec::const_iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ StreamParams existing_stream;
+ bool stream_exists = GetStreamByNickAndName(remote_streams_, it->nick,
+ it->name, &existing_stream);
+ if (!stream_exists && it->has_ssrcs()) {
+ if (AddRecvStream_w(*it)) {
+ remote_streams_.push_back(*it);
+ LOG(LS_INFO) << "Add remote stream ssrc: " << it->first_ssrc();
+ } else {
+ LOG(LS_INFO) << "Failed to add remote stream ssrc: "
+ << it->first_ssrc();
+ return false;
+ }
+ } else if (stream_exists && !it->has_ssrcs()) {
+ if (!RemoveRecvStream_w(existing_stream.first_ssrc())) {
+ LOG(LS_ERROR) << "Failed to remove remote stream with ssrc "
+ << it->first_ssrc() << ".";
+ return false;
+ }
+ RemoveStreamBySsrc(&remote_streams_, existing_stream.first_ssrc());
+ } else {
+ LOG(LS_WARNING) << "Ignore unsupported stream update";
+ }
+ }
+ return true;
+ }
+ // Else streams are all the streams we want to receive.
+
+ // Check for streams that have been removed.
+ bool ret = true;
+ for (StreamParamsVec::const_iterator it = remote_streams_.begin();
+ it != remote_streams_.end(); ++it) {
+ if (!GetStreamBySsrc(streams, it->first_ssrc(), NULL)) {
+ if (!RemoveRecvStream_w(it->first_ssrc())) {
+ LOG(LS_ERROR) << "Failed to remove remote stream with ssrc "
+ << it->first_ssrc() << ".";
+ ret = false;
+ }
+ }
+ }
+ // Check for new streams.
+ for (StreamParamsVec::const_iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ if (!GetStreamBySsrc(remote_streams_, it->first_ssrc(), NULL)) {
+ if (AddRecvStream_w(*it)) {
+ LOG(LS_INFO) << "Add remote ssrc: " << it->ssrcs[0];
+ } else {
+ LOG(LS_INFO) << "Failed to add remote stream ssrc: "
+ << it->first_ssrc();
+ ret = false;
+ }
+ }
+ }
+ remote_streams_ = streams;
+ return ret;
+}
+
+bool BaseChannel::SetBaseLocalContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ bool ret = UpdateLocalStreams_w(content->streams(), action);
+ // Set local SRTP parameters (what we will encrypt with).
+ ret &= SetSrtp_w(content->cryptos(), action, CS_LOCAL);
+ // Set local RTCP mux parameters.
+ ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_LOCAL);
+ // Set local RTP header extensions.
+ if (content->rtp_header_extensions_set()) {
+ ret &= media_channel()->SetRecvRtpHeaderExtensions(
+ content->rtp_header_extensions());
+ }
+ return ret;
+}
+
+bool BaseChannel::SetBaseRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ bool ret = UpdateRemoteStreams_w(content->streams(), action);
+ // Set remote SRTP parameters (what the other side will encrypt with).
+ ret &= SetSrtp_w(content->cryptos(), action, CS_REMOTE);
+ // Set remote RTCP mux parameters.
+ ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_REMOTE);
+ // Set remote RTP header extensions.
+ if (content->rtp_header_extensions_set()) {
+ ret &= media_channel()->SetSendRtpHeaderExtensions(
+ content->rtp_header_extensions());
+ }
+ return ret;
+}
+
void BaseChannel::OnMessage(talk_base::Message *pmsg) {
switch (pmsg->message_id) {
case MSG_ENABLE:
@@ -564,13 +867,6 @@
case MSG_UNMUTE:
UnmuteMedia_w();
break;
-
- case MSG_SETRTCPCNAME: {
- SetRtcpCNameData* data = static_cast<SetRtcpCNameData*>(pmsg->pdata);
- data->result = SetRtcpCName_w(data->cname);
- break;
- }
-
case MSG_SETLOCALCONTENT: {
SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
data->result = SetLocalContent_w(data->content, data->action);
@@ -581,13 +877,16 @@
data->result = SetRemoteContent_w(data->content, data->action);
break;
}
-
- case MSG_REMOVESTREAM: {
+ case MSG_ADDRECVSTREAM: {
StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
- RemoveStream_w(data->ssrc1);
+ data->result = AddRecvStream_w(data->sp);
break;
}
-
+ case MSG_REMOVERECVSTREAM: {
+ SsrcMessageData* data = static_cast<SsrcMessageData*>(pmsg->pdata);
+ data->result = RemoveRecvStream_w(data->ssrc);
+ break;
+ }
case MSG_SETMAXSENDBANDWIDTH: {
SetBandwidthData* data = static_cast<SetBandwidthData*>(pmsg->pdata);
data->result = SetMaxSendBandwidth_w(data->value);
@@ -641,7 +940,9 @@
bool rtcp)
: BaseChannel(thread, media_engine, media_channel, session, content_name,
rtcp),
- received_media_(false) {
+ received_media_(false),
+ mute_on_type_(false),
+ mute_on_type_timeout_(kTypingBlackoutPeriod) {
}
VoiceChannel::~VoiceChannel() {
@@ -665,12 +966,6 @@
return true;
}
-bool VoiceChannel::AddStream(uint32 ssrc) {
- StreamMessageData data(ssrc, 0);
- Send(MSG_ADDSTREAM, &data);
- return true;
-}
-
bool VoiceChannel::SetRingbackTone(const void* buf, int len) {
SetRingbackToneMessageData data(buf, len);
Send(MSG_SETRINGBACKTONE, &data);
@@ -774,7 +1069,7 @@
if (!media_channel()->SetPlayout(recv)) {
SendLastMediaError();
}
-
+
// Send outgoing data if we're the active call, we have the remote content,
// and we have had some form of connectivity.
bool send = enabled() && has_remote_content() && was_ever_writable();
@@ -804,27 +1099,17 @@
const AudioContentDescription* audio =
static_cast<const AudioContentDescription*>(content);
ASSERT(audio != NULL);
+ if (!audio) return false;
- bool ret;
- if (audio->ssrc_set()) {
- media_channel()->SetSendSsrc(audio->ssrc());
- LOG(LS_INFO) << "Set send ssrc for audio: " << audio->ssrc();
+ bool ret = SetBaseLocalContent_w(content, action);
+ // Set local audio codecs (what we want to receive).
+ // TODO: Change action != CA_UPDATE to !audio->partial() when partial
+ // is set properly.
+ if (action != CA_UPDATE || audio->has_codecs()) {
+ ret &= media_channel()->SetRecvCodecs(audio->codecs());
}
- // set SRTP
- ret = SetSrtp_w(audio->cryptos(), action, CS_LOCAL);
- // set RTCP mux
- if (ret) {
- ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_LOCAL);
- }
- // set payload type and config for voice codecs
- if (ret) {
- ret = media_channel()->SetRecvCodecs(audio->codecs());
- }
- // set header extensions
- if (ret && audio->rtp_header_extensions_set()) {
- ret = media_channel()->SetRecvRtpHeaderExtensions(
- audio->rtp_header_extensions());
- }
+
+ // If everything worked, see if we can start receiving.
if (ret) {
set_has_local_content(true);
ChangeState();
@@ -842,34 +1127,32 @@
const AudioContentDescription* audio =
static_cast<const AudioContentDescription*>(content);
ASSERT(audio != NULL);
+ if (!audio) return false;
- bool ret;
- // set SRTP
- ret = SetSrtp_w(audio->cryptos(), action, CS_REMOTE);
- // set RTCP mux
- if (ret) {
- ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_REMOTE);
- }
- // set codecs and payload types
- if (ret) {
- ret = media_channel()->SetSendCodecs(audio->codecs());
- }
- // set header extensions
- if (ret && audio->rtp_header_extensions_set()) {
- ret = media_channel()->SetSendRtpHeaderExtensions(
- audio->rtp_header_extensions());
+ bool ret = true;
+ // Set remote video codecs (what the other side wants to receive).
+ if (action != CA_UPDATE || audio->has_codecs()) {
+ ret &= media_channel()->SetSendCodecs(audio->codecs());
}
- int audio_options = 0;
- if (audio->conference_mode()) {
- audio_options |= OPT_CONFERENCE;
- }
- if (!media_channel()->SetOptions(audio_options)) {
- // Log an error on failure, but don't abort the call.
- LOG(LS_ERROR) << "Failed to set voice channel options";
+ ret &= SetBaseRemoteContent_w(content, action);
+
+ if (action != CA_UPDATE) {
+ // Tweak our audio processing settings, if needed.
+ int audio_options = 0;
+ if (audio->conference_mode()) {
+ audio_options |= OPT_CONFERENCE;
+ }
+ if (audio->agc_minus_10db()) {
+ audio_options |= OPT_AGC_MINUS_10DB;
+ }
+ if (!media_channel()->SetOptions(audio_options)) {
+ // Log an error on failure, but don't abort the call.
+ LOG(LS_ERROR) << "Failed to set voice channel options";
+ }
}
- // update state
+ // If everything worked, see if we can start sending.
if (ret) {
set_has_remote_content(true);
ChangeState();
@@ -879,15 +1162,6 @@
return ret;
}
-void VoiceChannel::AddStream_w(uint32 ssrc) {
- ASSERT(worker_thread() == talk_base::Thread::Current());
- media_channel()->AddStream(ssrc);
-}
-
-void VoiceChannel::RemoveStream_w(uint32 ssrc) {
- media_channel()->RemoveStream(ssrc);
-}
-
bool VoiceChannel::SetRingbackTone_w(const void* buf, int len) {
ASSERT(worker_thread() == talk_base::Thread::Current());
return media_channel()->SetRingbackTone(static_cast<const char*>(buf), len);
@@ -925,11 +1199,6 @@
void VoiceChannel::OnMessage(talk_base::Message *pmsg) {
switch (pmsg->message_id) {
- case MSG_ADDSTREAM: {
- StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
- AddStream_w(data->ssrc1);
- break;
- }
case MSG_SETRINGBACKTONE: {
SetRingbackToneMessageData* data =
static_cast<SetRingbackToneMessageData*>(pmsg->pdata);
@@ -963,7 +1232,6 @@
delete data;
break;
}
-
default:
BaseChannel::OnMessage(pmsg);
break;
@@ -987,9 +1255,14 @@
}
void VoiceChannel::OnVoiceChannelError(
- uint32 ssrc, VoiceMediaChannel::Error error) {
- VoiceChannelErrorMessageData *data = new VoiceChannelErrorMessageData(
- ssrc, error);
+ uint32 ssrc, VoiceMediaChannel::Error err) {
+ if (err == VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED &&
+ mute_on_type_ && !muted()) {
+ Mute(true);
+ PostDelayed(mute_on_type_timeout_, MSG_UNMUTE, NULL);
+ }
+ VoiceChannelErrorMessageData* data = new VoiceChannelErrorMessageData(
+ ssrc, err);
signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
}
@@ -1036,6 +1309,8 @@
rtcp_channel)) {
return false;
}
+ media_channel()->SignalScreencastWindowEvent.connect(
+ this, &VideoChannel::OnScreencastWindowEvent);
media_channel()->SignalMediaError.connect(
this, &VideoChannel::OnVideoChannelError);
srtp_filter()->SignalSrtpError.connect(
@@ -1056,19 +1331,29 @@
DisableMedia_w();
}
-bool VideoChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
- StreamMessageData data(ssrc, voice_ssrc);
- Send(MSG_ADDSTREAM, &data);
- return true;
-}
-
bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
RenderMessageData data(ssrc, renderer);
Send(MSG_SETRENDERER, &data);
return true;
}
+bool VideoChannel::ApplyViewRequest(const ViewRequest& request) {
+ ViewRequestMessageData data(request);
+ Send(MSG_HANDLEVIEWREQUEST, &data);
+ return data.result;
+}
+bool VideoChannel::AddScreencast(uint32 ssrc, const ScreencastId& id) {
+ ScreencastMessageData data(ssrc, id);
+ Send(MSG_ADDSCREENCAST, &data);
+ return true;
+}
+
+bool VideoChannel::RemoveScreencast(uint32 ssrc) {
+ ScreencastMessageData data(ssrc, ScreencastId());
+ Send(MSG_REMOVESCREENCAST, &data);
+ return true;
+}
bool VideoChannel::SendIntraFrame() {
Send(MSG_SENDINTRAFRAME);
@@ -1136,26 +1421,15 @@
const VideoContentDescription* video =
static_cast<const VideoContentDescription*>(content);
ASSERT(video != NULL);
+ if (!video) return false;
- bool ret;
- if (video->ssrc_set()) {
- media_channel()->SetSendSsrc(video->ssrc());
- LOG(LS_INFO) << "Set send ssrc for video: " << video->ssrc();
+ bool ret = SetBaseLocalContent_w(content, action);
+ // Set local video codecs (what we want to receive).
+ if (action != CA_UPDATE || video->has_codecs()) {
+ ret &= media_channel()->SetRecvCodecs(video->codecs());
}
- // set SRTP
- ret = SetSrtp_w(video->cryptos(), action, CS_LOCAL);
- // set RTCP mux
- if (ret) {
- ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_LOCAL);
- }
- // set payload types and config for receiving video
- if (ret) {
- ret = media_channel()->SetRecvCodecs(video->codecs());
- }
- if (ret && video->rtp_header_extensions_set()) {
- ret = media_channel()->SetRecvRtpHeaderExtensions(
- video->rtp_header_extensions());
- }
+
+ // If everything worked, see if we can start receiving.
if (ret) {
set_has_local_content(true);
ChangeState();
@@ -1173,28 +1447,33 @@
const VideoContentDescription* video =
static_cast<const VideoContentDescription*>(content);
ASSERT(video != NULL);
+ if (!video) return false;
- bool ret;
- // set SRTP
- ret = SetSrtp_w(video->cryptos(), action, CS_REMOTE);
- // set RTCP mux
- if (ret) {
- ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_REMOTE);
+ bool ret = true;
+ // Set remote video codecs (what the other side wants to receive).
+ if (action != CA_UPDATE || video->has_codecs()) {
+ ret &= media_channel()->SetSendCodecs(video->codecs());
}
- // Set video bandwidth parameters.
- if (ret) {
+
+ ret &= SetBaseRemoteContent_w(content, action);
+
+ if (action != CA_UPDATE) {
+ // Tweak our video processing settings, if needed.
+ int video_options = 0;
+ if (video->conference_mode()) {
+ video_options |= OPT_CONFERENCE;
+ }
+ if (!media_channel()->SetOptions(video_options)) {
+ // Log an error on failure, but don't abort the call.
+ LOG(LS_ERROR) << "Failed to set video channel options";
+ }
+ // Set bandwidth parameters (what the other side wants to get, default=auto)
int bandwidth_bps = video->bandwidth();
bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
- ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+ ret &= media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
}
- if (ret) {
- ret = media_channel()->SetSendCodecs(video->codecs());
- }
- // set header extensions
- if (ret && video->rtp_header_extensions_set()) {
- ret = media_channel()->SetSendRtpHeaderExtensions(
- video->rtp_header_extensions());
- }
+
+ // If everything worked, see if we can start sending.
if (ret) {
set_has_remote_content(true);
ChangeState();
@@ -1204,31 +1483,87 @@
return ret;
}
-void VideoChannel::AddStream_w(uint32 ssrc, uint32 voice_ssrc) {
- media_channel()->AddStream(ssrc, voice_ssrc);
-}
+bool VideoChannel::ApplyViewRequest_w(const ViewRequest& request) {
+ bool ret = true;
+ // Set the send format for each of the local streams. If the view request
+ // does not contain a local stream, set its send format to 0x0, which will
+ // drop all frames.
+ for (std::vector<StreamParams>::const_iterator it = local_streams().begin();
+ it != local_streams().end(); ++it) {
+ VideoFormat format(0, 0, 0, cricket::FOURCC_I420);
+ StaticVideoViews::const_iterator view;
+ for (view = request.static_video_views.begin();
+ view != request.static_video_views.end(); ++view) {
+ // Sender view request from Reflector has SSRC 0 (b/5977302). Here we hack
+ // the client to apply the view request with SSRC 0. TODO: Remove
+ // 0 == view->SSRC once Reflector uses the correct SSRC in view request.
+ if (it->has_ssrc(view->ssrc) || 0 == view->ssrc) {
+ format.width = view->width;
+ format.height = view->height;
+ format.interval = cricket::VideoFormat::FpsToInterval(view->framerate);
+ break;
+ }
+ }
-void VideoChannel::RemoveStream_w(uint32 ssrc) {
- media_channel()->RemoveStream(ssrc);
+ ret &= media_channel()->SetSendStreamFormat(it->first_ssrc(), format);
+ }
+
+ // Check if the view request has invalid streams.
+ for (StaticVideoViews::const_iterator it = request.static_video_views.begin();
+ it != request.static_video_views.end(); ++it) {
+ if (!GetStreamBySsrc(local_streams(), it->ssrc, NULL)) {
+ LOG(LS_WARNING) << "View request's SSRC " << it->ssrc
+ << " is not in the local streams.";
+ }
+ }
+
+ return ret;
}
void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
media_channel()->SetRenderer(ssrc, renderer);
}
+void VideoChannel::AddScreencast_w(uint32 ssrc, const ScreencastId& id) {
+ media_channel()->AddScreencast(ssrc, id);
+}
+
+void VideoChannel::RemoveScreencast_w(uint32 ssrc) {
+ media_channel()->RemoveScreencast(ssrc);
+}
+
+void VideoChannel::OnScreencastWindowEvent_s(uint32 ssrc,
+ talk_base::WindowEvent we) {
+ ASSERT(signaling_thread() == talk_base::Thread::Current());
+ SignalScreencastWindowEvent(ssrc, we);
+}
void VideoChannel::OnMessage(talk_base::Message *pmsg) {
switch (pmsg->message_id) {
- case MSG_ADDSTREAM: {
- StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
- AddStream_w(data->ssrc1, data->ssrc2);
- break;
- }
case MSG_SETRENDERER: {
RenderMessageData* data = static_cast<RenderMessageData*>(pmsg->pdata);
SetRenderer_w(data->ssrc, data->renderer);
break;
}
+ case MSG_ADDSCREENCAST: {
+ ScreencastMessageData* data =
+ static_cast<ScreencastMessageData*>(pmsg->pdata);
+ AddScreencast_w(data->ssrc, data->window_id);
+ break;
+ }
+ case MSG_REMOVESCREENCAST: {
+ ScreencastMessageData* data =
+ static_cast<ScreencastMessageData*>(pmsg->pdata);
+ RemoveScreencast_w(data->ssrc);
+ break;
+ }
+ case MSG_SCREENCASTWINDOWEVENT: {
+ ScreencastEventData* data =
+ static_cast<ScreencastEventData*>(pmsg->pdata);
+ OnScreencastWindowEvent_s(data->ssrc, data->event);
+ delete data;
+ break;
+ }
case MSG_SENDINTRAFRAME:
SendIntraFrame_w();
break;
@@ -1248,6 +1583,12 @@
delete data;
break;
}
+ case MSG_HANDLEVIEWREQUEST: {
+ ViewRequestMessageData* data =
+ static_cast<ViewRequestMessageData*>(pmsg->pdata);
+ data->result = ApplyViewRequest_w(data->request);
+ break;
+ }
default:
BaseChannel::OnMessage(pmsg);
break;
@@ -1265,6 +1606,11 @@
SignalMediaMonitor(this, info);
}
+void VideoChannel::OnScreencastWindowEvent(uint32 ssrc,
+ talk_base::WindowEvent event) {
+ ScreencastEventData* pdata = new ScreencastEventData(ssrc, event);
+ signaling_thread()->Post(this, MSG_SCREENCASTWINDOWEVENT, pdata);
+}
void VideoChannel::OnVideoChannelError(uint32 ssrc,
VideoMediaChannel::Error error) {
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
index 324c35c..df0124f 100644
--- a/talk/session/phone/channel.h
+++ b/talk/session/phone/channel.h
@@ -35,6 +35,7 @@
#include "talk/base/criticalsection.h"
#include "talk/base/network.h"
#include "talk/base/sigslot.h"
+#include "talk/base/window.h"
#include "talk/p2p/client/socketmonitor.h"
#include "talk/p2p/base/session.h"
#include "talk/session/phone/audiomonitor.h"
@@ -42,38 +43,16 @@
#include "talk/session/phone/mediaengine.h"
#include "talk/session/phone/mediamonitor.h"
#include "talk/session/phone/rtcpmuxfilter.h"
+#include "talk/session/phone/screencastid.h"
+#include "talk/session/phone/ssrcmuxfilter.h"
+#include "talk/session/phone/streamparams.h"
#include "talk/session/phone/srtpfilter.h"
namespace cricket {
class MediaContentDescription;
struct CryptoParams;
-
-enum {
- MSG_ENABLE = 1,
- MSG_DISABLE = 2,
- MSG_MUTE = 3,
- MSG_UNMUTE = 4,
- MSG_SETREMOTECONTENT = 5,
- MSG_SETLOCALCONTENT = 6,
- MSG_EARLYMEDIATIMEOUT = 8,
- MSG_PRESSDTMF = 9,
- MSG_SETRENDERER = 10,
- MSG_ADDSTREAM = 11,
- MSG_REMOVESTREAM = 12,
- MSG_SETRINGBACKTONE = 13,
- MSG_PLAYRINGBACKTONE = 14,
- MSG_SETMAXSENDBANDWIDTH = 15,
- MSG_SETRTCPCNAME = 18,
- MSG_SENDINTRAFRAME = 19,
- MSG_REQUESTINTRAFRAME = 20,
- MSG_RTPPACKET = 22,
- MSG_RTCPPACKET = 23,
- MSG_CHANNEL_ERROR = 24,
- MSG_ENABLECPUADAPTATION = 25,
- MSG_DISABLECPUADAPTATION = 26,
- MSG_SCALEVOLUME = 27
-};
+struct ViewRequest;
// BaseChannel contains logic common to voice and video, including
// enable/mute, marshaling calls to a worker thread, and
@@ -102,7 +81,6 @@
bool secure() const { return srtp_filter_.IsActive(); }
// Channel control
- bool SetRtcpCName(const std::string& cname);
bool SetLocalContent(const MediaContentDescription* content,
ContentAction action);
bool SetRemoteContent(const MediaContentDescription* content,
@@ -113,7 +91,8 @@
bool Mute(bool mute);
// Multiplexing
- bool RemoveStream(uint32 ssrc);
+ bool AddRecvStream(const StreamParams& sp);
+ bool RemoveRecvStream(uint32 ssrc);
// Monitoring
void StartConnectionMonitor(int cms);
@@ -159,6 +138,15 @@
return !SignalRecvPacket.is_empty();
}
+ SsrcMuxFilter* ssrc_filter() { return &ssrc_filter_; }
+
+ const std::vector<StreamParams>& local_streams() const {
+ return local_streams_;
+ }
+ const std::vector<StreamParams>& remote_streams() const {
+ return remote_streams_;
+ }
+
protected:
MediaEngineInterface* media_engine() const { return media_engine_; }
virtual MediaChannel* media_channel() const { return media_channel_; }
@@ -199,7 +187,6 @@
// Setting the send codec based on the remote description.
void OnSessionState(BaseSession* session, BaseSession::State state);
- void OnRemoteDescriptionUpdate(BaseSession* session);
void EnableMedia_w();
void DisableMedia_w();
@@ -207,38 +194,24 @@
void UnmuteMedia_w();
void ChannelWritable_w();
void ChannelNotWritable_w();
-
- struct StreamMessageData : public talk_base::MessageData {
- StreamMessageData(uint32 s1, uint32 s2) : ssrc1(s1), ssrc2(s2) {}
- uint32 ssrc1;
- uint32 ssrc2;
- };
- virtual void RemoveStream_w(uint32 ssrc) = 0;
+ bool AddRecvStream_w(const StreamParams& sp);
+ bool RemoveRecvStream_w(uint32 ssrc);
virtual void ChangeState() = 0;
- struct SetRtcpCNameData : public talk_base::MessageData {
- explicit SetRtcpCNameData(const std::string& cname)
- : cname(cname), result(false) {}
- std::string cname;
- bool result;
- };
- bool SetRtcpCName_w(const std::string& cname);
-
- struct SetContentData : public talk_base::MessageData {
- SetContentData(const MediaContentDescription* content,
- ContentAction action)
- : content(content), action(action), result(false) {}
- const MediaContentDescription* content;
- ContentAction action;
- bool result;
- };
-
// Gets the content appropriate to the channel (audio or video).
virtual const MediaContentDescription* GetFirstContent(
const SessionDescription* sdesc) = 0;
+ bool UpdateLocalStreams_w(const std::vector<StreamParams>& streams,
+ ContentAction action);
+ bool UpdateRemoteStreams_w(const std::vector<StreamParams>& streams,
+ ContentAction action);
+ bool SetBaseLocalContent_w(const MediaContentDescription* content,
+ ContentAction action);
virtual bool SetLocalContent_w(const MediaContentDescription* content,
ContentAction action) = 0;
+ bool SetBaseRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action);
virtual bool SetRemoteContent_w(const MediaContentDescription* content,
ContentAction action) = 0;
@@ -246,11 +219,6 @@
ContentSource src);
bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
- struct SetBandwidthData : public talk_base::MessageData {
- explicit SetBandwidthData(int value) : value(value), result(false) {}
- int value;
- bool result;
- };
bool SetMaxSendBandwidth_w(int max_bandwidth);
// From MessageHandler
@@ -270,6 +238,8 @@
MediaEngineInterface *media_engine_;
BaseSession *session_;
MediaChannel *media_channel_;
+ std::vector<StreamParams> local_streams_;
+ std::vector<StreamParams> remote_streams_;
std::string content_name_;
bool rtcp_;
@@ -277,6 +247,7 @@
TransportChannel *rtcp_transport_channel_;
SrtpFilter srtp_filter_;
RtcpMuxFilter rtcp_mux_filter_;
+ SsrcMuxFilter ssrc_filter_;
talk_base::scoped_ptr<SocketMonitor> socket_monitor_;
bool enabled_;
bool writable_;
@@ -301,9 +272,6 @@
return static_cast<VoiceMediaChannel*>(BaseChannel::media_channel());
}
- // Add an incoming stream with the specified SSRC.
- bool AddStream(uint32 ssrc);
-
bool SetRingbackTone(const void* buf, int len);
void SetEarlyMedia(bool enable);
// This signal is emitted when we have gone a period of time without
@@ -314,6 +282,10 @@
bool PlayRingbackTone(uint32 ssrc, bool play, bool loop);
bool PressDTMF(int digit, bool playout);
bool SetOutputScaling(uint32 ssrc, double left, double right);
+ void set_mute_on_type(bool enable, int timeout) {
+ mute_on_type_ = enable;
+ mute_on_type_timeout_ = talk_base::_max(0, timeout);
+ }
// Monitoring functions
sigslot::signal2<VoiceChannel*, const std::vector<ConnectionInfo> &>
@@ -337,52 +309,9 @@
sigslot::signal3<VoiceChannel*, uint32, VoiceMediaChannel::Error>
SignalMediaError;
- private:
- struct SetRingbackToneMessageData : public talk_base::MessageData {
- SetRingbackToneMessageData(const void* b, int l)
- : buf(b),
- len(l),
- result(false) {
- }
- const void* buf;
- int len;
- bool result;
- };
- struct PlayRingbackToneMessageData : public talk_base::MessageData {
- PlayRingbackToneMessageData(uint32 s, bool p, bool l)
- : ssrc(s),
- play(p),
- loop(l),
- result(false) {
- }
- uint32 ssrc;
- bool play;
- bool loop;
- bool result;
- };
- struct DtmfMessageData : public talk_base::MessageData {
- DtmfMessageData(int d, bool p)
- : digit(d),
- playout(p),
- result(false) {
- }
- int digit;
- bool playout;
- bool result;
- };
- struct ScaleVolumeMessageData : public talk_base::MessageData {
- ScaleVolumeMessageData(uint32 s, double l, double r)
- : ssrc(s),
- left(l),
- right(r),
- result(false) {
- }
- uint32 ssrc;
- double left;
- double right;
- bool result;
- };
+ static const int kTypingBlackoutPeriod = 1500;
+ private:
// overrides from BaseChannel
virtual void OnChannelRead(TransportChannel* channel,
const char *data, size_t len);
@@ -394,9 +323,6 @@
virtual bool SetRemoteContent_w(const MediaContentDescription* content,
ContentAction action);
- void AddStream_w(uint32 ssrc);
- void RemoveStream_w(uint32 ssrc);
-
bool SetRingbackTone_w(const void* buf, int len);
bool PlayRingbackTone_w(uint32 ssrc, bool play, bool loop);
void HandleEarlyMediaTimeout();
@@ -417,6 +343,8 @@
bool received_media_;
talk_base::scoped_ptr<VoiceMediaMonitor> media_monitor_;
talk_base::scoped_ptr<AudioMonitor> audio_monitor_;
+ bool mute_on_type_;
+ int mute_on_type_timeout_;
};
// VideoChannel is a specialization for video.
@@ -434,11 +362,11 @@
return static_cast<VideoMediaChannel*>(BaseChannel::media_channel());
}
- // Add an incoming stream with the specified SSRC.
- bool AddStream(uint32 ssrc, uint32 voice_ssrc);
-
bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+ bool ApplyViewRequest(const ViewRequest& request);
+ bool AddScreencast(uint32 ssrc, const ScreencastId& id);
+ bool RemoveScreencast(uint32 ssrc);
sigslot::signal2<VideoChannel*, const std::vector<ConnectionInfo> &>
SignalConnectionMonitor;
@@ -446,6 +374,7 @@
void StartMediaMonitor(int cms);
void StopMediaMonitor();
sigslot::signal2<VideoChannel*, const VideoMediaInfo&> SignalMediaMonitor;
+ sigslot::signal2<uint32, talk_base::WindowEvent> SignalScreencastWindowEvent;
bool SendIntraFrame();
bool RequestIntraFrame();
@@ -464,9 +393,6 @@
virtual bool SetRemoteContent_w(const MediaContentDescription* content,
ContentAction action);
- void AddStream_w(uint32 ssrc, uint32 voice_ssrc);
- void RemoveStream_w(uint32 ssrc);
-
void SendIntraFrame_w() {
media_channel()->SendIntraFrame();
}
@@ -480,24 +406,24 @@
media_channel()->SetOptions(enable ? OPT_CPU_ADAPTATION : 0);
}
- struct RenderMessageData : public talk_base::MessageData {
- RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
- uint32 ssrc;
- VideoRenderer* renderer;
- };
-
-
+ bool ApplyViewRequest_w(const ViewRequest& request);
void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
+ void AddScreencast_w(uint32 ssrc, const ScreencastId&);
+ void RemoveScreencast_w(uint32 ssrc);
+ void OnScreencastWindowEvent_s(uint32 ssrc, talk_base::WindowEvent we);
virtual void OnMessage(talk_base::Message *pmsg);
virtual void OnConnectionMonitorUpdate(
SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
virtual void OnMediaMonitorUpdate(
VideoMediaChannel *media_channel, const VideoMediaInfo& info);
+ virtual void OnScreencastWindowEvent(uint32 ssrc,
+ talk_base::WindowEvent event);
void OnVideoChannelError(uint32 ssrc, VideoMediaChannel::Error error);
void OnSrtpError(uint32 ssrc, SrtpFilter::Mode mode, SrtpFilter::Error error);
+
VoiceChannel *voice_channel_;
VideoRenderer *renderer_;
talk_base::scoped_ptr<VideoMediaMonitor> media_monitor_;
diff --git a/talk/session/phone/channel_unittest.cc b/talk/session/phone/channel_unittest.cc
new file mode 100644
index 0000000..fa6c938
--- /dev/null
+++ b/talk/session/phone/channel_unittest.cc
@@ -0,0 +1,1916 @@
+// libjingle
+// Copyright 2009 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/signalthread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/mediamessages.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/mediarecorder.h"
+#include "talk/session/phone/rtpdump.h"
+
+using cricket::CA_OFFER;
+using cricket::CA_ANSWER;
+using cricket::CA_UPDATE;
+using cricket::StreamParams;
+
+static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 64000, 8000, 1, 0);
+static const cricket::AudioCodec kPcmaCodec(8, "PCMA", 64000, 8000, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 40000, 16000, 1, 0);
+static const cricket::VideoCodec kH264Codec(97, "H264", 640, 400, 30, 0);
+static const cricket::VideoCodec kH264SvcCodec(99, "H264-SVC", 320, 200, 15, 0);
+static const uint32 kSsrc1 = 0x1111;
+static const uint32 kSsrc2 = 0x2222;
+static const uint32 kSsrc3 = 0x3333;
+static const char kCName[] = "a@b.com";
+
+class VoiceTraits {
+ public:
+ typedef cricket::VoiceChannel Channel;
+ typedef cricket::FakeVoiceMediaChannel MediaChannel;
+ typedef cricket::AudioContentDescription Content;
+ typedef cricket::AudioCodec Codec;
+ typedef cricket::VoiceMediaInfo MediaInfo;
+};
+
+class VideoTraits {
+ public:
+ typedef cricket::VideoChannel Channel;
+ typedef cricket::FakeVideoMediaChannel MediaChannel;
+ typedef cricket::VideoContentDescription Content;
+ typedef cricket::VideoCodec Codec;
+ typedef cricket::VideoMediaInfo MediaInfo;
+};
+
+// Base class for Voice/VideoChannel tests
+template<class T>
+class ChannelTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ enum Flags { RTCP = 0x1, RTCP_MUX = 0x2, SECURE = 0x4, SSRC_MUX = 0x8 };
+ ChannelTest(const uint8* rtp_data, int rtp_len,
+ const uint8* rtcp_data, int rtcp_len)
+ : media_channel1_(NULL),
+ media_channel2_(NULL),
+ rtp_packet_(reinterpret_cast<const char*>(rtp_data), rtp_len),
+ rtcp_packet_(reinterpret_cast<const char*>(rtcp_data), rtcp_len),
+ media_info_callbacks1_(),
+ media_info_callbacks2_(),
+ ssrc_(0),
+ error_(T::MediaChannel::ERROR_NONE) {
+ }
+
+ void CreateChannels(int flags1, int flags2) {
+ CreateChannels(new typename T::MediaChannel(NULL),
+ new typename T::MediaChannel(NULL),
+ flags1, flags2, talk_base::Thread::Current());
+ }
+ void CreateChannels(int flags) {
+ CreateChannels(new typename T::MediaChannel(NULL),
+ new typename T::MediaChannel(NULL),
+ flags, talk_base::Thread::Current());
+ }
+ void CreateChannels(int flags1, int flags2,
+ talk_base::Thread* thread) {
+ CreateChannels(new typename T::MediaChannel(NULL),
+ new typename T::MediaChannel(NULL),
+ flags1, flags2, thread);
+ }
+ void CreateChannels(int flags,
+ talk_base::Thread* thread) {
+ CreateChannels(new typename T::MediaChannel(NULL),
+ new typename T::MediaChannel(NULL),
+ flags, thread);
+ }
+ void CreateChannels(
+ typename T::MediaChannel* ch1, typename T::MediaChannel* ch2,
+ int flags1, int flags2, talk_base::Thread* thread) {
+ media_channel1_ = ch1;
+ media_channel2_ = ch2;
+ channel1_.reset(CreateChannel(thread, &media_engine_, ch1, &session1_,
+ (flags1 & RTCP) != 0));
+ channel2_.reset(CreateChannel(thread, &media_engine_, ch2, &session2_,
+ (flags2 & RTCP) != 0));
+ channel1_->SignalMediaMonitor.connect(
+ this, &ChannelTest<T>::OnMediaMonitor);
+ channel2_->SignalMediaMonitor.connect(
+ this, &ChannelTest<T>::OnMediaMonitor);
+ channel1_->SignalMediaError.connect(
+ this, &ChannelTest<T>::OnMediaChannelError);
+ channel2_->SignalMediaError.connect(
+ this, &ChannelTest<T>::OnMediaChannelError);
+ CreateContent(flags1, kPcmuCodec, kH264Codec, &local_media_content1_);
+ CreateContent(flags2, kPcmuCodec, kH264Codec, &local_media_content2_);
+ CopyContent(local_media_content1_, &remote_media_content1_);
+ CopyContent(local_media_content2_, &remote_media_content2_);
+ // Add stream information (SSRC) to the local content but not to the remote
+ // content. This means that we per default know the SSRC of what we send but
+ // not what we receive.
+ AddLegacyStreamInContent(kSsrc1, flags1, &local_media_content1_);
+ AddLegacyStreamInContent(kSsrc2, flags2, &local_media_content2_);
+
+ // If SSRC_MUX is used we also need to know the SSRC of the incoming stream.
+ if (flags1 & SSRC_MUX) {
+ AddLegacyStreamInContent(kSsrc1, flags1, &remote_media_content1_);
+ }
+ if (flags2 & SSRC_MUX) {
+ AddLegacyStreamInContent(kSsrc2, flags2, &remote_media_content2_);
+ }
+ }
+
+ void CreateChannels(
+ typename T::MediaChannel* ch1, typename T::MediaChannel* ch2,
+ int flags, talk_base::Thread* thread) {
+ media_channel1_ = ch1;
+ media_channel2_ = ch2;
+ channel1_.reset(CreateChannel(thread, &media_engine_, ch1, &session1_,
+ (flags & RTCP) != 0));
+ channel2_.reset(CreateChannel(thread, &media_engine_, ch2, &session1_,
+ (flags & RTCP) != 0));
+ channel1_->SignalMediaMonitor.connect(
+ this, &ChannelTest<T>::OnMediaMonitor);
+ channel2_->SignalMediaMonitor.connect(
+ this, &ChannelTest<T>::OnMediaMonitor);
+ channel2_->SignalMediaError.connect(
+ this, &ChannelTest<T>::OnMediaChannelError);
+ CreateContent(flags, kPcmuCodec, kH264Codec, &local_media_content1_);
+ CreateContent(flags, kPcmuCodec, kH264Codec, &local_media_content2_);
+ CopyContent(local_media_content1_, &remote_media_content1_);
+ CopyContent(local_media_content2_, &remote_media_content2_);
+ // Add stream information (SSRC) to the local content but not to the remote
+ // content. This means that we per default know the SSRC of what we send but
+ // not what we receive.
+ AddLegacyStreamInContent(kSsrc1, flags, &local_media_content1_);
+ AddLegacyStreamInContent(kSsrc2, flags, &local_media_content2_);
+
+ // If SSRC_MUX is used we also need to know the SSRC of the incoming stream.
+ if (flags & SSRC_MUX) {
+ AddLegacyStreamInContent(kSsrc1, flags, &remote_media_content1_);
+ AddLegacyStreamInContent(kSsrc2, flags, &remote_media_content2_);
+ }
+ }
+ typename T::Channel* CreateChannel(talk_base::Thread* thread,
+ cricket::MediaEngineInterface* engine,
+ typename T::MediaChannel* ch,
+ cricket::BaseSession* session,
+ bool rtcp) {
+ typename T::Channel* channel = new typename T::Channel(
+ thread, engine, ch, session, cricket::CN_AUDIO, rtcp);
+ if (!channel->Init()) {
+ delete channel;
+ channel = NULL;
+ }
+ return channel;
+ }
+
+ bool SendInitiate() {
+ bool result = channel1_->SetLocalContent(&local_media_content1_, CA_OFFER);
+ if (result) {
+ channel1_->Enable(true);
+ result = channel2_->SetRemoteContent(&remote_media_content1_, CA_OFFER);
+ if (result) {
+ result = channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER);
+ if (result) {
+ session1_.Connect(&session2_);
+ }
+ }
+ }
+ return result;
+ }
+ bool SendAccept() {
+ channel2_->Enable(true);
+ return channel1_->SetRemoteContent(&remote_media_content2_, CA_ANSWER);
+ }
+ bool SendTerminate() {
+ channel1_.reset();
+ channel2_.reset();
+ return true;
+ }
+
+ bool AddStream1(int id) {
+ return channel1_->AddRecvStream(cricket::StreamParams::CreateLegacy(id));
+ }
+ bool RemoveStream1(int id) {
+ return channel1_->RemoveRecvStream(id);
+ }
+
+ cricket::FakeTransport* GetTransport1() {
+ return session1_.GetTransport(channel1_->content_name());
+ }
+ cricket::FakeTransport* GetTransport2() {
+ return session2_.GetTransport(channel2_->content_name());
+ }
+
+ bool SendRtp1() {
+ return media_channel1_->SendRtp(rtp_packet_.c_str(), rtp_packet_.size());
+ }
+ bool SendRtp2() {
+ return media_channel2_->SendRtp(rtp_packet_.c_str(), rtp_packet_.size());
+ }
+ bool SendRtcp1() {
+ return media_channel1_->SendRtcp(rtcp_packet_.c_str(), rtcp_packet_.size());
+ }
+ bool SendRtcp2() {
+ return media_channel2_->SendRtcp(rtcp_packet_.c_str(), rtcp_packet_.size());
+ }
+ // Methods to send custom data.
+ bool SendCustomRtp1(uint32 ssrc) {
+ std::string data(CreateRtpData(ssrc));
+ return media_channel1_->SendRtp(data.c_str(), data.size());
+ }
+ bool SendCustomRtp2(uint32 ssrc) {
+ std::string data(CreateRtpData(ssrc));
+ return media_channel2_->SendRtp(data.c_str(), data.size());
+ }
+ bool SendCustomRtcp1(uint32 ssrc) {
+ std::string data(CreateRtcpData(ssrc));
+ return media_channel1_->SendRtcp(data.c_str(), data.size());
+ }
+ bool SendCustomRtcp2(uint32 ssrc) {
+ std::string data(CreateRtcpData(ssrc));
+ return media_channel2_->SendRtcp(data.c_str(), data.size());
+ }
+ bool CheckRtp1() {
+ return media_channel1_->CheckRtp(rtp_packet_.c_str(), rtp_packet_.size());
+ }
+ bool CheckRtp2() {
+ return media_channel2_->CheckRtp(rtp_packet_.c_str(), rtp_packet_.size());
+ }
+ bool CheckRtcp1() {
+ return media_channel1_->CheckRtcp(rtcp_packet_.c_str(),
+ rtcp_packet_.size());
+ }
+ bool CheckRtcp2() {
+ return media_channel2_->CheckRtcp(rtcp_packet_.c_str(),
+ rtcp_packet_.size());
+ }
+ // Methods to check custom data.
+ bool CheckCustomRtp1(uint32 ssrc) {
+ std::string data(CreateRtpData(ssrc));
+ return media_channel1_->CheckRtp(data.c_str(), data.size());
+ }
+ bool CheckCustomRtp2(uint32 ssrc) {
+ std::string data(CreateRtpData(ssrc));
+ return media_channel2_->CheckRtp(data.c_str(), data.size());
+ }
+ bool CheckCustomRtcp1(uint32 ssrc) {
+ std::string data(CreateRtcpData(ssrc));
+ return media_channel1_->CheckRtcp(data.c_str(), data.size());
+ }
+ bool CheckCustomRtcp2(uint32 ssrc) {
+ std::string data(CreateRtcpData(ssrc));
+ return media_channel2_->CheckRtcp(data.c_str(), data.size());
+ }
+ std::string CreateRtpData(uint32 ssrc) {
+ std::string data(rtp_packet_);
+ // Set SSRC in the rtp packet copy.
+ talk_base::SetBE32(const_cast<char*>(data.c_str()) + 8, ssrc);
+ return data;
+ }
+ std::string CreateRtcpData(uint32 ssrc) {
+ std::string data(rtcp_packet_);
+ // Set SSRC in the rtcp packet copy.
+ talk_base::SetBE32(const_cast<char*>(data.c_str()) + 4, ssrc);
+ return data;
+ }
+
+ bool CheckNoRtp1() {
+ return media_channel1_->CheckNoRtp();
+ }
+ bool CheckNoRtp2() {
+ return media_channel2_->CheckNoRtp();
+ }
+ bool CheckNoRtcp1() {
+ return media_channel1_->CheckNoRtcp();
+ }
+ bool CheckNoRtcp2() {
+ return media_channel2_->CheckNoRtcp();
+ }
+
+ void CreateContent(int flags,
+ const cricket::AudioCodec& audio_codec,
+ const cricket::VideoCodec& video_codec,
+ typename T::Content* content) {
+ // overridden in specialized classes
+ }
+ void CopyContent(const typename T::Content& source,
+ typename T::Content* content) {
+ // overridden in specialized classes
+ }
+
+ class CallThread : public talk_base::SignalThread {
+ public:
+ typedef bool (ChannelTest<T>::*Method)();
+ CallThread(ChannelTest<T>* obj, Method method, bool* result)
+ : obj_(obj),
+ method_(method),
+ result_(result) {
+ *result = false;
+ }
+ virtual void DoWork() {
+ bool result = (*obj_.*method_)();
+ if (result_) {
+ *result_ = result;
+ }
+ }
+ private:
+ ChannelTest<T>* obj_;
+ Method method_;
+ bool* result_;
+ };
+ void CallOnThread(typename CallThread::Method method, bool* result) {
+ CallThread* thread = new CallThread(this, method, result);
+ thread->Start();
+ thread->Release();
+ }
+
+ void CallOnThreadAndWaitForDone(typename CallThread::Method method,
+ bool* result) {
+ CallThread* thread = new CallThread(this, method, result);
+ thread->Start();
+ thread->Destroy(true);
+ }
+
+ bool CodecMatches(const typename T::Codec& c1, const typename T::Codec& c2) {
+ return false; // overridden in specialized classes
+ }
+
+ void OnMediaMonitor(typename T::Channel* channel,
+ const typename T::MediaInfo& info) {
+ if (channel == channel1_.get()) {
+ media_info_callbacks1_++;
+ } else if (channel == channel2_.get()) {
+ media_info_callbacks2_++;
+ }
+ }
+
+ void OnMediaChannelError(typename T::Channel* channel,
+ uint32 ssrc,
+ typename T::MediaChannel::Error error) {
+ ssrc_ = ssrc;
+ error_ = error;
+ }
+
+ void AddLegacyStreamInContent(uint32 ssrc, int flags,
+ typename T::Content* content) {
+ // Base implementation.
+ }
+
+ // Tests that can be used by derived classes.
+
+ // Basic sanity check.
+ void TestInit() {
+ CreateChannels(0, 0);
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_FALSE(media_channel1_->playout());
+ EXPECT_TRUE(media_channel1_->codecs().empty());
+ EXPECT_TRUE(media_channel1_->recv_streams().empty());
+ EXPECT_TRUE(media_channel1_->rtp_packets().empty());
+ EXPECT_TRUE(media_channel1_->rtcp_packets().empty());
+ }
+
+ // Test that SetLocalContent and SetRemoteContent properly configure
+ // the codecs.
+ void TestSetContents() {
+ CreateChannels(0, 0);
+ typename T::Content content;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+ EXPECT_EQ(0U, media_channel1_->codecs().size());
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+ ASSERT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(CodecMatches(content.codecs()[0],
+ media_channel1_->codecs()[0]));
+ }
+
+ // Test that SetLocalContent and SetRemoteContent properly deals
+ // with an empty offer.
+ void TestSetContentsNullOffer() {
+ CreateChannels(0, 0);
+ typename T::Content content;
+ EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+ CreateContent(0, kPcmuCodec, kH264Codec, &content);
+ EXPECT_EQ(0U, media_channel1_->codecs().size());
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+ ASSERT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(CodecMatches(content.codecs()[0],
+ media_channel1_->codecs()[0]));
+ }
+
+ // Test that SetLocalContent and SetRemoteContent properly set RTCP
+ // mux.
+ void TestSetContentsRtcpMux() {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(channel1_->rtcp_transport_channel() != NULL);
+ EXPECT_TRUE(channel2_->rtcp_transport_channel() != NULL);
+ typename T::Content content;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content);
+ // Both sides agree on mux. Should no longer be a separate RTCP channel.
+ content.set_rtcp_mux(true);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+ EXPECT_TRUE(channel1_->rtcp_transport_channel() == NULL);
+ // Only initiator supports mux. Should still have a separate RTCP channel.
+ EXPECT_TRUE(channel2_->SetLocalContent(&content, CA_OFFER));
+ content.set_rtcp_mux(false);
+ EXPECT_TRUE(channel2_->SetRemoteContent(&content, CA_ANSWER));
+ EXPECT_TRUE(channel2_->rtcp_transport_channel() != NULL);
+ }
+
+ // Test that SetRemoteContent properly deals with a content update.
+ void TestSetRemoteContentUpdate() {
+ CreateChannels(0, 0);
+ typename T::Content content;
+ CreateContent(RTCP | RTCP_MUX | SECURE, kPcmuCodec, kH264Codec, &content);
+ EXPECT_EQ(0U, media_channel1_->codecs().size());
+ EXPECT_TRUE(channel1_->SetLocalContent(&content, CA_OFFER));
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content, CA_ANSWER));
+ ASSERT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(CodecMatches(content.codecs()[0],
+ media_channel1_->codecs()[0]));
+ // Now update with other codecs.
+ typename T::Content update_content;
+ update_content.set_partial(true);
+ CreateContent(0, kIsacCodec, kH264SvcCodec, &update_content);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&update_content, CA_UPDATE));
+ ASSERT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
+ media_channel1_->codecs()[0]));
+ // Now update without any codecs. This is ignored.
+ typename T::Content empty_content;
+ empty_content.set_partial(true);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&empty_content, CA_UPDATE));
+ ASSERT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
+ media_channel1_->codecs()[0]));
+ }
+
+ // Test that Add/RemoveStream properly forward to the media channel.
+ void TestStreams() {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(AddStream1(1));
+ EXPECT_TRUE(AddStream1(2));
+ EXPECT_EQ(2U, media_channel1_->recv_streams().size());
+ EXPECT_TRUE(RemoveStream1(2));
+ EXPECT_EQ(1U, media_channel1_->recv_streams().size());
+ EXPECT_TRUE(RemoveStream1(1));
+ EXPECT_EQ(0U, media_channel1_->recv_streams().size());
+ }
+
+ // Test that SetLocalContent properly handles adding and removing StreamParams
+ // to the local content description.
+ // This test uses the CA_UPDATE action that don't require a full
+ // MediaContentDescription to do an update.
+ void TestUpdateStreamsInLocalContent() {
+ cricket::StreamParams stream1;
+ stream1.name = "Stream1";
+ stream1.nick = "1";
+ stream1.ssrcs.push_back(kSsrc1);
+ stream1.cname = "stream1_cname";
+
+ cricket::StreamParams stream2;
+ stream2.name = "Stream2";
+ stream2.nick = "2";
+ stream2.ssrcs.push_back(kSsrc2);
+ stream2.cname = "stream2_cname";
+
+ cricket::StreamParams stream3;
+ stream3.name = "Stream3";
+ stream3.nick = "3";
+ stream3.ssrcs.push_back(kSsrc3);
+ stream3.cname = "stream3_cname";
+
+ CreateChannels(0, 0);
+ typename T::Content content1;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+ content1.AddStream(stream1);
+ EXPECT_EQ(0u, media_channel1_->send_streams().size());
+ EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER));
+
+ ASSERT_EQ(1u, media_channel1_->send_streams().size());
+ EXPECT_EQ(stream1, media_channel1_->send_streams()[0]);
+
+ // Update the local streams by adding another sending stream.
+ // Use a partial updated session description.
+ typename T::Content content2;
+ content2.AddStream(stream2);
+ content2.AddStream(stream3);
+ content2.set_partial(true);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content2, CA_UPDATE));
+ ASSERT_EQ(3u, media_channel1_->send_streams().size());
+ EXPECT_EQ(stream1, media_channel1_->send_streams()[0]);
+ EXPECT_EQ(stream2, media_channel1_->send_streams()[1]);
+ EXPECT_EQ(stream3, media_channel1_->send_streams()[2]);
+
+ // Update the local streams by removing the first sending stream.
+ // This is done by removing all SSRCS for this particular stream.
+ typename T::Content content3;
+ stream1.ssrcs.clear();
+ content3.AddStream(stream1);
+ content3.set_partial(true);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content3, CA_UPDATE));
+ ASSERT_EQ(2u, media_channel1_->send_streams().size());
+ EXPECT_EQ(stream2, media_channel1_->send_streams()[0]);
+ EXPECT_EQ(stream3, media_channel1_->send_streams()[1]);
+
+ // Update the local streams with a stream that does not change.
+ // THe update is ignored.
+ typename T::Content content4;
+ content4.AddStream(stream2);
+ content4.set_partial(true);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content4, CA_UPDATE));
+ ASSERT_EQ(2u, media_channel1_->send_streams().size());
+ EXPECT_EQ(stream2, media_channel1_->send_streams()[0]);
+ EXPECT_EQ(stream3, media_channel1_->send_streams()[1]);
+ }
+
+ // Test that SetRemoteContent properly handles adding and removing
+ // StreamParams to the remote content description.
+ // This test uses the CA_UPDATE action that don't require a full
+ // MediaContentDescription to do an update.
+ void TestUpdateStreamsInRemoteContent() {
+ cricket::StreamParams stream1;
+ stream1.name = "Stream1";
+ stream1.nick = "1";
+ stream1.ssrcs.push_back(kSsrc1);
+ stream1.cname = "stream1_cname";
+
+ cricket::StreamParams stream2;
+ stream2.name = "Stream2";
+ stream2.nick = "2";
+ stream2.ssrcs.push_back(kSsrc2);
+ stream2.cname = "stream2_cname";
+
+ cricket::StreamParams stream3;
+ stream3.name = "Stream3";
+ stream3.nick = "3";
+ stream3.ssrcs.push_back(kSsrc3);
+ stream3.cname = "stream3_cname";
+
+ CreateChannels(0, 0);
+ typename T::Content content1;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+ content1.AddStream(stream1);
+ EXPECT_EQ(0u, media_channel1_->recv_streams().size());
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content1, CA_OFFER));
+
+ ASSERT_EQ(1u, media_channel1_->codecs().size());
+ ASSERT_EQ(1u, media_channel1_->recv_streams().size());
+ EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
+
+ // Update the remote streams by adding another sending stream.
+ // Use a partial updated session description.
+ typename T::Content content2;
+ content2.AddStream(stream2);
+ content2.AddStream(stream3);
+ content2.set_partial(true);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_UPDATE));
+ ASSERT_EQ(3u, media_channel1_->recv_streams().size());
+ EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
+ EXPECT_EQ(stream2, media_channel1_->recv_streams()[1]);
+ EXPECT_EQ(stream3, media_channel1_->recv_streams()[2]);
+
+ // Update the remote streams by removing the first stream.
+ // This is done by removing all SSRCS for this particular stream.
+ typename T::Content content3;
+ stream1.ssrcs.clear();
+ content3.AddStream(stream1);
+ content3.set_partial(true);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_UPDATE));
+ ASSERT_EQ(2u, media_channel1_->recv_streams().size());
+ EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+ EXPECT_EQ(stream3, media_channel1_->recv_streams()[1]);
+
+ // Update the remote streams with a stream that does not change.
+ // The update is ignored.
+ typename T::Content content4;
+ content4.AddStream(stream2);
+ content4.set_partial(true);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content4, CA_UPDATE));
+ ASSERT_EQ(2u, media_channel1_->recv_streams().size());
+ EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+ EXPECT_EQ(stream3, media_channel1_->recv_streams()[1]);
+ }
+
+ // Test that SetLocalContent and SetRemoteContent properly
+ // handles adding and removing StreamParams when the action is a full
+ // CA_OFFER / CA_ANSWER.
+ void TestChangeStreamParamsInContent() {
+ cricket::StreamParams stream1;
+ stream1.name = "Stream1";
+ stream1.ssrcs.push_back(kSsrc1);
+ stream1.cname = "stream1_cname";
+
+ cricket::StreamParams stream2;
+ stream2.name = "Stream2";
+ stream2.ssrcs.push_back(kSsrc2);
+ stream2.cname = "stream2_cname";
+
+ // Setup a call where channel 1 send |stream1| to channel 2.
+ CreateChannels(0, 0);
+ typename T::Content content1;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content1);
+ content1.AddStream(stream1);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content1, CA_OFFER));
+ EXPECT_TRUE(channel1_->Enable(true));
+ EXPECT_EQ(1u, media_channel1_->send_streams().size());
+
+ EXPECT_TRUE(channel2_->SetRemoteContent(&content1, CA_OFFER));
+ EXPECT_EQ(1u, media_channel2_->recv_streams().size());
+ session1_.Connect(&session2_);
+
+ // Channel 2 do not send anything.
+ typename T::Content content2;
+ CreateContent(0, kPcmuCodec, kH264Codec, &content2);
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_ANSWER));
+ EXPECT_EQ(0u, media_channel1_->recv_streams().size());
+ EXPECT_TRUE(channel2_->SetLocalContent(&content2, CA_ANSWER));
+ EXPECT_TRUE(channel2_->Enable(true));
+ EXPECT_EQ(0u, media_channel2_->send_streams().size());
+
+ EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+ EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+
+ // Let channel 2 update the content by sending |stream2| and enable SRTP.
+ typename T::Content content3;
+ CreateContent(SECURE, kPcmuCodec, kH264Codec, &content3);
+ content3.AddStream(stream2);
+ EXPECT_TRUE(channel2_->SetLocalContent(&content3, CA_OFFER));
+ ASSERT_EQ(1u, media_channel2_->send_streams().size());
+ EXPECT_EQ(stream2, media_channel2_->send_streams()[0]);
+
+ EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_OFFER));
+ ASSERT_EQ(1u, media_channel1_->recv_streams().size());
+ EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
+
+ // Channel 1 replies but stop sending stream1.
+ typename T::Content content4;
+ CreateContent(SECURE, kPcmuCodec, kH264Codec, &content4);
+ EXPECT_TRUE(channel1_->SetLocalContent(&content4, CA_ANSWER));
+ EXPECT_EQ(0u, media_channel1_->send_streams().size());
+
+ EXPECT_TRUE(channel2_->SetRemoteContent(&content4, CA_ANSWER));
+ EXPECT_EQ(0u, media_channel2_->recv_streams().size());
+
+ EXPECT_TRUE(channel1_->secure());
+ EXPECT_TRUE(channel2_->secure());
+ EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+ EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+ }
+
+ // Test that we only start playout and sending at the right times.
+ void TestPlayoutAndSendingStates() {
+ CreateChannels(0, 0);
+ EXPECT_FALSE(media_channel1_->playout());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_FALSE(media_channel2_->playout());
+ EXPECT_FALSE(media_channel2_->sending());
+ EXPECT_TRUE(channel1_->Enable(true));
+ EXPECT_FALSE(media_channel1_->playout());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_, CA_OFFER));
+ EXPECT_TRUE(media_channel1_->playout());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_TRUE(channel2_->SetRemoteContent(&local_media_content1_, CA_OFFER));
+ EXPECT_FALSE(media_channel2_->playout());
+ EXPECT_FALSE(media_channel2_->sending());
+ EXPECT_TRUE(channel2_->SetLocalContent(&local_media_content2_, CA_ANSWER));
+ EXPECT_FALSE(media_channel2_->playout());
+ EXPECT_FALSE(media_channel2_->sending());
+ session1_.Connect(&session2_);
+ EXPECT_TRUE(media_channel1_->playout());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_FALSE(media_channel2_->playout());
+ EXPECT_FALSE(media_channel2_->sending());
+ EXPECT_TRUE(channel2_->Enable(true));
+ EXPECT_TRUE(media_channel2_->playout());
+ EXPECT_TRUE(media_channel2_->sending());
+ EXPECT_TRUE(channel1_->SetRemoteContent(&local_media_content2_, CA_ANSWER));
+ EXPECT_TRUE(media_channel1_->playout());
+ EXPECT_TRUE(media_channel1_->sending());
+ }
+
+ // Test setting up a call.
+ void TestCallSetup() {
+ CreateChannels(0, 0);
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(media_channel1_->playout());
+ EXPECT_FALSE(media_channel1_->sending());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_TRUE(media_channel1_->sending());
+ EXPECT_EQ(1U, media_channel1_->codecs().size());
+ EXPECT_TRUE(media_channel2_->playout());
+ EXPECT_TRUE(media_channel2_->sending());
+ EXPECT_EQ(1U, media_channel2_->codecs().size());
+ }
+
+ // Test that we don't crash if packets are sent during call teardown
+ // when RTCP mux is enabled. This is a regression test against a specific
+ // race condition that would only occur when a RTCP packet was sent during
+ // teardown of a channel on which RTCP mux was enabled.
+ void TestCallTeardownRtcpMux() {
+ class LastWordMediaChannel : public T::MediaChannel {
+ public:
+ LastWordMediaChannel() : T::MediaChannel(NULL) {}
+ ~LastWordMediaChannel() {
+ T::MediaChannel::SendRtp(kPcmuFrame, sizeof(kPcmuFrame));
+ T::MediaChannel::SendRtcp(kRtcpReport, sizeof(kRtcpReport));
+ }
+ };
+ CreateChannels(new LastWordMediaChannel(), new LastWordMediaChannel(),
+ RTCP | RTCP_MUX, RTCP | RTCP_MUX,
+ talk_base::Thread::Current());
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_TRUE(SendTerminate());
+ }
+
+ // Send voice RTP data to the other side and ensure it gets there.
+ void SendRtpToRtp() {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ }
+
+ // Check that RTCP is not transmitted if both sides don't support RTCP.
+ void SendNoRtcpToNoRtcp() {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_FALSE(SendRtcp1());
+ EXPECT_FALSE(SendRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTCP is not transmitted if the callee doesn't support RTCP.
+ void SendNoRtcpToRtcp() {
+ CreateChannels(0, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+ EXPECT_FALSE(SendRtcp1());
+ EXPECT_FALSE(SendRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTCP is not transmitted if the caller doesn't support RTCP.
+ void SendRtcpToNoRtcp() {
+ CreateChannels(RTCP, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_FALSE(SendRtcp1());
+ EXPECT_FALSE(SendRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTCP is transmitted if both sides support RTCP.
+ void SendRtcpToRtcp() {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTCP is transmitted if only the initiator supports mux.
+ void SendRtcpMuxToRtcp() {
+ CreateChannels(RTCP | RTCP_MUX, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTP and RTCP are transmitted ok when both sides support mux.
+ void SendRtcpMuxToRtcpMux() {
+ CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Check that RTCP data sent by the initiator before the accept is not muxed.
+ void SendEarlyRtcpMuxToRtcp() {
+ CreateChannels(RTCP | RTCP_MUX, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+
+ // RTCP can be sent before the call is accepted, if the transport is ready.
+ // It should not be muxed though, as the remote side doesn't support mux.
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckRtcp2());
+
+ // Send RTCP packet from callee and verify that it is received.
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckRtcp1());
+
+ // Complete call setup and ensure everything is still OK.
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtcp1());
+ }
+
+
+ // Check that RTCP data is not muxed until both sides have enabled muxing,
+ // but that we properly demux before we get the accept message, since there
+ // is a race between RTP data and the jingle accept.
+ void SendEarlyRtcpMuxToRtcpMux() {
+ CreateChannels(RTCP | RTCP_MUX, RTCP | RTCP_MUX);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+
+ // RTCP can't be sent yet, since the RTCP transport isn't writable, and
+ // we haven't yet received the accept that says we should mux.
+ EXPECT_FALSE(SendRtcp1());
+
+ // Send muxed RTCP packet from callee and verify that it is received.
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckRtcp1());
+
+ // Complete call setup and ensure everything is still OK.
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtcp1());
+ }
+
+ // Test that we properly send SRTP with RTCP in both directions.
+ void SendSrtpToSrtp() {
+ CreateChannels(RTCP | SECURE, RTCP | SECURE);
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_FALSE(channel2_->secure());
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_TRUE(channel1_->secure());
+ EXPECT_TRUE(channel2_->secure());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Test that we properly handling SRTP negotiating down to RTP.
+ void SendSrtpToRtp() {
+ CreateChannels(RTCP | SECURE, RTCP);
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_FALSE(channel2_->secure());
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_FALSE(channel2_->secure());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Test that we properly send SRTP with RTCP mux in both directions.
+ void SendSrtcpMux() {
+ CreateChannels(RTCP | RTCP_MUX | SECURE, RTCP | RTCP_MUX | SECURE);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(SendRtcp1());
+ EXPECT_TRUE(SendRtcp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckRtcp1());
+ EXPECT_TRUE(CheckRtcp2());
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Test that we properly send RTP without SRTP from a thread.
+ void SendRtpToRtpOnThread() {
+ bool sent_rtp1, sent_rtp2, sent_rtcp1, sent_rtcp2;
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ CallOnThread(&ChannelTest<T>::SendRtp1, &sent_rtp1);
+ CallOnThread(&ChannelTest<T>::SendRtp2, &sent_rtp2);
+ CallOnThread(&ChannelTest<T>::SendRtcp1, &sent_rtcp1);
+ CallOnThread(&ChannelTest<T>::SendRtcp2, &sent_rtcp2);
+ EXPECT_TRUE_WAIT(CheckRtp1(), 1000);
+ EXPECT_TRUE_WAIT(CheckRtp2(), 1000);
+ EXPECT_TRUE_WAIT(sent_rtp1, 1000);
+ EXPECT_TRUE_WAIT(sent_rtp2, 1000);
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE_WAIT(CheckRtcp1(), 1000);
+ EXPECT_TRUE_WAIT(CheckRtcp2(), 1000);
+ EXPECT_TRUE_WAIT(sent_rtcp1, 1000);
+ EXPECT_TRUE_WAIT(sent_rtcp2, 1000);
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Test that we properly send SRTP with RTCP from a thread.
+ void SendSrtpToSrtpOnThread() {
+ bool sent_rtp1, sent_rtp2, sent_rtcp1, sent_rtcp2;
+ CreateChannels(RTCP | SECURE, RTCP | SECURE);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ CallOnThread(&ChannelTest<T>::SendRtp1, &sent_rtp1);
+ CallOnThread(&ChannelTest<T>::SendRtp2, &sent_rtp2);
+ CallOnThread(&ChannelTest<T>::SendRtcp1, &sent_rtcp1);
+ CallOnThread(&ChannelTest<T>::SendRtcp2, &sent_rtcp2);
+ EXPECT_TRUE_WAIT(CheckRtp1(), 1000);
+ EXPECT_TRUE_WAIT(CheckRtp2(), 1000);
+ EXPECT_TRUE_WAIT(sent_rtp1, 1000);
+ EXPECT_TRUE_WAIT(sent_rtp2, 1000);
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE_WAIT(CheckRtcp1(), 1000);
+ EXPECT_TRUE_WAIT(CheckRtcp2(), 1000);
+ EXPECT_TRUE_WAIT(sent_rtcp1, 1000);
+ EXPECT_TRUE_WAIT(sent_rtcp2, 1000);
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ // Test that the mediachannel retains its sending state after the transport
+ // becomes non-writable.
+ void SendWithWritabilityLoss() {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+
+ GetTransport1()->SetDestination(NULL);
+ EXPECT_TRUE(media_channel1_->sending());
+ EXPECT_FALSE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+
+ GetTransport1()->SetDestination(GetTransport2());
+ EXPECT_TRUE(media_channel1_->sending());
+ EXPECT_TRUE(SendRtp1());
+ EXPECT_TRUE(SendRtp2());
+ EXPECT_TRUE(CheckRtp1());
+ EXPECT_TRUE(CheckRtp2());
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckNoRtp2());
+ }
+
+ void SendSsrcMuxToSsrcMuxWithRtcpMux() {
+ CreateChannels(SSRC_MUX | RTCP | RTCP_MUX, SSRC_MUX | RTCP | RTCP_MUX);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(1U, GetTransport1()->channels().size());
+ EXPECT_EQ(1U, GetTransport2()->channels().size());
+ EXPECT_TRUE(channel1_->ssrc_filter()->IsActive());
+ // channel1 - should have media_content2 as remote. i.e. kSsrc2
+ EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2));
+ EXPECT_TRUE(channel2_->ssrc_filter()->IsActive());
+ // channel2 - should have media_content1 as remote. i.e. kSsrc1
+ EXPECT_TRUE(channel2_->ssrc_filter()->FindStream(kSsrc1));
+ EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+ EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+ EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+ EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+ EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+ EXPECT_TRUE(CheckNoRtp1());
+ EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+ EXPECT_TRUE(CheckNoRtp2());
+ EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+ EXPECT_TRUE(CheckNoRtcp1());
+ EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+ EXPECT_TRUE(CheckNoRtcp2());
+ }
+
+ void SendSsrcMuxToSsrcMux() {
+ CreateChannels(SSRC_MUX | RTCP, SSRC_MUX | RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+ EXPECT_TRUE(channel1_->ssrc_filter()->IsActive());
+ // channel1 - should have media_content2 as remote. i.e. kSsrc2
+ EXPECT_TRUE(channel1_->ssrc_filter()->FindStream(kSsrc2));
+ EXPECT_TRUE(channel2_->ssrc_filter()->IsActive());
+ // channel2 - should have media_content1 as remote. i.e. kSsrc1
+ EXPECT_TRUE(SendCustomRtp1(kSsrc1));
+ EXPECT_TRUE(SendCustomRtp2(kSsrc2));
+ EXPECT_TRUE(SendCustomRtcp1(kSsrc1));
+ EXPECT_TRUE(SendCustomRtcp2(kSsrc2));
+ EXPECT_TRUE(CheckCustomRtp1(kSsrc2));
+ EXPECT_FALSE(CheckCustomRtp1(kSsrc1));
+ EXPECT_TRUE(CheckCustomRtp2(kSsrc1));
+ EXPECT_FALSE(CheckCustomRtp2(kSsrc2));
+ EXPECT_TRUE(CheckCustomRtcp1(kSsrc2));
+ EXPECT_FALSE(CheckCustomRtcp1(kSsrc1));
+ EXPECT_TRUE(CheckCustomRtcp2(kSsrc1));
+ EXPECT_FALSE(CheckCustomRtcp2(kSsrc2));
+ }
+
+ // Test that the media monitor can be run and gives timely callbacks.
+ void TestMediaMonitor() {
+ static const int kTimeout = 500;
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ channel1_->StartMediaMonitor(100);
+ channel2_->StartMediaMonitor(100);
+ // Ensure we get callbacks and stop.
+ EXPECT_TRUE_WAIT(media_info_callbacks1_ > 0, kTimeout);
+ EXPECT_TRUE_WAIT(media_info_callbacks2_ > 0, kTimeout);
+ channel1_->StopMediaMonitor();
+ channel2_->StopMediaMonitor();
+ // Ensure a restart of a stopped monitor works.
+ channel1_->StartMediaMonitor(100);
+ EXPECT_TRUE_WAIT(media_info_callbacks1_ > 0, kTimeout);
+ channel1_->StopMediaMonitor();
+ // Ensure stopping a stopped monitor is OK.
+ channel1_->StopMediaMonitor();
+ }
+
+ void TestMediaSinks() {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_FALSE(channel1_->HasSendSinks());
+ EXPECT_FALSE(channel1_->HasRecvSinks());
+
+ talk_base::Pathname path;
+ EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetFilename("sink-test.rtpdump");
+ talk_base::scoped_ptr<cricket::RtpDumpSink> sink(
+ new cricket::RtpDumpSink(path.pathname()));
+ sink->set_packet_filter(cricket::PF_ALL);
+ EXPECT_TRUE(sink->Enable(true));
+ channel1_->RegisterSendSink(sink.get(), &cricket::RtpDumpSink::OnPacket);
+ EXPECT_TRUE(channel1_->HasSendSinks());
+ EXPECT_FALSE(channel1_->HasRecvSinks());
+ // The first packet is recorded with header + data.
+ EXPECT_TRUE(SendRtp1());
+ // The second packet is recorded with header only.
+ sink->set_packet_filter(cricket::PF_RTPHEADER);
+ EXPECT_TRUE(SendRtp1());
+ // The third packet is not recorded since sink is disabled.
+ EXPECT_TRUE(sink->Enable(false));
+ EXPECT_TRUE(SendRtp1());
+ // The fourth packet is not recorded since sink is unregistered.
+ EXPECT_TRUE(sink->Enable(true));
+ channel1_->UnregisterSendSink(sink.get());
+ EXPECT_TRUE(SendRtp1());
+ sink.reset(); // This will close the file.
+
+ // Read the recorded file and verify two packets.
+ talk_base::scoped_ptr<talk_base::StreamInterface> stream(
+ talk_base::Filesystem::OpenFile(path, "rb"));
+
+ cricket::RtpDumpReader reader(stream.get());
+ cricket::RtpDumpPacket packet;
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+ std::string read_packet(reinterpret_cast<const char*>(&packet.data[0]),
+ packet.data.size());
+ EXPECT_EQ(rtp_packet_, read_packet);
+
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+ size_t len = 0;
+ packet.GetRtpHeaderLen(&len);
+ EXPECT_EQ(len, packet.data.size());
+ EXPECT_EQ(0, memcmp(&packet.data[0], rtp_packet_.c_str(), len));
+
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+ // Delete the file for media recording.
+ stream.reset();
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(path));
+ }
+
+ void TestSetContentFailure() {
+ CreateChannels(0, 0);
+ typename T::Content content;
+ cricket::SessionDescription* sdesc_loc = new cricket::SessionDescription();
+ cricket::SessionDescription* sdesc_rem = new cricket::SessionDescription();
+
+ // Set up the session description.
+ CreateContent(0, kPcmuCodec, kH264Codec, &content);
+ sdesc_loc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ new cricket::AudioContentDescription());
+ sdesc_loc->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+ new cricket::VideoContentDescription());
+ EXPECT_TRUE(session1_.set_local_description(sdesc_loc));
+ sdesc_rem->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
+ new cricket::AudioContentDescription());
+ sdesc_rem->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP,
+ new cricket::VideoContentDescription());
+ EXPECT_TRUE(session1_.set_remote_description(sdesc_rem));
+
+ // Test failures in SetLocalContent.
+ media_channel1_->set_fail_set_recv_codecs(true);
+ session1_.SetError(cricket::BaseSession::ERROR_NONE);
+ session1_.SignalState(&session1_, cricket::Session::STATE_SENTINITIATE);
+ EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+ media_channel1_->set_fail_set_recv_codecs(true);
+ session1_.SetError(cricket::BaseSession::ERROR_NONE);
+ session1_.SignalState(&session1_, cricket::Session::STATE_SENTACCEPT);
+ EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+
+ // Test failures in SetRemoteContent.
+ media_channel1_->set_fail_set_send_codecs(true);
+ session1_.SetError(cricket::BaseSession::ERROR_NONE);
+ session1_.SignalState(&session1_, cricket::Session::STATE_RECEIVEDINITIATE);
+ EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+ media_channel1_->set_fail_set_send_codecs(true);
+ session1_.SetError(cricket::BaseSession::ERROR_NONE);
+ session1_.SignalState(&session1_, cricket::Session::STATE_RECEIVEDACCEPT);
+ EXPECT_EQ(cricket::BaseSession::ERROR_CONTENT, session1_.error());
+ }
+
+ void TestFlushRtcp() {
+ bool send_rtcp1;
+
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(2U, GetTransport1()->channels().size());
+ EXPECT_EQ(2U, GetTransport2()->channels().size());
+
+ // Send RTCP1 from a different thread.
+ CallOnThreadAndWaitForDone(&ChannelTest<T>::SendRtcp1, &send_rtcp1);
+ EXPECT_TRUE(send_rtcp1);
+ // The sending message is only posted. channel2_ should be empty.
+ EXPECT_TRUE(CheckNoRtcp2());
+
+ // When channel1_ is deleted, the RTCP packet should be sent out to
+ // channel2_.
+ channel1_.reset();
+ EXPECT_TRUE(CheckRtcp2());
+ }
+
+ void TestChangeStateError() {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ media_channel2_->set_fail_set_send(true);
+ EXPECT_TRUE(channel2_->Enable(true));
+ EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED,
+ error_);
+ }
+
+ void TestSrtpError() {
+ static const unsigned char kBadPacket[] = {
+ 0x90, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+ CreateChannels(RTCP | SECURE, RTCP | SECURE);
+ EXPECT_FALSE(channel1_->secure());
+ EXPECT_FALSE(channel2_->secure());
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_TRUE(channel1_->secure());
+ EXPECT_TRUE(channel2_->secure());
+ channel2_->set_srtp_signal_silent_time(200);
+
+ // Testing failures in sending packets.
+ EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+ // The first failure will trigger an error.
+ EXPECT_EQ_WAIT(T::MediaChannel::ERROR_REC_SRTP_ERROR, error_, 500);
+ error_ = T::MediaChannel::ERROR_NONE;
+ // The next 1 sec failures will not trigger an error.
+ EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+ // Wait for a while to ensure no message comes in.
+ talk_base::Thread::Current()->ProcessMessages(210);
+ EXPECT_EQ(T::MediaChannel::ERROR_NONE, error_);
+ // The error will be triggered again.
+ EXPECT_FALSE(media_channel2_->SendRtp(kBadPacket, sizeof(kBadPacket)));
+ EXPECT_EQ_WAIT(T::MediaChannel::ERROR_REC_SRTP_ERROR, error_, 500);
+
+ // Testing failures in receiving packets.
+ error_ = T::MediaChannel::ERROR_NONE;
+ cricket::TransportChannel* transport_channel =
+ channel2_->transport_channel();
+ transport_channel->SignalReadPacket(
+ transport_channel, reinterpret_cast<const char*>(kBadPacket),
+ sizeof(kBadPacket));
+ EXPECT_EQ_WAIT(T::MediaChannel::ERROR_PLAY_SRTP_AUTH_FAILED, error_, 500);
+ }
+
+ protected:
+ cricket::FakeSession session1_;
+ cricket::FakeSession session2_;
+ cricket::FakeMediaEngine media_engine_;
+ // The media channels are owned by the voice channel objects below.
+ typename T::MediaChannel* media_channel1_;
+ typename T::MediaChannel* media_channel2_;
+ talk_base::scoped_ptr<typename T::Channel> channel1_;
+ talk_base::scoped_ptr<typename T::Channel> channel2_;
+ typename T::Content local_media_content1_;
+ typename T::Content local_media_content2_;
+ typename T::Content remote_media_content1_;
+ typename T::Content remote_media_content2_;
+ // The RTP and RTCP packets to send in the tests.
+ std::string rtp_packet_;
+ std::string rtcp_packet_;
+ int media_info_callbacks1_;
+ int media_info_callbacks2_;
+ uint32 ssrc_;
+ typename T::MediaChannel::Error error_;
+};
+
+
+template<>
+void ChannelTest<VoiceTraits>::CreateContent(
+ int flags,
+ const cricket::AudioCodec& audio_codec,
+ const cricket::VideoCodec& video_codec,
+ cricket::AudioContentDescription* audio) {
+ audio->AddCodec(audio_codec);
+ audio->set_rtcp_mux((flags & RTCP_MUX) != 0);
+ if (flags & SECURE) {
+ audio->AddCrypto(cricket::CryptoParams(
+ 1, cricket::CS_AES_CM_128_HMAC_SHA1_32,
+ "inline:" + talk_base::CreateRandomString(40), ""));
+ }
+}
+
+template<>
+void ChannelTest<VoiceTraits>::CopyContent(
+ const cricket::AudioContentDescription& source,
+ cricket::AudioContentDescription* audio) {
+ *audio = source;
+}
+
+template<>
+bool ChannelTest<VoiceTraits>::CodecMatches(const cricket::AudioCodec& c1,
+ const cricket::AudioCodec& c2) {
+ return c1.name == c2.name && c1.clockrate == c2.clockrate &&
+ c1.bitrate == c2.bitrate && c1.channels == c2.channels;
+}
+
+template<>
+void ChannelTest<VoiceTraits>::AddLegacyStreamInContent(
+ uint32 ssrc, int flags, cricket::AudioContentDescription* audio) {
+ audio->AddLegacyStream(ssrc);
+}
+
+class VoiceChannelTest
+ : public ChannelTest<VoiceTraits> {
+ public:
+ typedef ChannelTest<VoiceTraits>
+ Base;
+ VoiceChannelTest() : Base(kPcmuFrame, sizeof(kPcmuFrame),
+ kRtcpReport, sizeof(kRtcpReport)) {
+ }
+};
+
+// override to add NULL parameter
+template<>
+cricket::VideoChannel* ChannelTest<VideoTraits>::CreateChannel(
+ talk_base::Thread* thread, cricket::MediaEngineInterface* engine,
+ cricket::FakeVideoMediaChannel* ch, cricket::BaseSession* session,
+ bool rtcp) {
+ cricket::VideoChannel* channel = new cricket::VideoChannel(
+ thread, engine, ch, session, cricket::CN_VIDEO, rtcp, NULL);
+ if (!channel->Init()) {
+ delete channel;
+ channel = NULL;
+ }
+ return channel;
+}
+
+// override to add 0 parameter
+template<>
+bool ChannelTest<VideoTraits>::AddStream1(int id) {
+ return channel1_->AddRecvStream(cricket::StreamParams::CreateLegacy(id));
+}
+
+template<>
+void ChannelTest<VideoTraits>::CreateContent(
+ int flags,
+ const cricket::AudioCodec& audio_codec,
+ const cricket::VideoCodec& video_codec,
+ cricket::VideoContentDescription* video) {
+ video->AddCodec(video_codec);
+ video->set_rtcp_mux((flags & RTCP_MUX) != 0);
+ if (flags & SECURE) {
+ video->AddCrypto(cricket::CryptoParams(
+ 1, cricket::CS_AES_CM_128_HMAC_SHA1_80,
+ "inline:" + talk_base::CreateRandomString(40), ""));
+ }
+}
+
+template<>
+void ChannelTest<VideoTraits>::CopyContent(
+ const cricket::VideoContentDescription& source,
+ cricket::VideoContentDescription* video) {
+ *video = source;
+}
+
+template<>
+bool ChannelTest<VideoTraits>::CodecMatches(const cricket::VideoCodec& c1,
+ const cricket::VideoCodec& c2) {
+ return c1.name == c2.name && c1.width == c2.width && c1.height == c2.height &&
+ c1.framerate == c2.framerate;
+}
+
+template<>
+void ChannelTest<VideoTraits>::AddLegacyStreamInContent(
+ uint32 ssrc, int flags, cricket::VideoContentDescription* video) {
+ video->AddLegacyStream(ssrc);
+}
+
+class VideoChannelTest
+ : public ChannelTest<VideoTraits> {
+ public:
+ typedef ChannelTest<VideoTraits>
+ Base;
+ VideoChannelTest() : Base(kH264Packet, sizeof(kH264Packet),
+ kRtcpReport, sizeof(kRtcpReport)) {
+ }
+};
+
+
+// VoiceChannelTest
+
+TEST_F(VoiceChannelTest, TestInit) {
+ Base::TestInit();
+ EXPECT_FALSE(media_channel1_->muted());
+ EXPECT_TRUE(media_channel1_->dtmf_queue().empty());
+}
+
+TEST_F(VoiceChannelTest, TestSetContents) {
+ Base::TestSetContents();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsNullOffer) {
+ Base::TestSetContentsNullOffer();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentsRtcpMux) {
+ Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, TestSetRemoteContentUpdate) {
+ Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(VoiceChannelTest, TestStreams) {
+ Base::TestStreams();
+}
+
+TEST_F(VoiceChannelTest, TestUpdateStreamsInLocalContent) {
+ Base::TestUpdateStreamsInLocalContent();
+}
+
+TEST_F(VoiceChannelTest, TestUpdateRemoteStreamsInContent) {
+ Base::TestUpdateStreamsInRemoteContent();
+}
+
+TEST_F(VoiceChannelTest, TestChangeStreamParamsInContent) {
+ Base::TestChangeStreamParamsInContent();
+}
+
+TEST_F(VoiceChannelTest, TestPlayoutAndSendingStates) {
+ Base::TestPlayoutAndSendingStates();
+}
+
+TEST_F(VoiceChannelTest, TestCallSetup) {
+ Base::TestCallSetup();
+}
+
+TEST_F(VoiceChannelTest, TestCallTeardownRtcpMux) {
+ Base::TestCallTeardownRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendRtpToRtp) {
+ Base::SendRtpToRtp();
+}
+
+TEST_F(VoiceChannelTest, SendNoRtcpToNoRtcp) {
+ Base::SendNoRtcpToNoRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendNoRtcpToRtcp) {
+ Base::SendNoRtcpToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpToNoRtcp) {
+ Base::SendRtcpToNoRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpToRtcp) {
+ Base::SendRtcpToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpMuxToRtcp) {
+ Base::SendRtcpMuxToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendRtcpMuxToRtcpMux) {
+ Base::SendRtcpMuxToRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendEarlyRtcpMuxToRtcp) {
+ Base::SendEarlyRtcpMuxToRtcp();
+}
+
+TEST_F(VoiceChannelTest, SendEarlyRtcpMuxToRtcpMux) {
+ Base::SendEarlyRtcpMuxToRtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToSrtp) {
+ Base::SendSrtpToSrtp();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToRtp) {
+ Base::SendSrtpToSrtp();
+}
+
+TEST_F(VoiceChannelTest, SendSrtcpMux) {
+ Base::SendSrtcpMux();
+}
+
+TEST_F(VoiceChannelTest, SendRtpToRtpOnThread) {
+ Base::SendRtpToRtpOnThread();
+}
+
+TEST_F(VoiceChannelTest, SendSrtpToSrtpOnThread) {
+ Base::SendSrtpToSrtpOnThread();
+}
+
+TEST_F(VoiceChannelTest, SendWithWritabilityLoss) {
+ Base::SendWithWritabilityLoss();
+}
+
+TEST_F(VoiceChannelTest, TestMediaMonitor) {
+ Base::TestMediaMonitor();
+}
+
+// Test that Mute properly forwards to the media channel.
+TEST_F(VoiceChannelTest, TestMute) {
+ CreateChannels(0, 0);
+ EXPECT_FALSE(media_channel1_->muted());
+ EXPECT_TRUE(channel1_->Mute(true));
+ EXPECT_TRUE(media_channel1_->muted());
+ EXPECT_TRUE(channel1_->Mute(false));
+ EXPECT_FALSE(media_channel1_->muted());
+}
+
+// Test that keyboard automute works correctly.
+TEST_F(VoiceChannelTest, TestKeyboardMute) {
+ CreateChannels(0, 0);
+ EXPECT_FALSE(media_channel1_->muted());
+ EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_NONE, error_);
+
+ cricket::VoiceMediaChannel::Error e =
+ cricket::VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED;
+
+ // Typing doesn't mute automatically
+ media_channel1_->TriggerError(0, e);
+ talk_base::Thread::Current()->ProcessMessages(0);
+ EXPECT_EQ(e, error_);
+ EXPECT_FALSE(media_channel1_->muted());
+
+ // But it does when enabled
+ channel1_->set_mute_on_type(true, 200);
+ media_channel1_->TriggerError(0, e);
+ error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+ EXPECT_TRUE_WAIT(error_ == e, 100);
+ EXPECT_TRUE(media_channel1_->muted());
+ EXPECT_TRUE_WAIT(!media_channel1_->muted(), 250); // And resets.
+
+ // Muting manually preemts auto-unmute
+ media_channel1_->TriggerError(0, e);
+ error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+ EXPECT_TRUE_WAIT(error_ == e, 100);
+ EXPECT_TRUE(media_channel1_->muted());
+ EXPECT_TRUE(channel1_->Mute(true));
+ talk_base::Thread::Current()->ProcessMessages(250);
+ EXPECT_TRUE(media_channel1_->muted());
+}
+
+// Test that PressDTMF properly forwards to the media channel.
+TEST_F(VoiceChannelTest, TestDtmf) {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_EQ(0U, media_channel1_->dtmf_queue().size());
+ EXPECT_TRUE(channel1_->PressDTMF(1, true));
+ EXPECT_TRUE(channel1_->PressDTMF(8, false));
+ ASSERT_EQ(2U, media_channel1_->dtmf_queue().size());
+ EXPECT_EQ(1, media_channel1_->dtmf_queue()[0].first);
+ EXPECT_EQ(true, media_channel1_->dtmf_queue()[0].second);
+ EXPECT_EQ(8, media_channel1_->dtmf_queue()[1].first);
+ EXPECT_FALSE(media_channel1_->dtmf_queue()[1].second);
+}
+
+TEST_F(VoiceChannelTest, TestMediaSinks) {
+ Base::TestMediaSinks();
+}
+
+TEST_F(VoiceChannelTest, TestSetContentFailure) {
+ Base::TestSetContentFailure();
+}
+
+TEST_F(VoiceChannelTest, TestFlushRtcp) {
+ Base::TestFlushRtcp();
+}
+
+TEST_F(VoiceChannelTest, TestChangeStateError) {
+ Base::TestChangeStateError();
+}
+
+TEST_F(VoiceChannelTest, TestSrtpError) {
+ Base::TestSrtpError();
+}
+
+// Test that we can play a ringback tone properly.
+TEST_F(VoiceChannelTest, TestRingbackTone) {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_FALSE(media_channel1_->ringback_tone_play());
+ EXPECT_TRUE(channel1_->SetRingbackTone("RIFF", 4));
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ // Play ringback tone, no loop.
+ EXPECT_TRUE(channel1_->PlayRingbackTone(0, true, false));
+ EXPECT_EQ(0U, media_channel1_->ringback_tone_ssrc());
+ EXPECT_TRUE(media_channel1_->ringback_tone_play());
+ EXPECT_FALSE(media_channel1_->ringback_tone_loop());
+ // Stop the ringback tone.
+ EXPECT_TRUE(channel1_->PlayRingbackTone(0, false, false));
+ EXPECT_FALSE(media_channel1_->ringback_tone_play());
+ // Add a stream.
+ EXPECT_TRUE(AddStream1(1));
+ // Play ringback tone, looping, on the new stream.
+ EXPECT_TRUE(channel1_->PlayRingbackTone(1, true, true));
+ EXPECT_EQ(1U, media_channel1_->ringback_tone_ssrc());
+ EXPECT_TRUE(media_channel1_->ringback_tone_play());
+ EXPECT_TRUE(media_channel1_->ringback_tone_loop());
+ // Stop the ringback tone.
+ EXPECT_TRUE(channel1_->PlayRingbackTone(1, false, false));
+ EXPECT_FALSE(media_channel1_->ringback_tone_play());
+}
+
+// Test that we can scale the output volume properly for 1:1 calls.
+TEST_F(VoiceChannelTest, TestScaleVolume1to1Call) {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ double left, right;
+
+ // Default is (1.0, 1.0).
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+ // invalid ssrc.
+ EXPECT_FALSE(media_channel1_->GetOutputScaling(3, &left, &right));
+
+ // Set scale to (1.5, 0.5).
+ EXPECT_TRUE(channel1_->SetOutputScaling(0, 1.5, 0.5));
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(1.5, left);
+ EXPECT_DOUBLE_EQ(0.5, right);
+
+ // Set scale to (0, 0).
+ EXPECT_TRUE(channel1_->SetOutputScaling(0, 0.0, 0.0));
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(0.0, left);
+ EXPECT_DOUBLE_EQ(0.0, right);
+}
+
+// Test that we can scale the output volume properly for multiway calls.
+TEST_F(VoiceChannelTest, TestScaleVolumeMultiwayCall) {
+ CreateChannels(RTCP, RTCP);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+ EXPECT_TRUE(AddStream1(1));
+ EXPECT_TRUE(AddStream1(2));
+
+ double left, right;
+ // Default is (1.0, 1.0).
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+ // invalid ssrc.
+ EXPECT_FALSE(media_channel1_->GetOutputScaling(3, &left, &right));
+
+ // Set scale to (1.5, 0.5) for ssrc = 1.
+ EXPECT_TRUE(channel1_->SetOutputScaling(1, 1.5, 0.5));
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+ EXPECT_DOUBLE_EQ(1.5, left);
+ EXPECT_DOUBLE_EQ(0.5, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(1.0, left);
+ EXPECT_DOUBLE_EQ(1.0, right);
+
+ // Set scale to (0, 0) for all ssrcs.
+ EXPECT_TRUE(channel1_->SetOutputScaling(0, 0.0, 0.0));
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(0, &left, &right));
+ EXPECT_DOUBLE_EQ(0.0, left);
+ EXPECT_DOUBLE_EQ(0.0, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(1, &left, &right));
+ EXPECT_DOUBLE_EQ(0.0, left);
+ EXPECT_DOUBLE_EQ(0.0, right);
+ EXPECT_TRUE(media_channel1_->GetOutputScaling(2, &left, &right));
+ EXPECT_DOUBLE_EQ(0.0, left);
+ EXPECT_DOUBLE_EQ(0.0, right);
+}
+
+TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMux) {
+ Base::SendSsrcMuxToSsrcMux();
+}
+
+TEST_F(VoiceChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) {
+ Base::SendSsrcMuxToSsrcMuxWithRtcpMux();
+}
+
+// VideoChannelTest
+TEST_F(VideoChannelTest, TestInit) {
+ Base::TestInit();
+}
+
+TEST_F(VideoChannelTest, TestSetContents) {
+ Base::TestSetContents();
+}
+
+TEST_F(VideoChannelTest, TestSetContentsNullOffer) {
+ Base::TestSetContentsNullOffer();
+}
+
+TEST_F(VideoChannelTest, TestSetContentsRtcpMux) {
+ Base::TestSetContentsRtcpMux();
+}
+
+TEST_F(VideoChannelTest, TestSetRemoteContentUpdate) {
+ Base::TestSetRemoteContentUpdate();
+}
+
+TEST_F(VideoChannelTest, TestStreams) {
+ Base::TestStreams();
+}
+
+TEST_F(VideoChannelTest, TestUpdateStreamsInLocalContent) {
+ Base::TestUpdateStreamsInLocalContent();
+}
+
+TEST_F(VideoChannelTest, TestUpdateRemoteStreamsInContent) {
+ Base::TestUpdateStreamsInRemoteContent();
+}
+
+TEST_F(VideoChannelTest, TestChangeStreamParamsInContent) {
+ Base::TestChangeStreamParamsInContent();
+}
+
+TEST_F(VideoChannelTest, TestPlayoutAndSendingStates) {
+ Base::TestPlayoutAndSendingStates();
+}
+
+TEST_F(VideoChannelTest, TestCallSetup) {
+ Base::TestCallSetup();
+}
+
+TEST_F(VideoChannelTest, TestCallTeardownRtcpMux) {
+ Base::TestCallTeardownRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendRtpToRtp) {
+ Base::SendRtpToRtp();
+}
+
+TEST_F(VideoChannelTest, SendNoRtcpToNoRtcp) {
+ Base::SendNoRtcpToNoRtcp();
+}
+
+TEST_F(VideoChannelTest, SendNoRtcpToRtcp) {
+ Base::SendNoRtcpToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpToNoRtcp) {
+ Base::SendRtcpToNoRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpToRtcp) {
+ Base::SendRtcpToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpMuxToRtcp) {
+ Base::SendRtcpMuxToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendRtcpMuxToRtcpMux) {
+ Base::SendRtcpMuxToRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendEarlyRtcpMuxToRtcp) {
+ Base::SendEarlyRtcpMuxToRtcp();
+}
+
+TEST_F(VideoChannelTest, SendEarlyRtcpMuxToRtcpMux) {
+ Base::SendEarlyRtcpMuxToRtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToSrtp) {
+ Base::SendSrtpToSrtp();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToRtp) {
+ Base::SendSrtpToSrtp();
+}
+
+TEST_F(VideoChannelTest, SendSrtcpMux) {
+ Base::SendSrtcpMux();
+}
+
+TEST_F(VideoChannelTest, SendRtpToRtpOnThread) {
+ Base::SendRtpToRtpOnThread();
+}
+
+TEST_F(VideoChannelTest, SendSrtpToSrtpOnThread) {
+ Base::SendSrtpToSrtpOnThread();
+}
+
+TEST_F(VideoChannelTest, SendWithWritabilityLoss) {
+ Base::SendWithWritabilityLoss();
+}
+
+TEST_F(VideoChannelTest, TestMediaMonitor) {
+ Base::TestMediaMonitor();
+}
+
+TEST_F(VideoChannelTest, TestMediaSinks) {
+ Base::TestMediaSinks();
+}
+
+TEST_F(VideoChannelTest, TestSetContentFailure) {
+ Base::TestSetContentFailure();
+}
+
+TEST_F(VideoChannelTest, TestFlushRtcp) {
+ Base::TestFlushRtcp();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMux) {
+ Base::SendSsrcMuxToSsrcMux();
+}
+
+TEST_F(VideoChannelTest, SendSsrcMuxToSsrcMuxWithRtcpMux) {
+ Base::SendSsrcMuxToSsrcMuxWithRtcpMux();
+}
+
+// TODO: Add VideoChannelTest.TestChangeStateError.
+
+TEST_F(VideoChannelTest, TestSrtpError) {
+ Base::TestSrtpError();
+}
+
+TEST_F(VideoChannelTest, TestApplyViewRequest) {
+ CreateChannels(0, 0);
+ EXPECT_TRUE(SendInitiate());
+ EXPECT_TRUE(SendAccept());
+
+ cricket::VideoFormat send_format;
+ EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+ EXPECT_EQ(640, send_format.width);
+ EXPECT_EQ(400, send_format.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), send_format.interval);
+
+ cricket::ViewRequest request;
+ request.static_video_views.push_back(
+ cricket::StaticVideoView(kSsrc1, 320, 200, 15));
+ EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+ EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+ EXPECT_EQ(320, send_format.width);
+ EXPECT_EQ(200, send_format.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), send_format.interval);
+
+ // Short term hack for view request with SSRC 0. TODO: Remove this
+ // once Reflector uses the correct SSRC in view request (b/5977302).
+ request.static_video_views.clear();
+ request.static_video_views.push_back(
+ cricket::StaticVideoView(0, 160, 100, 8));
+ EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+ EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+ EXPECT_EQ(160, send_format.width);
+ EXPECT_EQ(100, send_format.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(8), send_format.interval);
+
+ request.static_video_views.clear();
+ EXPECT_TRUE(channel1_->ApplyViewRequest(request));
+ EXPECT_TRUE(media_channel1_->GetSendStreamFormat(kSsrc1, &send_format));
+ EXPECT_EQ(0, send_format.width);
+ EXPECT_EQ(0, send_format.height);
+}
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
index 8f8a0fc..7cca61a 100644
--- a/talk/session/phone/channelmanager.cc
+++ b/talk/session/phone/channelmanager.cc
@@ -59,8 +59,16 @@
MSG_DESTROYSOUNDCLIP = 18,
MSG_CAMERASTARTED = 19,
MSG_SETVIDEOCAPTURE = 20,
+ MSG_TERMINATE = 21,
+ MSG_REGISTERVIDEOPROCESSOR = 22,
+ MSG_UNREGISTERVIDEOPROCESSOR = 23,
+ MSG_REGISTERVOICEPROCESSOR = 24,
+ MSG_UNREGISTERVOICEPROCESSOR = 25,
+ MSG_SETVIDEOCAPTURER = 26,
};
+static const int kNotSetOutputVolume = -1;
+
struct CreationParams : public talk_base::MessageData {
CreationParams(BaseSession* session, const std::string& content_name,
bool rtcp, VoiceChannel* voice_channel)
@@ -117,6 +125,16 @@
bool result;
};
+struct Capturer : public talk_base::MessageData {
+ Capturer(VideoCapturer* c, uint32 s)
+ : capturer(c),
+ ssrc(s),
+ result(false) {}
+ VideoCapturer* capturer;
+ uint32 ssrc;
+ bool result;
+};
+
struct LoggingOptions : public talk_base::MessageData {
explicit LoggingOptions(int lev, const char* f) : level(lev), filter(f) {}
int level;
@@ -125,11 +143,27 @@
struct CaptureParams : public talk_base::MessageData {
explicit CaptureParams(bool c) : capture(c), result(CR_FAILURE) {}
-
bool capture;
CaptureResult result;
};
+struct VideoProcessorParams : public talk_base::MessageData {
+ VideoProcessorParams(uint32 s, VideoProcessor* p)
+ : ssrc(s), processor(p), result(false) {}
+ uint32 ssrc;
+ VideoProcessor* processor;
+ bool result;
+};
+
+struct VoiceProcessorParams : public talk_base::MessageData {
+ VoiceProcessorParams(uint32 c, VoiceProcessor* p, MediaProcessorDirection d)
+ : ssrc(c), direction(d), processor(p), result(false) {}
+ uint32 ssrc;
+ MediaProcessorDirection direction;
+ VoiceProcessor* processor;
+ bool result;
+};
+
ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
: media_engine_(MediaEngineFactory::Create()),
device_manager_(cricket::DeviceManagerFactory::Create()),
@@ -139,6 +173,7 @@
audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
+ audio_output_volume_(kNotSetOutputVolume),
local_renderer_(NULL),
capturing_(false),
monitoring_(false) {
@@ -156,6 +191,7 @@
audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
+ audio_output_volume_(kNotSetOutputVolume),
local_renderer_(NULL),
capturing_(false),
monitoring_(false) {
@@ -166,8 +202,6 @@
// Init the device manager immediately, and set up our default video device.
SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
device_manager_->Init();
- // Set camera_device_ to the name of the default video capturer.
- SetVideoOptions(DeviceManagerInterface::kDefaultDeviceName);
// Camera is started asynchronously, request callbacks when startup
// completes to be able to forward them to the rendering manager.
@@ -250,6 +284,14 @@
<< " speaker: " << audio_out_device_
<< " options: " << audio_options_;
}
+
+ // If audio_output_volume_ has been set via SetOutputVolume(), set the
+ // audio output volume of the engine.
+ if (kNotSetOutputVolume != audio_output_volume_ &&
+ !SetOutputVolume(audio_output_volume_)) {
+ LOG(LS_WARNING) << "Failed to SetOutputVolume to "
+ << audio_output_volume_;
+ }
if (!SetVideoOptions(camera_device_) && !camera_device_.empty()) {
LOG(LS_WARNING) << "Failed to SetVideoOptions with camera: "
<< camera_device_;
@@ -278,8 +320,14 @@
if (!initialized_) {
return;
}
+ Send(MSG_TERMINATE, NULL);
+ media_engine_->Terminate();
+ initialized_ = false;
+}
- // Need to destroy the voice/video channels
+void ChannelManager::Terminate_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ // Need to destroy the voice/video channels
while (!video_channels_.empty()) {
DestroyVideoChannel_w(video_channels_.back());
}
@@ -289,9 +337,6 @@
while (!soundclips_.empty()) {
DestroySoundclip_w(soundclips_.back());
}
-
- media_engine_->Terminate();
- initialized_ = false;
}
VoiceChannel* ChannelManager::CreateVoiceChannel(
@@ -302,8 +347,6 @@
VoiceChannel* ChannelManager::CreateVoiceChannel_w(
BaseSession* session, const std::string& content_name, bool rtcp) {
- talk_base::CritScope cs(&crit_);
-
// This is ok to alloc from a thread other than the worker thread
ASSERT(initialized_);
VoiceMediaChannel* media_channel = media_engine_->CreateChannel();
@@ -329,7 +372,6 @@
}
void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) {
- talk_base::CritScope cs(&crit_);
// Destroy voice channel.
ASSERT(initialized_);
VoiceChannels::iterator it = std::find(voice_channels_.begin(),
@@ -352,8 +394,6 @@
VideoChannel* ChannelManager::CreateVideoChannel_w(
BaseSession* session, const std::string& content_name, bool rtcp,
VoiceChannel* voice_channel) {
- talk_base::CritScope cs(&crit_);
-
// This is ok to alloc from a thread other than the worker thread
ASSERT(initialized_);
VideoMediaChannel* media_channel =
@@ -382,7 +422,6 @@
}
void ChannelManager::DestroyVideoChannel_w(VideoChannel *video_channel) {
- talk_base::CritScope cs(&crit_);
// Destroy voice channel.
ASSERT(initialized_);
VideoChannels::iterator it = std::find(video_channels_.begin(),
@@ -402,8 +441,6 @@
}
Soundclip* ChannelManager::CreateSoundclip_w() {
- talk_base::CritScope cs(&crit_);
-
ASSERT(initialized_);
ASSERT(worker_thread_ == talk_base::Thread::Current());
@@ -425,7 +462,6 @@
}
void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) {
- talk_base::CritScope cs(&crit_);
// Destroy soundclip.
ASSERT(initialized_);
Soundclips::iterator it = std::find(soundclips_.begin(),
@@ -485,7 +521,6 @@
// Set the audio devices
if (ret) {
- talk_base::CritScope cs(&crit_);
ret = media_engine_->SetSoundDevices(in_dev, out_dev);
}
@@ -509,8 +544,17 @@
}
bool ChannelManager::SetOutputVolume(int level) {
- VolumeLevel volume(level);
- return (Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result);
+ bool ret = level >= 0 && level <= 255;
+ if (initialized_) {
+ VolumeLevel volume(level);
+ ret &= Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result;
+ }
+
+ if (ret) {
+ audio_output_volume_ = level;
+ }
+
+ return ret;
}
bool ChannelManager::SetOutputVolume_w(int level) {
@@ -520,22 +564,33 @@
}
bool ChannelManager::GetVideoOptions(std::string* cam_name) {
+ if (camera_device_.empty()) {
+ // Initialize camera_device_ with default.
+ Device device;
+ if (!device_manager_->GetVideoCaptureDevice(
+ DeviceManagerInterface::kDefaultDeviceName, &device)) {
+ LOG(LS_WARNING) << "Device manager can't find default camera: " <<
+ DeviceManagerInterface::kDefaultDeviceName;
+ return false;
+ }
+ camera_device_ = device.name;
+ }
*cam_name = camera_device_;
return true;
}
bool ChannelManager::SetVideoOptions(const std::string& cam_name) {
Device device;
+ bool ret = true;
if (!device_manager_->GetVideoCaptureDevice(cam_name, &device)) {
if (!cam_name.empty()) {
LOG(LS_WARNING) << "Device manager can't find camera: " << cam_name;
}
- return false;
+ ret = false;
}
// If we're running, tell the media engine about it.
- bool ret = true;
- if (initialized_) {
+ if (initialized_ && ret) {
VideoOptions options(&device);
ret = (Send(MSG_SETVIDEOOPTIONS, &options) && options.result);
}
@@ -543,7 +598,18 @@
// If everything worked, retain the name of the selected camera.
if (ret) {
camera_device_ = device.name;
+ } else if (camera_device_.empty()) {
+ // When video option setting fails, we still want camera_device_ to be in a
+ // good state, so we initialize it with default if it's empty.
+ Device default_device;
+ if (!device_manager_->GetVideoCaptureDevice(
+ DeviceManagerInterface::kDefaultDeviceName, &default_device)) {
+ LOG(LS_WARNING) << "Device manager can't find default camera: " <<
+ DeviceManagerInterface::kDefaultDeviceName;
+ }
+ camera_device_ = default_device.name;
}
+
return ret;
}
@@ -607,6 +673,21 @@
return media_engine_->SetLocalRenderer(renderer);
}
+bool ChannelManager::SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) {
+ bool ret = true;
+ if (initialized_) {
+ Capturer capture(capturer, ssrc);
+ ret = (Send(MSG_SETVIDEOCAPTURER, &capture) && capture.result);
+ }
+ return ret;
+}
+
+bool ChannelManager::SetVideoCapturer_w(VideoCapturer* capturer, uint32 ssrc) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetVideoCapturer(capturer, ssrc);
+}
+
CaptureResult ChannelManager::SetVideoCapture(bool capture) {
bool ret;
CaptureParams capture_params(capture);
@@ -655,6 +736,62 @@
}
}
+// TODO: For now pass this request through the mediaengine to the
+// voice and video engines to do the real work. Once the capturer refactoring
+// is done, we will access the capturer using the ssrc (similar to how the
+// renderer is accessed today) and register with it directly.
+bool ChannelManager::RegisterVideoProcessor(uint32 ssrc,
+ VideoProcessor* processor) {
+ VideoProcessorParams processor_params(ssrc, processor);
+ return (Send(MSG_REGISTERVIDEOPROCESSOR, &processor_params) &&
+ processor_params.result);
+}
+bool ChannelManager::RegisterVideoProcessor_w(uint32 ssrc,
+ VideoProcessor* processor) {
+ return media_engine_->RegisterVideoProcessor(processor);
+}
+
+bool ChannelManager::UnregisterVideoProcessor(uint32 ssrc,
+ VideoProcessor* processor) {
+ VideoProcessorParams processor_params(ssrc, processor);
+ return (Send(MSG_UNREGISTERVIDEOPROCESSOR, &processor_params) &&
+ processor_params.result);
+}
+bool ChannelManager::UnregisterVideoProcessor_w(uint32 ssrc,
+ VideoProcessor* processor) {
+ return media_engine_->UnregisterVideoProcessor(processor);
+}
+
+bool ChannelManager::RegisterVoiceProcessor(
+ uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ VoiceProcessorParams processor_params(ssrc, processor, direction);
+ return (Send(MSG_REGISTERVOICEPROCESSOR, &processor_params) &&
+ processor_params.result);
+}
+bool ChannelManager::RegisterVoiceProcessor_w(
+ uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return media_engine_->RegisterVoiceProcessor(ssrc, processor, direction);
+}
+
+bool ChannelManager::UnregisterVoiceProcessor(
+ uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ VoiceProcessorParams processor_params(ssrc, processor, direction);
+ return (Send(MSG_UNREGISTERVOICEPROCESSOR, &processor_params) &&
+ processor_params.result);
+}
+bool ChannelManager::UnregisterVoiceProcessor_w(
+ uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return media_engine_->UnregisterVoiceProcessor(ssrc, processor, direction);
+}
+
bool ChannelManager::Send(uint32 id, talk_base::MessageData* data) {
if (!worker_thread_ || !initialized_) return false;
worker_thread_->Send(this, id, data);
@@ -746,6 +883,11 @@
p->result = SetLocalRenderer_w(p->renderer);
break;
}
+ case MSG_SETVIDEOCAPTURER: {
+ Capturer* p = static_cast<Capturer*>(data);
+ p->result = SetVideoCapturer_w(p->capturer, p->ssrc);
+ break;
+ }
case MSG_SETVIDEOCAPTURE: {
CaptureParams* p = static_cast<CaptureParams*>(data);
p->result = SetVideoCapture_w(p->capture);
@@ -766,6 +908,38 @@
delete data;
break;
}
+ case MSG_TERMINATE: {
+ Terminate_w();
+ break;
+ }
+ case MSG_REGISTERVIDEOPROCESSOR: {
+ VideoProcessorParams* data =
+ static_cast<VideoProcessorParams*>(message->pdata);
+ data->result = RegisterVideoProcessor_w(data->ssrc, data->processor);
+ break;
+ }
+ case MSG_UNREGISTERVIDEOPROCESSOR: {
+ VideoProcessorParams* data =
+ static_cast<VideoProcessorParams*>(message->pdata);
+ data->result = UnregisterVideoProcessor_w(data->ssrc, data->processor);
+ break;
+ }
+ case MSG_REGISTERVOICEPROCESSOR: {
+ VoiceProcessorParams* data =
+ static_cast<VoiceProcessorParams*>(message->pdata);
+ data->result = RegisterVoiceProcessor_w(data->ssrc,
+ data->processor,
+ data->direction);
+ break;
+ }
+ case MSG_UNREGISTERVOICEPROCESSOR: {
+ VoiceProcessorParams* data =
+ static_cast<VoiceProcessorParams*>(message->pdata);
+ data->result = UnregisterVoiceProcessor_w(data->ssrc,
+ data->processor,
+ data->direction);
+ break;
+ }
}
}
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
index c574a43..9d100ee 100644
--- a/talk/session/phone/channelmanager.h
+++ b/talk/session/phone/channelmanager.h
@@ -41,7 +41,9 @@
namespace cricket {
class Soundclip;
+class VideoProcessor;
class VoiceChannel;
+class VoiceProcessor;
// ChannelManager allows the MediaEngine to run on a separate thread, and takes
// care of marshalling calls between threads. It also creates and keeps track of
@@ -83,10 +85,6 @@
bool initialized() const { return initialized_; }
// Starts up the media engine.
bool Init();
- // TODO: Remove this temporary API once Flute is updated.
- bool Init(talk_base::Thread* thread) {
- return set_worker_thread(thread) && Init();
- }
// Shuts down the media engine.
void Terminate();
@@ -132,6 +130,9 @@
bool monitoring() const { return monitoring_; }
// Sets the local renderer where to renderer the local camera.
bool SetLocalRenderer(VideoRenderer* renderer);
+ // Sets the externally provided video capturer. The ssrc is the ssrc of the
+ // (video) stream for which the video capturer should be set.
+ bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc);
// Starts and stops the local camera and renders it to the local renderer.
CaptureResult SetVideoCapture(bool capture);
bool capturing() const { return capturing_; }
@@ -140,6 +141,21 @@
void SetVoiceLogging(int level, const char* filter);
void SetVideoLogging(int level, const char* filter);
+ // The channel manager handles the Tx side for Video processing,
+ // as well as Tx and Rx side for Voice processing.
+ // (The Rx Video processing will go throug the simplerenderingmanager,
+ // to be implemented).
+ bool RegisterVideoProcessor(uint32 ssrc,
+ VideoProcessor* processor);
+ bool UnregisterVideoProcessor(uint32 ssrc,
+ VideoProcessor* processor);
+ bool RegisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction);
+ bool UnregisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction);
+
// The operations below occur on the main thread.
bool GetAudioInputDevices(std::vector<std::string>* names);
@@ -155,6 +171,7 @@
void Construct();
bool Send(uint32 id, talk_base::MessageData* pdata);
+ void Terminate_w();
VoiceChannel* CreateVoiceChannel_w(
BaseSession* session, const std::string& content_name, bool rtcp);
void DestroyVoiceChannel_w(VoiceChannel* voice_channel);
@@ -172,14 +189,25 @@
bool SetVideoOptions_w(const Device* cam_device);
bool SetDefaultVideoEncoderConfig_w(const VideoEncoderConfig& config);
bool SetLocalRenderer_w(VideoRenderer* renderer);
+ bool SetVideoCapturer_w(VideoCapturer* capturer, uint32 ssrc);
CaptureResult SetVideoCapture_w(bool capture);
void SetMediaLogging(bool video, int level, const char* filter);
void SetMediaLogging_w(bool video, int level, const char* filter);
void OnVideoCaptureResult(VideoCapturer* capturer,
CaptureResult result);
+ bool RegisterVideoProcessor_w(uint32 ssrc,
+ VideoProcessor* processor);
+ bool UnregisterVideoProcessor_w(uint32 ssrc,
+ VideoProcessor* processor);
+ bool RegisterVoiceProcessor_w(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction);
+ bool UnregisterVoiceProcessor_w(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction);
+
void OnMessage(talk_base::Message *message);
- talk_base::CriticalSection crit_;
talk_base::scoped_ptr<MediaEngineInterface> media_engine_;
talk_base::scoped_ptr<DeviceManagerInterface> device_manager_;
bool initialized_;
@@ -193,6 +221,7 @@
std::string audio_in_device_;
std::string audio_out_device_;
int audio_options_;
+ int audio_output_volume_;
std::string camera_device_;
VideoEncoderConfig default_video_encoder_config_;
VideoRenderer* local_renderer_;
diff --git a/talk/session/phone/channelmanager_unittest.cc b/talk/session/phone/channelmanager_unittest.cc
new file mode 100644
index 0000000..0c132ac
--- /dev/null
+++ b/talk/session/phone/channelmanager_unittest.cc
@@ -0,0 +1,523 @@
+// libjingle
+// Copyright 2008 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakemediaprocessor.h"
+#include "talk/session/phone/nullvideorenderer.h"
+
+class ChannelManagerTest : public testing::Test {
+ protected:
+ ChannelManagerTest() : fme_(NULL), fdm_(NULL), cm_(NULL) {
+ }
+
+ virtual void SetUp() {
+ fme_ = new cricket::FakeMediaEngine();
+ fdm_ = new cricket::FakeDeviceManager();
+ cm_ = new cricket::ChannelManager(fme_, fdm_, talk_base::Thread::Current());
+ session_ = new cricket::FakeSession();
+
+ std::vector<std::string> in_device_list, out_device_list, vid_device_list;
+ in_device_list.push_back("audio-in1");
+ in_device_list.push_back("audio-in2");
+ out_device_list.push_back("audio-out1");
+ out_device_list.push_back("audio-out2");
+ vid_device_list.push_back("video-in1");
+ vid_device_list.push_back("video-in2");
+ fdm_->SetAudioInputDevices(in_device_list);
+ fdm_->SetAudioOutputDevices(out_device_list);
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+ }
+
+ virtual void TearDown() {
+ delete session_;
+ delete cm_;
+ cm_ = NULL;
+ fdm_ = NULL;
+ fme_ = NULL;
+ }
+
+ talk_base::Thread worker_;
+ cricket::FakeMediaEngine* fme_;
+ cricket::FakeDeviceManager* fdm_;
+ cricket::ChannelManager* cm_;
+ cricket::FakeSession* session_;
+};
+
+// Test that we startup/shutdown properly.
+TEST_F(ChannelManagerTest, StartupShutdown) {
+ EXPECT_FALSE(cm_->initialized());
+ EXPECT_EQ(talk_base::Thread::Current(), cm_->worker_thread());
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->initialized());
+ cm_->Terminate();
+ EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we startup/shutdown properly with a worker thread.
+TEST_F(ChannelManagerTest, StartupShutdownOnThread) {
+ worker_.Start();
+ EXPECT_FALSE(cm_->initialized());
+ EXPECT_EQ(talk_base::Thread::Current(), cm_->worker_thread());
+ EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+ EXPECT_EQ(&worker_, cm_->worker_thread());
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->initialized());
+ // Setting the worker thread while initialized should fail.
+ EXPECT_FALSE(cm_->set_worker_thread(talk_base::Thread::Current()));
+ cm_->Terminate();
+ EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we fail to startup if we're given an unstarted thread.
+TEST_F(ChannelManagerTest, StartupShutdownOnUnstartedThread) {
+ EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+ EXPECT_FALSE(cm_->Init());
+ EXPECT_FALSE(cm_->initialized());
+}
+
+// Test that we can create and destroy a voice and video channel.
+TEST_F(ChannelManagerTest, CreateDestroyChannels) {
+ EXPECT_TRUE(cm_->Init());
+ cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+ session_, cricket::CN_AUDIO, false);
+ EXPECT_TRUE(voice_channel != NULL);
+ cricket::VideoChannel* video_channel =
+ cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+ false, voice_channel);
+ EXPECT_TRUE(video_channel != NULL);
+ cm_->DestroyVideoChannel(video_channel);
+ cm_->DestroyVoiceChannel(voice_channel);
+ cm_->Terminate();
+}
+
+// Test that we can create and destroy a voice and video channel with a worker.
+TEST_F(ChannelManagerTest, CreateDestroyChannelsOnThread) {
+ worker_.Start();
+ EXPECT_TRUE(cm_->set_worker_thread(&worker_));
+ EXPECT_TRUE(cm_->Init());
+ cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+ session_, cricket::CN_AUDIO, false);
+ EXPECT_TRUE(voice_channel != NULL);
+ cricket::VideoChannel* video_channel =
+ cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+ false, voice_channel);
+ EXPECT_TRUE(video_channel != NULL);
+ cm_->DestroyVideoChannel(video_channel);
+ cm_->DestroyVoiceChannel(voice_channel);
+ cm_->Terminate();
+}
+
+// Test that we fail to create a voice/video channel if the session is unable
+// to create a cricket::TransportChannel
+TEST_F(ChannelManagerTest, NoTransportChannelTest) {
+ EXPECT_TRUE(cm_->Init());
+ session_->set_fail_channel_creation(true);
+ // The test is useless unless the session does not fail creating
+ // cricket::TransportChannel.
+ ASSERT_TRUE(session_->CreateChannel("audio", "rtp") == NULL);
+
+ cricket::VoiceChannel* voice_channel = cm_->CreateVoiceChannel(
+ session_, cricket::CN_AUDIO, false);
+ EXPECT_TRUE(voice_channel == NULL);
+ cricket::VideoChannel* video_channel =
+ cm_->CreateVideoChannel(session_, cricket::CN_VIDEO,
+ false, voice_channel);
+ EXPECT_TRUE(video_channel == NULL);
+ cm_->Terminate();
+}
+
+// Test that SetDefaultVideoCodec passes through the right values.
+TEST_F(ChannelManagerTest, SetDefaultVideoEncoderConfig) {
+ cricket::VideoCodec codec(96, "G264", 1280, 720, 60, 0);
+ cricket::VideoEncoderConfig config(codec, 1, 2);
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->SetDefaultVideoEncoderConfig(config));
+ EXPECT_EQ(config, fme_->default_video_encoder_config());
+}
+
+// Test that SetDefaultVideoCodec passes through the right values.
+TEST_F(ChannelManagerTest, SetDefaultVideoCodecBeforeInit) {
+ cricket::VideoCodec codec(96, "G264", 1280, 720, 60, 0);
+ cricket::VideoEncoderConfig config(codec, 1, 2);
+ EXPECT_TRUE(cm_->SetDefaultVideoEncoderConfig(config));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ(config, fme_->default_video_encoder_config());
+}
+
+TEST_F(ChannelManagerTest, SetAudioOptionsBeforeInit) {
+ // Test that values that we set before Init are applied.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("audio-in1", fme_->audio_in_device());
+ EXPECT_EQ("audio-out1", fme_->audio_out_device());
+ EXPECT_EQ(0x2, fme_->audio_options());
+}
+
+TEST_F(ChannelManagerTest, GetAudioOptionsBeforeInit) {
+ std::string audio_in, audio_out;
+ int opts;
+ // Test that GetAudioOptions works before Init.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in2", "audio-out2", 0x1));
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ("audio-in2", audio_in);
+ EXPECT_EQ("audio-out2", audio_out);
+ EXPECT_EQ(0x1, opts);
+ // Test that options set before Init can be gotten after Init.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ("audio-in1", audio_in);
+ EXPECT_EQ("audio-out1", audio_out);
+ EXPECT_EQ(0x2, opts);
+}
+
+TEST_F(ChannelManagerTest, SetAudioOptions) {
+ // Test initial state.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("", fme_->audio_in_device());
+ EXPECT_EQ("", fme_->audio_out_device());
+ EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS,
+ fme_->audio_options());
+ // Test setting defaults.
+ EXPECT_TRUE(cm_->SetAudioOptions("", "", 0x3));
+ EXPECT_EQ("", fme_->audio_in_device());
+ EXPECT_EQ("", fme_->audio_out_device());
+ EXPECT_EQ(0x3, fme_->audio_options());
+ // Test setting specific values.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+ EXPECT_EQ("audio-in1", fme_->audio_in_device());
+ EXPECT_EQ("audio-out1", fme_->audio_out_device());
+ EXPECT_EQ(0x2, fme_->audio_options());
+ // Test setting bad values.
+ EXPECT_FALSE(cm_->SetAudioOptions("audio-in9", "audio-out2", 0x1));
+}
+
+TEST_F(ChannelManagerTest, GetAudioOptions) {
+ std::string audio_in, audio_out;
+ int opts;
+ // Test initial state.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+ audio_in);
+ EXPECT_EQ(std::string(cricket::DeviceManagerInterface::kDefaultDeviceName),
+ audio_out);
+ EXPECT_EQ(cricket::MediaEngineInterface::DEFAULT_AUDIO_OPTIONS, opts);
+ // Test that we get back specific values that we set.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ("audio-in1", audio_in);
+ EXPECT_EQ("audio-out1", audio_out);
+ EXPECT_EQ(0x2, opts);
+}
+
+TEST_F(ChannelManagerTest, SetVideoOptionsBeforeInit) {
+ // Test that values that we set before Init are applied.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("video-in2", fme_->video_in_device());
+}
+
+TEST_F(ChannelManagerTest, GetVideoOptionsBeforeInit) {
+ std::string video_in;
+ // Test that GetVideoOptions works before Init.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+ // Test that options set before Init can be gotten after Init.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in2", video_in);
+}
+
+TEST_F(ChannelManagerTest, SetVideoOptions) {
+ // Test setting defaults.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->SetVideoOptions("")); // will use DeviceManager default
+ EXPECT_EQ("video-in1", fme_->video_in_device());
+ // Test setting specific values.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+ EXPECT_EQ("video-in2", fme_->video_in_device());
+ // TODO: Add test for invalid value here.
+}
+
+// Test unplugging and plugging back the preferred devices. When the preferred
+// device is unplugged, we fall back to the default device. When the preferred
+// device is plugged back, we use it.
+TEST_F(ChannelManagerTest, SetAudioOptionsUnplugPlug) {
+ // Set preferences "audio-in1" and "audio-out1" before init.
+ EXPECT_TRUE(cm_->SetAudioOptions("audio-in1", "audio-out1", 0x2));
+ // Unplug device "audio-in1" and "audio-out1".
+ std::vector<std::string> in_device_list, out_device_list;
+ in_device_list.push_back("audio-in2");
+ out_device_list.push_back("audio-out2");
+ fdm_->SetAudioInputDevices(in_device_list);
+ fdm_->SetAudioOutputDevices(out_device_list);
+ // Init should fall back to default devices.
+ EXPECT_TRUE(cm_->Init());
+ // The media engine should use the default.
+ EXPECT_EQ("", fme_->audio_in_device());
+ EXPECT_EQ("", fme_->audio_out_device());
+ // The channel manager keeps the preferences "audio-in1" and "audio-out1".
+ std::string audio_in, audio_out;
+ int opts;
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ("audio-in1", audio_in);
+ EXPECT_EQ("audio-out1", audio_out);
+ cm_->Terminate();
+
+ // Plug devices "audio-in2" and "audio-out2" back.
+ in_device_list.push_back("audio-in1");
+ out_device_list.push_back("audio-out1");
+ fdm_->SetAudioInputDevices(in_device_list);
+ fdm_->SetAudioOutputDevices(out_device_list);
+ // Init again. The preferences, "audio-in2" and "audio-out2", are used.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("audio-in1", fme_->audio_in_device());
+ EXPECT_EQ("audio-out1", fme_->audio_out_device());
+ EXPECT_TRUE(cm_->GetAudioOptions(&audio_in, &audio_out, &opts));
+ EXPECT_EQ("audio-in1", audio_in);
+ EXPECT_EQ("audio-out1", audio_out);
+}
+
+// We have one camera. Unplug it, fall back to no camera.
+TEST_F(ChannelManagerTest, SetVideoOptionsUnplugPlugOneCamera) {
+ // Set preferences "video-in1" before init.
+ std::vector<std::string> vid_device_list;
+ vid_device_list.push_back("video-in1");
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+
+ // Unplug "video-in1".
+ vid_device_list.clear();
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+
+ // Init should fall back to avatar.
+ EXPECT_TRUE(cm_->Init());
+ // The media engine should use no camera.
+ EXPECT_EQ("", fme_->video_in_device());
+ // The channel manager keeps the user preference "video-in".
+ std::string video_in;
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+ cm_->Terminate();
+
+ // Plug device "video-in1" back.
+ vid_device_list.push_back("video-in1");
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+ // Init again. The user preferred device, "video-in1", is used.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("video-in1", fme_->video_in_device());
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+}
+
+// We have multiple cameras. Unplug the preferred, fall back to another camera.
+TEST_F(ChannelManagerTest, SetVideoOptionsUnplugPlugTwoDevices) {
+ // Set video device to "video-in1" before init.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in1"));
+ // Unplug device "video-in1".
+ std::vector<std::string> vid_device_list;
+ vid_device_list.push_back("video-in2");
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+ // Init should fall back to default device "video-in2".
+ EXPECT_TRUE(cm_->Init());
+ // The media engine should use the default device "video-in2".
+ EXPECT_EQ("video-in2", fme_->video_in_device());
+ // The channel manager keeps the user preference "video-in".
+ std::string video_in;
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+ cm_->Terminate();
+
+ // Plug device "video-in1" back.
+ vid_device_list.push_back("video-in1");
+ fdm_->SetVideoCaptureDevices(vid_device_list);
+ // Init again. The user preferred device, "video-in1", is used.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ("video-in1", fme_->video_in_device());
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+}
+
+TEST_F(ChannelManagerTest, GetVideoOptions) {
+ std::string video_in;
+ // Test setting/getting defaults.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->SetVideoOptions(""));
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in1", video_in);
+ // Test setting/getting specific values.
+ EXPECT_TRUE(cm_->SetVideoOptions("video-in2"));
+ EXPECT_TRUE(cm_->GetVideoOptions(&video_in));
+ EXPECT_EQ("video-in2", video_in);
+}
+
+TEST_F(ChannelManagerTest, GetSetOutputVolumeBeforeInit) {
+ int level;
+ // Before init, SetOutputVolume() remembers the volume but does not change the
+ // volume of the engine. GetOutputVolume() should fail.
+ EXPECT_EQ(-1, fme_->output_volume());
+ EXPECT_FALSE(cm_->GetOutputVolume(&level));
+ EXPECT_FALSE(cm_->SetOutputVolume(-1)); // Invalid volume.
+ EXPECT_TRUE(cm_->SetOutputVolume(99));
+ EXPECT_EQ(-1, fme_->output_volume());
+
+ // Init() will apply the remembered volume.
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->GetOutputVolume(&level));
+ EXPECT_EQ(99, level);
+ EXPECT_EQ(level, fme_->output_volume());
+
+ EXPECT_TRUE(cm_->SetOutputVolume(60));
+ EXPECT_TRUE(cm_->GetOutputVolume(&level));
+ EXPECT_EQ(60, level);
+ EXPECT_EQ(level, fme_->output_volume());
+}
+
+TEST_F(ChannelManagerTest, GetSetOutputVolume) {
+ int level;
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->GetOutputVolume(&level));
+ EXPECT_EQ(level, fme_->output_volume());
+
+ EXPECT_FALSE(cm_->SetOutputVolume(-1)); // Invalid volume.
+ EXPECT_TRUE(cm_->SetOutputVolume(60));
+ EXPECT_EQ(60, fme_->output_volume());
+ EXPECT_TRUE(cm_->GetOutputVolume(&level));
+ EXPECT_EQ(60, level);
+}
+
+// Test that a value set before Init is applied properly.
+TEST_F(ChannelManagerTest, SetLocalRendererBeforeInit) {
+ cricket::NullVideoRenderer renderer;
+ EXPECT_TRUE(cm_->SetLocalRenderer(&renderer));
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ(&renderer, fme_->local_renderer());
+}
+
+// Test that a value set after init is passed through properly.
+TEST_F(ChannelManagerTest, SetLocalRenderer) {
+ cricket::NullVideoRenderer renderer;
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_TRUE(cm_->SetLocalRenderer(&renderer));
+ EXPECT_EQ(&renderer, fme_->local_renderer());
+}
+
+// Test that logging options set before Init are applied properly,
+// and retained even after Init.
+TEST_F(ChannelManagerTest, SetLoggingBeforeInit) {
+ cm_->SetVoiceLogging(talk_base::LS_INFO, "test-voice");
+ cm_->SetVideoLogging(talk_base::LS_VERBOSE, "test-video");
+ EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+ EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+ EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+ EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+ EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+ EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+ EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+}
+
+// Test that logging options set after Init are applied properly.
+TEST_F(ChannelManagerTest, SetLogging) {
+ EXPECT_TRUE(cm_->Init());
+ cm_->SetVoiceLogging(talk_base::LS_INFO, "test-voice");
+ cm_->SetVideoLogging(talk_base::LS_VERBOSE, "test-video");
+ EXPECT_EQ(talk_base::LS_INFO, fme_->voice_loglevel());
+ EXPECT_STREQ("test-voice", fme_->voice_logfilter().c_str());
+ EXPECT_EQ(talk_base::LS_VERBOSE, fme_->video_loglevel());
+ EXPECT_STREQ("test-video", fme_->video_logfilter().c_str());
+}
+
+// Test that SetVideoCapture passes through the right value.
+TEST_F(ChannelManagerTest, SetVideoCapture) {
+ // Should fail until we are initialized.
+ EXPECT_FALSE(fme_->capture());
+ EXPECT_EQ(cricket::CR_FAILURE, cm_->SetVideoCapture(true));
+ EXPECT_FALSE(fme_->capture());
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_FALSE(fme_->capture());
+ EXPECT_EQ(cricket::CR_SUCCESS, cm_->SetVideoCapture(true));
+ EXPECT_TRUE(fme_->capture());
+ EXPECT_EQ(cricket::CR_SUCCESS, cm_->SetVideoCapture(false));
+ EXPECT_FALSE(fme_->capture());
+}
+
+// Test that the Video/Voice Processors register and unregister
+TEST_F(ChannelManagerTest, RegisterProcessors) {
+ cricket::FakeMediaProcessor fmp;
+ EXPECT_TRUE(cm_->Init());
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+ EXPECT_TRUE(cm_->RegisterVideoProcessor(1, &fmp));
+ EXPECT_TRUE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+ EXPECT_TRUE(cm_->UnregisterVideoProcessor(1, &fmp));
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+ EXPECT_TRUE(cm_->RegisterVoiceProcessor(1,
+ &fmp,
+ cricket::MPD_RX));
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_TRUE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+
+ EXPECT_TRUE(cm_->UnregisterVoiceProcessor(1,
+ &fmp,
+ cricket::MPD_RX));
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+ EXPECT_TRUE(cm_->RegisterVoiceProcessor(1,
+ &fmp,
+ cricket::MPD_TX));
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_TRUE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+
+ EXPECT_TRUE(cm_->UnregisterVoiceProcessor(1,
+ &fmp,
+ cricket::MPD_TX));
+ EXPECT_FALSE(fme_->video_processor_registered());
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_TX));
+ EXPECT_FALSE(fme_->voice_processor_registered(cricket::MPD_RX));
+}
diff --git a/talk/session/phone/codec_unittest.cc b/talk/session/phone/codec_unittest.cc
new file mode 100644
index 0000000..18e1eb6
--- /dev/null
+++ b/talk/session/phone/codec_unittest.cc
@@ -0,0 +1,222 @@
+/*
+ * libjingle
+ * Copyright 2009--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/codec.h"
+
+using cricket::AudioCodec;
+using cricket::VideoCodec;
+using cricket::VideoEncoderConfig;
+
+class CodecTest : public testing::Test {
+ public:
+ CodecTest() {}
+};
+
+TEST_F(CodecTest, TestAudioCodecOperators) {
+ AudioCodec c0(96, "A", 44100, 20000, 2, 3);
+ AudioCodec c1(95, "A", 44100, 20000, 2, 3);
+ AudioCodec c2(96, "x", 44100, 20000, 2, 3);
+ AudioCodec c3(96, "A", 48000, 20000, 2, 3);
+ AudioCodec c4(96, "A", 44100, 10000, 2, 3);
+ AudioCodec c5(96, "A", 44100, 20000, 1, 3);
+ AudioCodec c6(96, "A", 44100, 20000, 2, 1);
+ EXPECT_TRUE(c0 != c1);
+ EXPECT_TRUE(c0 != c2);
+ EXPECT_TRUE(c0 != c3);
+ EXPECT_TRUE(c0 != c4);
+ EXPECT_TRUE(c0 != c5);
+ EXPECT_TRUE(c0 != c6);
+
+ AudioCodec c7;
+ AudioCodec c8(0, "", 0, 0, 0, 0);
+ AudioCodec c9 = c0;
+ EXPECT_TRUE(c8 == c7);
+ EXPECT_TRUE(c9 != c7);
+ EXPECT_TRUE(c9 == c0);
+
+ AudioCodec c10(c0);
+ AudioCodec c11(c0);
+ AudioCodec c12(c0);
+ AudioCodec c13(c0);
+ c10.params["x"] = "abc";
+ c11.params["x"] = "def";
+ c12.params["y"] = "abc";
+ c13.params["x"] = "abc";
+ EXPECT_TRUE(c10 != c0);
+ EXPECT_TRUE(c11 != c0);
+ EXPECT_TRUE(c11 != c10);
+ EXPECT_TRUE(c12 != c0);
+ EXPECT_TRUE(c12 != c10);
+ EXPECT_TRUE(c12 != c11);
+ EXPECT_TRUE(c13 == c10);
+}
+
+TEST_F(CodecTest, TestAudioCodecMatches) {
+ // Test a codec with a static payload type.
+ AudioCodec c0(95, "A", 44100, 20000, 2, 3);
+ EXPECT_TRUE(c0.Matches(95, ""));
+ EXPECT_FALSE(c0.Matches(96, ""));
+ EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 20000, 2, 0)));
+ EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 20000, 0, 0)));
+ EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 44100, 0, 0, 0)));
+ EXPECT_TRUE(c0.Matches(AudioCodec(95, "", 0, 0, 0, 0)));
+ EXPECT_FALSE(c0.Matches(AudioCodec(96, "", 44100, 20000, 2, 0)));
+ EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 20000, 2, 0)));
+ EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 30000, 2, 0)));
+ EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 44100, 20000, 1, 0)));
+ EXPECT_FALSE(c0.Matches(AudioCodec(95, "", 55100, 30000, 1, 0)));
+
+ // Test a codec with a dynamic payload type.
+ AudioCodec c1(96, "A", 44100, 20000, 2, 3);
+ EXPECT_TRUE(c1.Matches(96, "A"));
+ EXPECT_TRUE(c1.Matches(97, "A"));
+ EXPECT_TRUE(c1.Matches(96, "a"));
+ EXPECT_TRUE(c1.Matches(97, "a"));
+ EXPECT_FALSE(c1.Matches(96, ""));
+ EXPECT_FALSE(c1.Matches(95, "A"));
+ EXPECT_TRUE(c1.Matches(AudioCodec(96, "A", 0, 0, 0, 0)));
+ EXPECT_FALSE(c1.Matches(AudioCodec(96, "", 44100, 20000, 2, 0)));
+ EXPECT_FALSE(c1.Matches(AudioCodec(96, "A", 55100, 30000, 1, 0)));
+
+ // Test a codec with a dynamic payload type, and auto bitrate.
+ AudioCodec c2(97, "A", 16000, 0, 1, 3);
+ // Use default bitrate.
+ EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 0, 1, 0)));
+ EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 0, 0, 0)));
+ // Use explicit bitrate.
+ EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, 32000, 1, 0)));
+ // Backward compatibility with clients that might send "-1" (for default).
+ EXPECT_TRUE(c2.Matches(AudioCodec(97, "A", 16000, -1, 1, 0)));
+}
+
+TEST_F(CodecTest, TestVideoCodecOperators) {
+ VideoCodec c0(96, "V", 320, 200, 30, 3);
+ VideoCodec c1(95, "V", 320, 200, 30, 3);
+ VideoCodec c2(96, "x", 320, 200, 30, 3);
+ VideoCodec c3(96, "V", 120, 200, 30, 3);
+ VideoCodec c4(96, "V", 320, 100, 30, 3);
+ VideoCodec c5(96, "V", 320, 200, 10, 3);
+ VideoCodec c6(96, "V", 320, 200, 30, 1);
+ EXPECT_TRUE(c0 != c1);
+ EXPECT_TRUE(c0 != c2);
+ EXPECT_TRUE(c0 != c3);
+ EXPECT_TRUE(c0 != c4);
+ EXPECT_TRUE(c0 != c5);
+ EXPECT_TRUE(c0 != c6);
+
+ VideoCodec c7;
+ VideoCodec c8(0, "", 0, 0, 0, 0);
+ VideoCodec c9 = c0;
+ EXPECT_TRUE(c8 == c7);
+ EXPECT_TRUE(c9 != c7);
+ EXPECT_TRUE(c9 == c0);
+
+ VideoCodec c10(c0);
+ VideoCodec c11(c0);
+ VideoCodec c12(c0);
+ VideoCodec c13(c0);
+ c10.params["x"] = "abc";
+ c11.params["x"] = "def";
+ c12.params["y"] = "abc";
+ c13.params["x"] = "abc";
+ EXPECT_TRUE(c10 != c0);
+ EXPECT_TRUE(c11 != c0);
+ EXPECT_TRUE(c11 != c10);
+ EXPECT_TRUE(c12 != c0);
+ EXPECT_TRUE(c12 != c10);
+ EXPECT_TRUE(c12 != c11);
+ EXPECT_TRUE(c13 == c10);
+}
+
+TEST_F(CodecTest, TestVideoCodecMatches) {
+ // Test a codec with a static payload type.
+ VideoCodec c0(95, "V", 320, 200, 30, 3);
+ EXPECT_TRUE(c0.Matches(95, ""));
+ EXPECT_FALSE(c0.Matches(96, ""));
+ EXPECT_TRUE(c0.Matches(VideoCodec(95, "", 640, 400, 15, 0)));
+ EXPECT_FALSE(c0.Matches(VideoCodec(96, "", 320, 200, 30, 0)));
+
+ // Test a codec with a dynamic payload type.
+ VideoCodec c1(96, "V", 320, 200, 30, 3);
+ EXPECT_TRUE(c1.Matches(96, "V"));
+ EXPECT_TRUE(c1.Matches(97, "V"));
+ EXPECT_TRUE(c1.Matches(96, "v"));
+ EXPECT_TRUE(c1.Matches(97, "v"));
+ EXPECT_FALSE(c1.Matches(96, ""));
+ EXPECT_FALSE(c1.Matches(95, "V"));
+ EXPECT_TRUE(c1.Matches(VideoCodec(96, "V", 640, 400, 15, 0)));
+ EXPECT_FALSE(c1.Matches(VideoCodec(96, "", 320, 200, 30, 0)));
+}
+
+TEST_F(CodecTest, TestVideoEncoderConfigOperators) {
+ VideoEncoderConfig c1(VideoCodec(
+ 96, "SVC", 320, 200, 30, 3), 1, 2);
+ VideoEncoderConfig c2(VideoCodec(
+ 95, "SVC", 320, 200, 30, 3), 1, 2);
+ VideoEncoderConfig c3(VideoCodec(
+ 96, "xxx", 320, 200, 30, 3), 1, 2);
+ VideoEncoderConfig c4(VideoCodec(
+ 96, "SVC", 120, 200, 30, 3), 1, 2);
+ VideoEncoderConfig c5(VideoCodec(
+ 96, "SVC", 320, 100, 30, 3), 1, 2);
+ VideoEncoderConfig c6(VideoCodec(
+ 96, "SVC", 320, 200, 10, 3), 1, 2);
+ VideoEncoderConfig c7(VideoCodec(
+ 96, "SVC", 320, 200, 30, 1), 1, 2);
+ VideoEncoderConfig c8(VideoCodec(
+ 96, "SVC", 320, 200, 30, 3), 0, 2);
+ VideoEncoderConfig c9(VideoCodec(
+ 96, "SVC", 320, 200, 30, 3), 1, 1);
+ EXPECT_TRUE(c1 != c2);
+ EXPECT_TRUE(c1 != c2);
+ EXPECT_TRUE(c1 != c3);
+ EXPECT_TRUE(c1 != c4);
+ EXPECT_TRUE(c1 != c5);
+ EXPECT_TRUE(c1 != c6);
+ EXPECT_TRUE(c1 != c7);
+ EXPECT_TRUE(c1 != c8);
+ EXPECT_TRUE(c1 != c9);
+
+ VideoEncoderConfig c10;
+ VideoEncoderConfig c11(VideoCodec(
+ 0, "", 0, 0, 0, 0));
+ VideoEncoderConfig c12(VideoCodec(
+ 0, "", 0, 0, 0, 0),
+ VideoEncoderConfig::kDefaultMaxThreads,
+ VideoEncoderConfig::kDefaultCpuProfile);
+ VideoEncoderConfig c13 = c1;
+ VideoEncoderConfig c14(VideoCodec(
+ 0, "", 0, 0, 0, 0), 0, 0);
+
+ EXPECT_TRUE(c11 == c10);
+ EXPECT_TRUE(c12 == c10);
+ EXPECT_TRUE(c13 != c10);
+ EXPECT_TRUE(c13 == c1);
+ EXPECT_TRUE(c14 != c11);
+ EXPECT_TRUE(c14 != c12);
+}
diff --git a/talk/session/phone/currentspeakermonitor.cc b/talk/session/phone/currentspeakermonitor.cc
index bfc20a2..a148b7a 100644
--- a/talk/session/phone/currentspeakermonitor.cc
+++ b/talk/session/phone/currentspeakermonitor.cc
@@ -56,8 +56,8 @@
if (!started_) {
call_->SignalAudioMonitor.connect(
this, &CurrentSpeakerMonitor::OnAudioMonitor);
- call_->SignalMediaSourcesUpdate.connect(
- this, &CurrentSpeakerMonitor::OnMediaSourcesUpdate);
+ call_->SignalMediaStreamsUpdate.connect(
+ this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate);
started_ = true;
}
@@ -66,7 +66,7 @@
void CurrentSpeakerMonitor::Stop() {
if (started_) {
call_->SignalAudioMonitor.disconnect(this);
- call_->SignalMediaSourcesUpdate.disconnect(this);
+ call_->SignalMediaStreamsUpdate.disconnect(this);
started_ = false;
ssrc_to_speaking_state_map_.clear();
@@ -106,8 +106,9 @@
state_it != ssrc_to_speaking_state_map_.end(); ++state_it) {
bool is_previous_speaker = current_speaker_ssrc_ == state_it->first;
- // This uses a state machine in order to gradually identify members as
- // having started or stopped speaking.
+ // This uses a state machine in order to gradually identify
+ // members as having started or stopped speaking. Matches the
+ // algorithm used by the hangouts js code.
std::map<uint32, int>::const_iterator level_it =
active_ssrc_to_level_map.find(state_it->first);
@@ -186,21 +187,20 @@
}
}
-void CurrentSpeakerMonitor::OnMediaSourcesUpdate(Call* call,
+void CurrentSpeakerMonitor::OnMediaStreamsUpdate(Call* call,
Session* session,
- const MediaSources& sources) {
+ const MediaStreams& added,
+ const MediaStreams& removed) {
if (call == call_ && session == session_) {
- // Update the speaking state map based on new or removed sources.
- NamedSources::const_iterator it;
- for (it = sources.audio().begin(); it != sources.audio().end(); it++) {
- if (it->ssrc_set) {
- if (it->removed) {
- ssrc_to_speaking_state_map_.erase(it->ssrc);
- } else if (ssrc_to_speaking_state_map_.find(it->ssrc) ==
- ssrc_to_speaking_state_map_.begin()) {
- ssrc_to_speaking_state_map_[it->ssrc] = SS_NOT_SPEAKING;
- }
- }
+ // Update the speaking state map based on added and removed streams.
+ for (std::vector<cricket::StreamParams>::const_iterator
+ it = removed.video().begin(); it != removed.video().end(); ++it) {
+ ssrc_to_speaking_state_map_.erase(it->first_ssrc());
+ }
+
+ for (std::vector<cricket::StreamParams>::const_iterator
+ it = added.video().begin(); it != added.video().end(); ++it) {
+ ssrc_to_speaking_state_map_[it->first_ssrc()] = SS_NOT_SPEAKING;
}
}
}
diff --git a/talk/session/phone/currentspeakermonitor.h b/talk/session/phone/currentspeakermonitor.h
index 84207fb..7871a82 100644
--- a/talk/session/phone/currentspeakermonitor.h
+++ b/talk/session/phone/currentspeakermonitor.h
@@ -42,7 +42,7 @@
class Call;
class Session;
struct AudioInfo;
-struct MediaSources;
+struct MediaStreams;
// Note that the call's audio monitor must be started before this is started.
// It's recommended that the audio monitor be started with a 100 ms period.
@@ -68,9 +68,10 @@
private:
void OnAudioMonitor(Call* call, const AudioInfo& info);
- void OnMediaSourcesUpdate(Call* call,
+ void OnMediaStreamsUpdate(Call* call,
Session* session,
- const MediaSources& sources);
+ const MediaStreams& added,
+ const MediaStreams& removed);
// These are states that a participant will pass through so that we gradually
// recognize that they have started and stopped speaking. This avoids
diff --git a/talk/session/phone/currentspeakermonitor_unittest.cc b/talk/session/phone/currentspeakermonitor_unittest.cc
new file mode 100644
index 0000000..264fa25
--- /dev/null
+++ b/talk/session/phone/currentspeakermonitor_unittest.cc
@@ -0,0 +1,232 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/currentspeakermonitor.h"
+
+namespace cricket {
+
+static const uint32 kSsrc1 = 1001;
+static const uint32 kSsrc2 = 1002;
+static const uint32 kMinTimeBetweenSwitches = 10;
+// Due to limited system clock resolution, the CurrentSpeakerMonitor may
+// actually require more or less time between switches than that specified
+// in the call to set_min_time_between_switches. To be safe, we sleep for
+// 90 ms more than the min time between switches before checking for a switch.
+// I am assuming system clocks do not have a coarser resolution than 90 ms.
+static const uint32 kSleepTimeBetweenSwitches = 100;
+
+class MockCall : public Call {
+ public:
+ MockCall() : Call(NULL) {}
+
+ void EmitAudioMonitor(const AudioInfo& info) {
+ SignalAudioMonitor(this, info);
+ }
+};
+
+class CurrentSpeakerMonitorTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ CurrentSpeakerMonitorTest() {
+ call_ = new MockCall();
+ monitor_ = new CurrentSpeakerMonitor(call_, NULL);
+ // Shrink the minimum time betweeen switches to 10 ms so we don't have to
+ // slow down our tests.
+ monitor_->set_min_time_between_switches(kMinTimeBetweenSwitches);
+ monitor_->SignalUpdate.connect(this, &CurrentSpeakerMonitorTest::OnUpdate);
+ current_speaker_ = 0;
+ num_changes_ = 0;
+ monitor_->Start();
+ }
+
+ ~CurrentSpeakerMonitorTest() {
+ delete monitor_;
+ delete call_;
+ }
+
+ protected:
+ MockCall* call_;
+ CurrentSpeakerMonitor* monitor_;
+ int num_changes_;
+ uint32 current_speaker_;
+
+ void OnUpdate(CurrentSpeakerMonitor* monitor, uint32 current_speaker) {
+ current_speaker_ = current_speaker;
+ num_changes_++;
+ }
+};
+
+static void InitAudioInfo(AudioInfo* info, int input_level, int output_level) {
+ info->input_level = input_level;
+ info->output_level = output_level;
+}
+
+TEST_F(CurrentSpeakerMonitorTest, NoActiveStreams) {
+ AudioInfo info;
+ InitAudioInfo(&info, 0, 0);
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, 0U);
+ EXPECT_EQ(num_changes_, 0);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, MultipleActiveStreams) {
+ AudioInfo info;
+ InitAudioInfo(&info, 0, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ // No speaker recognized because the initial sample is treated as possibly
+ // just noise and disregarded.
+ EXPECT_EQ(current_speaker_, 0U);
+ EXPECT_EQ(num_changes_, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, RapidSpeakerChange) {
+ AudioInfo info;
+ InitAudioInfo(&info, 0, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, 0U);
+ EXPECT_EQ(num_changes_, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 9));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 1));
+ call_->EmitAudioMonitor(info);
+
+ // We expect no speaker change because of the rapid change.
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, SpeakerChange) {
+ AudioInfo info;
+ InitAudioInfo(&info, 0, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, 0U);
+ EXPECT_EQ(num_changes_, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ // Wait so the changes don't come so rapidly.
+ talk_base::Thread::SleepMs(kSleepTimeBetweenSwitches);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 9));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 1));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc1);
+ EXPECT_EQ(num_changes_, 2);
+}
+
+TEST_F(CurrentSpeakerMonitorTest, InterwordSilence) {
+ AudioInfo info;
+ InitAudioInfo(&info, 0, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, 0U);
+ EXPECT_EQ(num_changes_, 0);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 7));
+ call_->EmitAudioMonitor(info);
+
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ // Wait so the changes don't come so rapidly.
+ talk_base::Thread::SleepMs(kSleepTimeBetweenSwitches);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+ call_->EmitAudioMonitor(info);
+
+ // Current speaker shouldn't have changed because we treat this as an inter-
+ // word silence.
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+ call_->EmitAudioMonitor(info);
+
+ // Current speaker shouldn't have changed because we treat this as an inter-
+ // word silence.
+ EXPECT_EQ(current_speaker_, kSsrc2);
+ EXPECT_EQ(num_changes_, 1);
+
+ info.active_streams.push_back(std::make_pair(kSsrc1, 3));
+ info.active_streams.push_back(std::make_pair(kSsrc2, 0));
+ call_->EmitAudioMonitor(info);
+
+ // At this point, we should have concluded that SSRC2 stopped speaking.
+ EXPECT_EQ(current_speaker_, kSsrc1);
+ EXPECT_EQ(num_changes_, 2);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
index 6188461..67dfe41 100644
--- a/talk/session/phone/devicemanager.cc
+++ b/talk/session/phone/devicemanager.cc
@@ -27,40 +27,6 @@
#include "talk/session/phone/devicemanager.h"
-#if WIN32
-#include <atlbase.h>
-#include <dbt.h>
-#include <strmif.h> // must come before ks.h
-#include <ks.h>
-#include <ksmedia.h>
-#define INITGUID // For PKEY_AudioEndpoint_GUID
-#include <mmdeviceapi.h>
-#include <mmsystem.h>
-#include <functiondiscoverykeys_devpkey.h>
-#include <uuids.h>
-#include "talk/base/win32.h" // ToUtf8
-#include "talk/base/win32window.h"
-#elif OSX
-#include <CoreAudio/CoreAudio.h>
-#include <QuickTime/QuickTime.h>
-#elif LINUX
-#include <libudev.h>
-#include <unistd.h>
-#include "talk/base/linux.h"
-#include "talk/base/fileutils.h"
-#include "talk/base/pathutils.h"
-#include "talk/base/physicalsocketserver.h"
-#include "talk/base/stream.h"
-#include "talk/session/phone/libudevsymboltable.h"
-#include "talk/session/phone/v4llookup.h"
-#if !defined(NO_SOUND_SYSTEM)
-#include "talk/sound/platformsoundsystem.h"
-#include "talk/sound/platformsoundsystemfactory.h"
-#include "talk/sound/sounddevicelocator.h"
-#include "talk/sound/soundsysteminterface.h"
-#endif // !defined(NO_SOUND_SYSTEM)
-#endif
-
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
#include "talk/base/thread.h"
@@ -70,135 +36,30 @@
// Initialize to empty string.
const char DeviceManagerInterface::kDefaultDeviceName[] = "";
-DeviceManagerInterface* DeviceManagerFactory::Create() {
- return new DeviceManager();
-}
-
-#ifdef WIN32
-class DeviceWatcher : public talk_base::Win32Window {
- public:
- explicit DeviceWatcher(DeviceManager* dm);
- bool Start();
- void Stop();
-
- private:
- HDEVNOTIFY Register(REFGUID guid);
- void Unregister(HDEVNOTIFY notify);
- virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
-
- DeviceManager* manager_;
- HDEVNOTIFY audio_notify_;
- HDEVNOTIFY video_notify_;
-};
-#elif defined(LINUX)
-class DeviceWatcher : private talk_base::Dispatcher {
- public:
- explicit DeviceWatcher(DeviceManager* dm);
- bool Start();
- void Stop();
-
- private:
- virtual uint32 GetRequestedEvents();
- virtual void OnPreEvent(uint32 ff);
- virtual void OnEvent(uint32 ff, int err);
- virtual int GetDescriptor();
- virtual bool IsDescriptorClosed();
-
- DeviceManager* manager_;
- LibUDevSymbolTable libudev_;
- struct udev* udev_;
- struct udev_monitor* udev_monitor_;
- bool registered_;
-};
-#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
-#elif defined(OSX)
-class DeviceWatcher {
- public:
- explicit DeviceWatcher(DeviceManager* dm);
- bool Start();
- void Stop();
- private:
- DeviceManager* manager_;
- void* impl_;
-};
-#endif
-
-#if defined(CHROMEOS)
-static bool ShouldAudioDeviceBeIgnored(const std::string& device_name);
-#endif
-#if !defined(LINUX) && !defined(IOS)
-static bool ShouldVideoDeviceBeIgnored(const std::string& device_name);
-#endif
-#ifndef OSX
-static bool GetVideoDevices(std::vector<Device>* out);
-#endif
-#if WIN32
-static const wchar_t kFriendlyName[] = L"FriendlyName";
-static const wchar_t kDevicePath[] = L"DevicePath";
-static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
-static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
-static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
-static bool GetWaveDevices(bool input, std::vector<Device>* devs);
-#elif OSX
-static const int kVideoDeviceOpenAttempts = 3;
-static const UInt32 kAudioDeviceNameLength = 64;
-// Obj-C functions defined in devicemanager-mac.mm
-extern void* CreateDeviceWatcherCallback(DeviceManager* dm);
-extern void ReleaseDeviceWatcherCallback(void* impl);
-extern bool GetQTKitVideoDevices(std::vector<Device>* out);
-static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
-static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
-#endif
-
DeviceManager::DeviceManager()
- : initialized_(false),
-#if defined(WIN32)
- need_couninitialize_(false),
-#endif
- watcher_(new DeviceWatcher(this))
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
- , sound_system_(new PlatformSoundSystemFactory())
-#endif
- {
+ : initialized_(false) {
}
DeviceManager::~DeviceManager() {
- if (initialized_) {
+ if (initialized()) {
Terminate();
}
- delete watcher_;
}
bool DeviceManager::Init() {
- if (!initialized_) {
-#if defined(WIN32)
- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- need_couninitialize_ = SUCCEEDED(hr);
- if (FAILED(hr)) {
- LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
- if (hr != RPC_E_CHANGED_MODE) {
- return false;
- }
- }
-#endif
- if (!watcher_->Start()) {
+ if (!initialized()) {
+ if (!watcher()->Start()) {
return false;
}
- initialized_ = true;
+ set_initialized(true);
}
return true;
}
void DeviceManager::Terminate() {
- if (initialized_) {
- watcher_->Stop();
-#if defined(WIN32)
- if (need_couninitialize_) {
- CoUninitialize();
- need_couninitialize_ = false;
- }
-#endif
- initialized_ = false;
+ if (initialized()) {
+ watcher()->Stop();
+ set_initialized(false);
}
}
@@ -218,11 +79,11 @@
}
bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) {
- return GetAudioDevicesByPlatform(true, devices);
+ return GetAudioDevices(true, devices);
}
bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) {
- return GetAudioDevicesByPlatform(false, devices);
+ return GetAudioDevices(false, devices);
}
bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) {
@@ -233,55 +94,20 @@
return GetAudioDevice(false, name, out);
}
-#ifdef OSX
-static bool FilterDevice(const Device& d) {
- return ShouldVideoDeviceBeIgnored(d.name);
-}
-#endif
-
bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
devices->clear();
-#ifdef OSX
- if (GetQTKitVideoDevices(devices)) {
- // Now filter out any known incompatible devices
- devices->erase(remove_if(devices->begin(), devices->end(), FilterDevice),
- devices->end());
- return true;
- }
+#if defined(ANDROID) || defined(IOS)
+ // TODO: Incomplete. Use ANDROID implementation for IOS
+ // to quiet compiler.
+ // On Android, we treat the camera(s) as a single device. Even if there are
+ // multiple cameras, that's abstracted away at a higher level.
+ Device dev("camera", "1"); // name and ID
+ devices->push_back(dev);
+#else
return false;
-#else
- return GetVideoDevices(devices);
#endif
}
-bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
- bool ret = false;
-#if WIN32
- // If there are multiple capture devices, we want the first USB one.
- // This avoids issues with defaulting to virtual cameras or grabber cards.
- std::vector<Device> devices;
- ret = (GetVideoDevices(&devices) && !devices.empty());
- if (ret) {
- *device = devices[0];
- for (size_t i = 0; i < devices.size(); ++i) {
- if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
- ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
- *device = devices[i];
- break;
- }
- }
- }
-#else
- // We just return the first device.
- std::vector<Device> devices;
- ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
- if (ret) {
- *device = devices[0];
- }
-#endif
- return ret;
-}
-
bool DeviceManager::GetVideoCaptureDevice(const std::string& name,
Device* out) {
// If the name is empty, return the default device.
@@ -305,6 +131,25 @@
return false;
}
+bool DeviceManager::GetAudioDevices(bool input,
+ std::vector<Device>* devs) {
+ devs->clear();
+#ifdef ANDROID
+ // Under Android, we don't access the device file directly.
+ // Arbitrary use 0 for the mic and 1 for the output.
+ // These ids are used in MediaEngine::SetSoundDevices(in, out);
+ // The strings are for human consumption.
+ if (input) {
+ devs->push_back(Device("audiorecord", 0));
+ } else {
+ devs->push_back(Device("audiotrack", 1));
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
bool DeviceManager::GetAudioDevice(bool is_input, const std::string& name,
Device* out) {
// If the name is empty, return the default device id.
@@ -329,695 +174,50 @@
return ret;
}
-bool DeviceManager::GetAudioDevicesByPlatform(bool input,
- std::vector<Device>* devs) {
- devs->clear();
-
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
- if (!sound_system_.get()) {
- return false;
- }
- SoundSystemInterface::SoundDeviceLocatorList list;
- bool success;
- if (input) {
- success = sound_system_->EnumerateCaptureDevices(&list);
- } else {
- success = sound_system_->EnumeratePlaybackDevices(&list);
- }
- if (!success) {
- LOG(LS_ERROR) << "Can't enumerate devices";
- sound_system_.release();
- return false;
- }
- // We have to start the index at 1 because GIPS VoiceEngine puts the default
- // device at index 0, but Enumerate(Capture|Playback)Devices does not include
- // a locator for the default device.
- int index = 1;
- for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
- i != list.end();
- ++i, ++index) {
-#if defined(CHROMEOS)
- // On ChromeOS, we ignore ALSA surround and S/PDIF devices.
- if (!ShouldAudioDeviceBeIgnored((*i)->device_name())) {
-#endif
- devs->push_back(Device((*i)->name(), index));
-#if defined(CHROMEOS)
- }
-#endif
- }
- SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
- sound_system_.release();
- return true;
-
-#elif defined(WIN32)
- if (talk_base::IsWindowsVistaOrLater()) {
- return GetCoreAudioDevices(input, devs);
- } else {
- return GetWaveDevices(input, devs);
- }
-
-#elif defined(OSX)
- std::vector<AudioDeviceID> dev_ids;
- bool ret = GetAudioDeviceIDs(input, &dev_ids);
+bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+ bool ret = false;
+ // We just return the first device.
+ std::vector<Device> devices;
+ ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
if (ret) {
- for (size_t i = 0; i < dev_ids.size(); ++i) {
- std::string name;
- if (GetAudioDeviceName(dev_ids[i], input, &name)) {
- devs->push_back(Device(name, dev_ids[i]));
- }
- }
+ *device = devices[0];
}
return ret;
-
-#else
- return false;
-#endif
}
-#if defined(WIN32)
-bool GetVideoDevices(std::vector<Device>* devices) {
- return GetDevices(CLSID_VideoInputDeviceCategory, devices);
-}
-
-bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
- HRESULT hr;
-
- // CComPtr is a scoped pointer that will be auto released when going
- // out of scope. CoUninitialize must not be called before the
- // release.
- CComPtr<ICreateDevEnum> sys_dev_enum;
- CComPtr<IEnumMoniker> cam_enum;
- if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
- FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
- LOG(LS_ERROR) << "Failed to create device enumerator, hr=" << hr;
+bool DeviceManager::ShouldDeviceBeIgnored(const std::string& device_name,
+ const char* const exclusion_list[]) {
+ // If exclusion_list is empty return directly.
+ if (!exclusion_list)
return false;
- }
- // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
- // devices available, S_FALSE will be returned, but enumMk will be NULL.
- if (hr == S_OK) {
- CComPtr<IMoniker> mk;
- while (cam_enum->Next(1, &mk, NULL) == S_OK) {
- CComPtr<IPropertyBag> bag;
- if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
- __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
- CComVariant name, path;
- std::string name_str, path_str;
- if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
- name.vt == VT_BSTR) {
- name_str = talk_base::ToUtf8(name.bstrVal);
- if (!ShouldVideoDeviceBeIgnored(name_str)) {
- // Get the device id if one exists.
- if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
- path.vt == VT_BSTR) {
- path_str = talk_base::ToUtf8(path.bstrVal);
- }
-
- devices->push_back(Device(name_str, path_str));
- }
- }
- }
- mk = NULL;
+ int i = 0;
+ while (exclusion_list[i]) {
+ if (strnicmp(device_name.c_str(), exclusion_list[i],
+ strlen(exclusion_list[i])) == 0) {
+ LOG(LS_INFO) << "Ignoring device " << device_name;
+ return true;
}
+ ++i;
}
-
- return true;
-}
-
-HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
- out->clear();
- PROPVARIANT var;
- PropVariantInit(&var);
-
- HRESULT hr = bag->GetValue(key, &var);
- if (SUCCEEDED(hr)) {
- if (var.pwszVal)
- *out = talk_base::ToUtf8(var.pwszVal);
- else
- hr = E_FAIL;
- }
-
- PropVariantClear(&var);
- return hr;
-}
-
-// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
-HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
- CComPtr<IPropertyStore> props;
-
- HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
- if (FAILED(hr)) {
- return hr;
- }
-
- // Get the endpoint's name and id.
- std::string name, guid;
- hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
- if (SUCCEEDED(hr)) {
- hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
-
- if (SUCCEEDED(hr)) {
- out->name = name;
- out->id = guid;
- }
- }
- return hr;
-}
-
-bool GetCoreAudioDevices(bool input, std::vector<Device>* devs) {
- HRESULT hr = S_OK;
- CComPtr<IMMDeviceEnumerator> enumerator;
-
- hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
- __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
- if (SUCCEEDED(hr)) {
- CComPtr<IMMDeviceCollection> devices;
- hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
- DEVICE_STATE_ACTIVE, &devices);
- if (SUCCEEDED(hr)) {
- unsigned int count;
- hr = devices->GetCount(&count);
-
- if (SUCCEEDED(hr)) {
- for (unsigned int i = 0; i < count; i++) {
- CComPtr<IMMDevice> device;
-
- // Get pointer to endpoint number i.
- hr = devices->Item(i, &device);
- if (FAILED(hr)) {
- break;
- }
-
- Device dev;
- hr = CricketDeviceFromImmDevice(device, &dev);
- if (SUCCEEDED(hr)) {
- devs->push_back(dev);
- } else {
- LOG(LS_WARNING) << "Unable to query IMM Device, skipping. HR="
- << hr;
- hr = S_FALSE;
- }
- }
- }
- }
- }
-
- if (FAILED(hr)) {
- LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
- return false;
- }
- return true;
-}
-
-bool GetWaveDevices(bool input, std::vector<Device>* devs) {
- // Note, we don't use the System Device Enumerator interface here since it
- // adds lots of pseudo-devices to the list, such as DirectSound and Wave
- // variants of the same device.
- if (input) {
- int num_devs = waveInGetNumDevs();
- for (int i = 0; i < num_devs; ++i) {
- WAVEINCAPS caps;
- if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
- caps.wChannels > 0) {
- devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
- talk_base::ToString(i)));
- }
- }
- } else {
- int num_devs = waveOutGetNumDevs();
- for (int i = 0; i < num_devs; ++i) {
- WAVEOUTCAPS caps;
- if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
- caps.wChannels > 0) {
- devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
- }
- }
- }
- return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* manager)
- : manager_(manager), audio_notify_(NULL), video_notify_(NULL) {
-}
-
-bool DeviceWatcher::Start() {
- if (!Create(NULL, _T("libjingle DeviceWatcher Window"),
- 0, 0, 0, 0, 0, 0)) {
- return false;
- }
-
- audio_notify_ = Register(KSCATEGORY_AUDIO);
- if (!audio_notify_) {
- Stop();
- return false;
- }
-
- video_notify_ = Register(KSCATEGORY_VIDEO);
- if (!video_notify_) {
- Stop();
- return false;
- }
-
- return true;
-}
-
-void DeviceWatcher::Stop() {
- UnregisterDeviceNotification(video_notify_);
- video_notify_ = NULL;
- UnregisterDeviceNotification(audio_notify_);
- audio_notify_ = NULL;
- Destroy();
-}
-
-HDEVNOTIFY DeviceWatcher::Register(REFGUID guid) {
- DEV_BROADCAST_DEVICEINTERFACE dbdi;
- dbdi.dbcc_size = sizeof(dbdi);
- dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
- dbdi.dbcc_classguid = guid;
- dbdi.dbcc_name[0] = '\0';
- return RegisterDeviceNotification(handle(), &dbdi,
- DEVICE_NOTIFY_WINDOW_HANDLE);
-}
-
-void DeviceWatcher::Unregister(HDEVNOTIFY handle) {
- UnregisterDeviceNotification(handle);
-}
-
-bool DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
- LRESULT& result) {
- if (uMsg == WM_DEVICECHANGE) {
- if (wParam == DBT_DEVICEARRIVAL ||
- wParam == DBT_DEVICEREMOVECOMPLETE) {
- DEV_BROADCAST_DEVICEINTERFACE* dbdi =
- reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
- if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
- dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
- manager_->OnDevicesChange();
- }
- }
- result = 0;
- return true;
- }
-
return false;
}
-#elif defined(OSX)
-static bool GetAudioDeviceIDs(bool input,
- std::vector<AudioDeviceID>* out_dev_ids) {
- UInt32 propsize;
- OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
- &propsize, NULL);
- if (0 != err) {
- LOG(LS_ERROR) << "Couldn't get information about property, "
- << "so no device list acquired.";
+
+bool DeviceManager::FilterDevices(std::vector<Device>* devices,
+ const char* const exclusion_list[]) {
+ if (!devices) {
return false;
}
- size_t num_devices = propsize / sizeof(AudioDeviceID);
- talk_base::scoped_array<AudioDeviceID> device_ids(
- new AudioDeviceID[num_devices]);
-
- err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
- &propsize, device_ids.get());
- if (0 != err) {
- LOG(LS_ERROR) << "Failed to get device ids, "
- << "so no device listing acquired.";
- return false;
- }
-
- for (size_t i = 0; i < num_devices; ++i) {
- AudioDeviceID an_id = device_ids[i];
- // find out the number of channels for this direction
- // (input/output) on this device -
- // we'll ignore anything with no channels.
- err = AudioDeviceGetPropertyInfo(an_id, 0, input,
- kAudioDevicePropertyStreams,
- &propsize, NULL);
- if (0 == err) {
- unsigned num_channels = propsize / sizeof(AudioStreamID);
- if (0 < num_channels) {
- out_dev_ids->push_back(an_id);
- }
+ for (std::vector<Device>::iterator it = devices->begin();
+ it != devices->end(); ) {
+ if (ShouldDeviceBeIgnored(it->name, exclusion_list)) {
+ it = devices->erase(it);
} else {
- LOG(LS_ERROR) << "No property info for stream property for device id "
- << an_id << "(is_input == " << input
- << "), so not including it in the list.";
+ ++it;
}
}
-
return true;
}
-static bool GetAudioDeviceName(AudioDeviceID id,
- bool input,
- std::string* out_name) {
- UInt32 nameLength = kAudioDeviceNameLength;
- char name[kAudioDeviceNameLength + 1];
- OSErr err = AudioDeviceGetProperty(id, 0, input,
- kAudioDevicePropertyDeviceName,
- &nameLength, name);
- if (0 != err) {
- LOG(LS_ERROR) << "No name acquired for device id " << id;
- return false;
- }
-
- *out_name = name;
- return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* manager)
- : manager_(manager), impl_(NULL) {
-}
-
-bool DeviceWatcher::Start() {
- if (!impl_) {
- impl_ = CreateDeviceWatcherCallback(manager_);
- }
- return impl_ != NULL;
-}
-
-void DeviceWatcher::Stop() {
- if (impl_) {
- ReleaseDeviceWatcherCallback(impl_);
- impl_ = NULL;
- }
-}
-
-#elif defined(LINUX)
-static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
-static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
-
-enum MetaType { M2_4, M2_6, NONE };
-
-static void ScanDeviceDirectory(const std::string& devdir,
- std::vector<Device>* devices) {
- talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
- talk_base::Filesystem::IterateDirectory());
-
- if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
- do {
- std::string filename = directoryIterator->Name();
- std::string device_name = devdir + filename;
- if (!directoryIterator->IsDots()) {
- if (filename.find("video") == 0 &&
- V4LLookup::IsV4L2Device(device_name)) {
- devices->push_back(Device(device_name, device_name));
- }
- }
- } while (directoryIterator->Next());
- }
-}
-
-static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
- std::string device_name;
-
- talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
- talk_base::Filesystem::OpenFile(device_meta_path, "r"));
-
- if (device_meta_stream.get() != NULL) {
- if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
- LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
- }
- device_meta_stream->Close();
- }
-
- return device_name;
-}
-
-static std::string Trim(const std::string& s, const std::string& drop = " \t") {
- std::string::size_type first = s.find_first_not_of(drop);
- std::string::size_type last = s.find_last_not_of(drop);
-
- if (first == std::string::npos || last == std::string::npos)
- return std::string("");
-
- return s.substr(first, last - first + 1);
-}
-
-static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
- talk_base::ConfigParser::MapVector all_values;
-
- talk_base::ConfigParser config_parser;
- talk_base::FileStream* file_stream =
- talk_base::Filesystem::OpenFile(device_meta_path, "r");
-
- if (file_stream == NULL) return "";
-
- config_parser.Attach(file_stream);
- config_parser.Parse(&all_values);
-
- for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
- i != all_values.end(); ++i) {
- talk_base::ConfigParser::SimpleMap::iterator device_name_i =
- i->find("name");
-
- if (device_name_i != i->end()) {
- return device_name_i->second;
- }
- }
-
- return "";
-}
-
-static std::string GetVideoDeviceName(MetaType meta,
- const std::string& device_file_name) {
- std::string device_meta_path;
- std::string device_name;
- std::string meta_file_path;
-
- if (meta == M2_6) {
- meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
-
- LOG(LS_INFO) << "Trying " + meta_file_path;
- device_name = GetVideoDeviceNameK2_6(meta_file_path);
-
- if (device_name.empty()) {
- meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
-
- LOG(LS_INFO) << "Trying " << meta_file_path;
- device_name = GetVideoDeviceNameK2_6(meta_file_path);
- }
- } else {
- meta_file_path = kVideoMetaPathK2_4 + device_file_name;
- LOG(LS_INFO) << "Trying " << meta_file_path;
- device_name = GetVideoDeviceNameK2_4(meta_file_path);
- }
-
- if (device_name.empty()) {
- device_name = "/dev/" + device_file_name;
- LOG(LS_ERROR)
- << "Device name not found, defaulting to device path " << device_name;
- }
-
- LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
-
- return Trim(device_name);
-}
-
-static void ScanV4L2Devices(std::vector<Device>* devices) {
- LOG(LS_INFO) << ("Enumerating V4L2 devices");
-
- MetaType meta;
- std::string metadata_dir;
-
- talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
- talk_base::Filesystem::IterateDirectory());
-
- // Try and guess kernel version
- if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
- meta = M2_6;
- metadata_dir = kVideoMetaPathK2_6;
- } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
- meta = M2_4;
- metadata_dir = kVideoMetaPathK2_4;
- } else {
- meta = NONE;
- }
-
- if (meta != NONE) {
- LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
-
- do {
- std::string filename = directoryIterator->Name();
-
- if (filename.find("video") == 0) {
- std::string device_path = "/dev/" + filename;
-
- if (V4LLookup::IsV4L2Device(device_path)) {
- devices->push_back(
- Device(GetVideoDeviceName(meta, filename), device_path));
- }
- }
- } while (directoryIterator->Next());
- } else {
- LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
- }
-
- if (devices->size() == 0) {
- LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
- ScanDeviceDirectory("/dev/", devices);
- }
-
- LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
-}
-
-static bool GetVideoDevices(std::vector<Device>* devices) {
- ScanV4L2Devices(devices);
- return true;
-}
-
-DeviceWatcher::DeviceWatcher(DeviceManager* dm)
- : manager_(dm), udev_(NULL), udev_monitor_(NULL), registered_(false) {}
-
-bool DeviceWatcher::Start() {
- // We deliberately return true in the failure paths here because libudev is
- // not a critical component of a Linux system so it may not be present/usable,
- // and we don't want to halt DeviceManager initialization in such a case.
- if (!libudev_.Load()) {
- LOG(LS_WARNING) << "libudev not present/usable; DeviceWatcher disabled";
- return true;
- }
- udev_ = LATE(udev_new)();
- if (!udev_) {
- LOG_ERR(LS_ERROR) << "udev_new()";
- return true;
- }
- // The second argument here is the event source. It can be either "kernel" or
- // "udev", but "udev" is the only correct choice. Apps listen on udev and the
- // udev daemon in turn listens on the kernel.
- udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
- if (!udev_monitor_) {
- LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
- return true;
- }
- // We only listen for changes in the video devices. Audio devices are more or
- // less unimportant because receiving device change notifications really only
- // matters for broadcasting updated send/recv capabilities based on whether
- // there is at least one device available, and almost all computers have at
- // least one audio device. Also, PulseAudio device notifications don't come
- // from the udev daemon, they come from the PulseAudio daemon, so we'd only
- // want to listen for audio device changes from udev if using ALSA. For
- // simplicity, we don't bother with any audio stuff at all.
- if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
- "video4linux",
- NULL) < 0) {
- LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
- return true;
- }
- if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
- LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
- return true;
- }
- static_cast<talk_base::PhysicalSocketServer*>(
- talk_base::Thread::Current()->socketserver())->Add(this);
- registered_ = true;
- return true;
-}
-
-void DeviceWatcher::Stop() {
- if (registered_) {
- static_cast<talk_base::PhysicalSocketServer*>(
- talk_base::Thread::Current()->socketserver())->Remove(this);
- registered_ = false;
- }
- if (udev_monitor_) {
- LATE(udev_monitor_unref)(udev_monitor_);
- udev_monitor_ = NULL;
- }
- if (udev_) {
- LATE(udev_unref)(udev_);
- udev_ = NULL;
- }
- libudev_.Unload();
-}
-
-uint32 DeviceWatcher::GetRequestedEvents() {
- return talk_base::DE_READ;
-}
-
-void DeviceWatcher::OnPreEvent(uint32 ff) {
- // Nothing to do.
-}
-
-void DeviceWatcher::OnEvent(uint32 ff, int err) {
- udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
- if (!device) {
- // Probably the socket connection to the udev daemon was terminated (perhaps
- // the daemon crashed or is being restarted?).
- LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
- // Stop listening to avoid potential livelock (an fd with EOF in it is
- // always considered readable).
- static_cast<talk_base::PhysicalSocketServer*>(
- talk_base::Thread::Current()->socketserver())->Remove(this);
- registered_ = false;
- return;
- }
- // Else we read the device successfully.
-
- // Since we already have our own filesystem-based device enumeration code, we
- // simply re-enumerate rather than inspecting the device event.
- LATE(udev_device_unref)(device);
- manager_->OnDevicesChange();
-}
-
-int DeviceWatcher::GetDescriptor() {
- return LATE(udev_monitor_get_fd)(udev_monitor_);
-}
-
-bool DeviceWatcher::IsDescriptorClosed() {
- // If it is closed then we will just get an error in
- // udev_monitor_receive_device and unregister, so we don't need to check for
- // it separately.
- return false;
-}
-
-#endif
-
-#if defined(CHROMEOS)
-// Checks if we want to ignore this audio device.
-static bool ShouldAudioDeviceBeIgnored(const std::string& device_name) {
- static const char* const kFilteredAudioDevicesName[] = {
- "surround40:",
- "surround41:",
- "surround50:",
- "surround51:",
- "surround71:",
- "iec958:" // S/PDIF
- };
- for (int i = 0; i < ARRAY_SIZE(kFilteredAudioDevicesName); ++i) {
- if (0 == device_name.find(kFilteredAudioDevicesName[i])) {
- LOG(LS_INFO) << "Ignoring device " << device_name;
- return true;
- }
- }
- return false;
-}
-#endif
-
-// TODO: Try to get hold of a copy of Final Cut to understand why we
-// crash while scanning their components on OS X.
-#if !defined(LINUX) && !defined(IOS)
-static bool ShouldVideoDeviceBeIgnored(const std::string& device_name) {
- static const char* const kFilteredVideoDevicesName[] = {
- "Google Camera Adapter", // Our own magiccams
-#ifdef WIN32
- "Asus virtual Camera", // Bad Asus desktop virtual cam
- "Bluetooth Video", // Bad Sony viao bluetooth sharing driver
-#elif OSX
- "DVCPRO HD", // Final cut
- "Sonix SN9C201p", // Crashes in OpenAComponent and CloseComponent
-#endif
- };
-
- for (int i = 0; i < ARRAY_SIZE(kFilteredVideoDevicesName); ++i) {
- if (strnicmp(device_name.c_str(), kFilteredVideoDevicesName[i],
- strlen(kFilteredVideoDevicesName[i])) == 0) {
- LOG(LS_INFO) << "Ignoring device " << device_name;
- return true;
- }
- }
- return false;
-}
-#endif
-
-}; // namespace cricket
+} // namespace cricket
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
index d846ee5..3cf3554 100644
--- a/talk/session/phone/devicemanager.h
+++ b/talk/session/phone/devicemanager.h
@@ -31,26 +31,12 @@
#include <string>
#include <vector>
+#include "talk/base/scoped_ptr.h"
#include "talk/base/sigslot.h"
#include "talk/base/stringencode.h"
-#ifdef BUILD_WITH_CHROMIUM
-// The SoundSystem related code refers to some definitions that are not
-// available in chromium. (LS_VERBOSE, DISALLOW_ASSIGN etc.)
-// For now, disable the sound system code from devicemanager.h/cc.
-// TODO: Split the DeviceManager implemenations out of
-// devicemanager.h/cc so that we can exclude the DeviceManager impls from
-// libjingle build when they are not needed.
-#define NO_SOUND_SYSTEM
-#endif
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
-#include "talk/sound/soundsystemfactory.h"
-#endif
-
namespace cricket {
-class DeviceWatcher;
-
// Used to represent an audio or video capture or render device.
struct Device {
Device() {}
@@ -93,6 +79,14 @@
static const char kDefaultDeviceName[];
};
+class DeviceWatcher {
+ public:
+ explicit DeviceWatcher(DeviceManagerInterface* dm) {}
+ virtual ~DeviceWatcher() {}
+ virtual bool Start() { return true; }
+ virtual void Stop() {}
+};
+
class DeviceManagerFactory {
public:
static DeviceManagerInterface* Create();
@@ -122,25 +116,28 @@
virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
virtual bool GetVideoCaptureDevice(const std::string& name, Device* out);
+ // The exclusion_list MUST be a NULL terminated list.
+ static bool FilterDevices(std::vector<Device>* devices,
+ const char* const exclusion_list[]);
bool initialized() const { return initialized_; }
- void OnDevicesChange() { SignalDevicesChange(); }
protected:
+ virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
virtual bool GetAudioDevice(bool is_input, const std::string& name,
Device* out);
virtual bool GetDefaultVideoCaptureDevice(Device* device);
- private:
- bool GetAudioDevicesByPlatform(bool input, std::vector<Device>* devs);
+ void set_initialized(bool initialized) { initialized_ = initialized; }
+ void set_watcher(DeviceWatcher* watcher) { watcher_.reset(watcher); }
+ DeviceWatcher* watcher() { return watcher_.get(); }
+
+ private:
+ // The exclusion_list MUST be a NULL terminated list.
+ static bool ShouldDeviceBeIgnored(const std::string& device_name,
+ const char* const exclusion_list[]);
bool initialized_;
-#ifdef WIN32
- bool need_couninitialize_;
-#endif
- DeviceWatcher* watcher_;
-#if defined(LINUX) && !defined(NO_SOUND_SYSTEM)
- SoundSystemHandle sound_system_;
-#endif
+ talk_base::scoped_ptr<DeviceWatcher> watcher_;
};
} // namespace cricket
diff --git a/talk/session/phone/devicemanager_unittest.cc b/talk/session/phone/devicemanager_unittest.cc
new file mode 100644
index 0000000..a64eab9
--- /dev/null
+++ b/talk/session/phone/devicemanager_unittest.cc
@@ -0,0 +1,312 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <objbase.h>
+#endif
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/v4llookup.h"
+
+#ifdef LINUX
+// TODO: Figure out why this doesn't compile on Windows.
+#include "talk/base/fileutils_mock.h"
+#endif // LINUX
+
+#include "talk/session/phone/devicemanager.h"
+
+using talk_base::Pathname;
+using talk_base::FileTimeType;
+using talk_base::scoped_ptr;
+using cricket::Device;
+using cricket::DeviceManager;
+using cricket::DeviceManagerFactory;
+using cricket::DeviceManagerInterface;
+
+// Test that we startup/shutdown properly.
+TEST(DeviceManagerTest, StartupShutdown) {
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ EXPECT_TRUE(dm->Init());
+ dm->Terminate();
+}
+
+// Test CoInitEx behavior
+#ifdef WIN32
+TEST(DeviceManagerTest, CoInitialize) {
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> devices;
+ // Ensure that calls to video device work if COM is not yet initialized.
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&devices));
+ dm->Terminate();
+ // Ensure that the ref count is correct.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+ CoUninitialize();
+ // Ensure that Init works in COINIT_APARTMENTTHREADED setting.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+ EXPECT_TRUE(dm->Init());
+ dm->Terminate();
+ CoUninitialize();
+ // Ensure that the ref count is correct.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+ CoUninitialize();
+ // Ensure that Init works in COINIT_MULTITHREADED setting.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+ EXPECT_TRUE(dm->Init());
+ dm->Terminate();
+ CoUninitialize();
+ // Ensure that the ref count is correct.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+ CoUninitialize();
+}
+#endif
+
+// Test enumerating devices (although we may not find any).
+TEST(DeviceManagerTest, GetDevices) {
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> audio_ins, audio_outs, video_ins;
+ std::vector<cricket::Device> video_in_devs;
+ cricket::Device def_video;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins));
+ EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs));
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_in_devs));
+ EXPECT_EQ(video_ins.size(), video_in_devs.size());
+ // If we have any video devices, we should be able to pick a default.
+ EXPECT_TRUE(dm->GetVideoCaptureDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &def_video)
+ != video_ins.empty());
+}
+
+// Test that we return correct ids for default and bogus devices.
+TEST(DeviceManagerTest, GetAudioDeviceIds) {
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ Device device;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetAudioInputDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+ EXPECT_EQ("-1", device.id);
+ EXPECT_TRUE(dm->GetAudioOutputDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+ EXPECT_EQ("-1", device.id);
+ EXPECT_FALSE(dm->GetAudioInputDevice("_NOT A REAL DEVICE_", &device));
+ EXPECT_FALSE(dm->GetAudioOutputDevice("_NOT A REAL DEVICE_", &device));
+}
+
+// Test that we get the video capture device by name properly.
+TEST(DeviceManagerTest, GetVideoDeviceIds) {
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ Device device;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_FALSE(dm->GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device));
+ std::vector<Device> video_ins;
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ if (!video_ins.empty()) {
+ // Get the default device with the parameter kDefaultDeviceName.
+ EXPECT_TRUE(dm->GetVideoCaptureDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+
+ // Get the first device with the parameter video_ins[0].name.
+ EXPECT_TRUE(dm->GetVideoCaptureDevice(video_ins[0].name, &device));
+ EXPECT_EQ(device.name, video_ins[0].name);
+ EXPECT_EQ(device.id, video_ins[0].id);
+ }
+}
+
+TEST(DeviceManagerTest, VerifyDevicesListsAreCleared) {
+ const std::string imaginary("_NOT A REAL DEVICE_");
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> audio_ins, audio_outs, video_ins;
+ audio_ins.push_back(Device(imaginary, imaginary));
+ audio_outs.push_back(Device(imaginary, imaginary));
+ video_ins.push_back(Device(imaginary, imaginary));
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetAudioInputDevices(&audio_ins));
+ EXPECT_TRUE(dm->GetAudioOutputDevices(&audio_outs));
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ for (size_t i = 0; i < audio_ins.size(); ++i) {
+ EXPECT_NE(imaginary, audio_ins[i].name);
+ }
+ for (size_t i = 0; i < audio_outs.size(); ++i) {
+ EXPECT_NE(imaginary, audio_outs[i].name);
+ }
+ for (size_t i = 0; i < video_ins.size(); ++i) {
+ EXPECT_NE(imaginary, video_ins[i].name);
+ }
+}
+
+static bool CompareDeviceList(std::vector<Device>& devices,
+ const char* const device_list[], int list_size) {
+ if (list_size != static_cast<int>(devices.size())) {
+ return false;
+ }
+ for (int i = 0; i < list_size; ++i) {
+ if (devices[i].name.compare(device_list[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+TEST(DeviceManagerTest, VerifyFilterDevices) {
+ static const char* const kTotalDevicesName[] = {
+ "Google Camera Adapters are tons of fun.",
+ "device1",
+ "device2",
+ "device3",
+ "device4",
+ "device5",
+ "Google Camera Adapter 0",
+ "Google Camera Adapter 1",
+ };
+ static const char* const kFilteredDevicesName[] = {
+ "device2",
+ "device4",
+ "Google Camera Adapter",
+ NULL,
+ };
+ static const char* const kDevicesName[] = {
+ "device1",
+ "device3",
+ "device5",
+ };
+ std::vector<Device> devices;
+ for (int i = 0; i < ARRAY_SIZE(kTotalDevicesName); ++i) {
+ devices.push_back(Device(kTotalDevicesName[i], i));
+ }
+ EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName,
+ ARRAY_SIZE(kTotalDevicesName)));
+ // Return false if given NULL as the exclusion list.
+ EXPECT_TRUE(DeviceManager::FilterDevices(&devices, NULL));
+ // The devices should not change.
+ EXPECT_TRUE(CompareDeviceList(devices, kTotalDevicesName,
+ ARRAY_SIZE(kTotalDevicesName)));
+ EXPECT_TRUE(DeviceManager::FilterDevices(&devices, kFilteredDevicesName));
+ EXPECT_TRUE(CompareDeviceList(devices, kDevicesName,
+ ARRAY_SIZE(kDevicesName)));
+}
+
+#ifdef LINUX
+class FakeV4LLookup : public cricket::V4LLookup {
+ public:
+ explicit FakeV4LLookup(std::vector<std::string> device_paths)
+ : device_paths_(device_paths) {}
+
+ protected:
+ bool CheckIsV4L2Device(const std::string& device) {
+ return std::find(device_paths_.begin(), device_paths_.end(), device)
+ != device_paths_.end();
+ }
+
+ private:
+ std::vector<std::string> device_paths_;
+};
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_6) {
+ std::vector<std::string> devices;
+ devices.push_back("/dev/video0");
+ devices.push_back("/dev/video5");
+ cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+ std::vector<talk_base::FakeFileSystem::File> files;
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+ files.push_back(talk_base::FakeFileSystem::File(
+ "/sys/class/video4linux/video0/name", "Video Device 1"));
+ files.push_back(talk_base::FakeFileSystem::File(
+ "/sys/class/video4linux/video1/model", "Bad Device"));
+ files.push_back(
+ talk_base::FakeFileSystem::File("/sys/class/video4linux/video5/model",
+ "Video Device 2"));
+ talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> video_ins;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ EXPECT_EQ(2u, video_ins.size());
+ EXPECT_EQ("Video Device 1", video_ins.at(0).name);
+ EXPECT_EQ("Video Device 2", video_ins.at(1).name);
+}
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_K2_4) {
+ std::vector<std::string> devices;
+ devices.push_back("/dev/video0");
+ devices.push_back("/dev/video5");
+ cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+ std::vector<talk_base::FakeFileSystem::File> files;
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+ files.push_back(talk_base::FakeFileSystem::File(
+ "/proc/video/dev/video0",
+ "param1: value1\nname: Video Device 1\n param2: value2\n"));
+ files.push_back(talk_base::FakeFileSystem::File(
+ "/proc/video/dev/video1",
+ "param1: value1\nname: Bad Device\n param2: value2\n"));
+ files.push_back(talk_base::FakeFileSystem::File(
+ "/proc/video/dev/video5",
+ "param1: value1\nname: Video Device 2\n param2: value2\n"));
+ talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> video_ins;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ EXPECT_EQ(2u, video_ins.size());
+ EXPECT_EQ("Video Device 1", video_ins.at(0).name);
+ EXPECT_EQ("Video Device 2", video_ins.at(1).name);
+}
+
+TEST(DeviceManagerTest, GetVideoCaptureDevices_KUnknown) {
+ std::vector<std::string> devices;
+ devices.push_back("/dev/video0");
+ devices.push_back("/dev/video5");
+ cricket::V4LLookup::SetV4LLookup(new FakeV4LLookup(devices));
+
+ std::vector<talk_base::FakeFileSystem::File> files;
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video0", ""));
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video1", ""));
+ files.push_back(talk_base::FakeFileSystem::File("/dev/video5", ""));
+ talk_base::FilesystemScope fs(new talk_base::FakeFileSystem(files));
+
+ scoped_ptr<DeviceManagerInterface> dm(DeviceManagerFactory::Create());
+ std::vector<Device> video_ins;
+ EXPECT_TRUE(dm->Init());
+ EXPECT_TRUE(dm->GetVideoCaptureDevices(&video_ins));
+ EXPECT_EQ(2u, video_ins.size());
+ EXPECT_EQ("/dev/video0", video_ins.at(0).name);
+ EXPECT_EQ("/dev/video5", video_ins.at(1).name);
+}
+#endif // LINUX
diff --git a/talk/session/phone/dummydevicemanager.cc b/talk/session/phone/dummydevicemanager.cc
new file mode 100644
index 0000000..5c2d1c2
--- /dev/null
+++ b/talk/session/phone/dummydevicemanager.cc
@@ -0,0 +1,36 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/dummydevicemanager.h"
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+ return new DummyDeviceManager();
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/dummydevicemanager_unittest.cc b/talk/session/phone/dummydevicemanager_unittest.cc
new file mode 100644
index 0000000..3fe734e
--- /dev/null
+++ b/talk/session/phone/dummydevicemanager_unittest.cc
@@ -0,0 +1,104 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/dummydevicemanager.h"
+
+using cricket::Device;
+using cricket::DummyDeviceManager;
+
+// Test that we startup/shutdown properly.
+TEST(DummyDeviceManagerTest, StartupShutdown) {
+ DummyDeviceManager dm;
+ EXPECT_TRUE(dm.Init());
+ dm.Terminate();
+}
+
+// Test enumerating capabilities.
+TEST(DummyDeviceManagerTest, GetCapabilities) {
+ DummyDeviceManager dm;
+ int capabilities = dm.GetCapabilities();
+ EXPECT_EQ((cricket::AUDIO_SEND | cricket::AUDIO_RECV |
+ cricket::VIDEO_SEND | cricket::VIDEO_RECV), capabilities);
+}
+
+// Test enumerating devices.
+TEST(DummyDeviceManagerTest, GetDevices) {
+ DummyDeviceManager dm;
+ EXPECT_TRUE(dm.Init());
+ std::vector<Device> audio_ins, audio_outs, video_ins;
+ EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins));
+ EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs));
+ EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins));
+}
+
+// Test that we return correct ids for default and bogus devices.
+TEST(DummyDeviceManagerTest, GetAudioDeviceIds) {
+ DummyDeviceManager dm;
+ Device device;
+ EXPECT_TRUE(dm.Init());
+ EXPECT_TRUE(dm.GetAudioInputDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+ EXPECT_EQ("-1", device.id);
+ EXPECT_TRUE(dm.GetAudioOutputDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+ EXPECT_EQ("-1", device.id);
+ EXPECT_FALSE(dm.GetAudioInputDevice("_NOT A REAL DEVICE_", &device));
+ EXPECT_FALSE(dm.GetAudioOutputDevice("_NOT A REAL DEVICE_", &device));
+}
+
+// Test that we get the video capture device by name properly.
+TEST(DummyDeviceManagerTest, GetVideoDeviceIds) {
+ DummyDeviceManager dm;
+ Device device;
+ EXPECT_TRUE(dm.Init());
+ EXPECT_FALSE(dm.GetVideoCaptureDevice("_NOT A REAL DEVICE_", &device));
+ EXPECT_TRUE(dm.GetVideoCaptureDevice(
+ cricket::DeviceManagerInterface::kDefaultDeviceName, &device));
+}
+
+TEST(DummyDeviceManagerTest, VerifyDevicesListsAreCleared) {
+ const std::string imaginary("_NOT A REAL DEVICE_");
+ DummyDeviceManager dm;
+ std::vector<Device> audio_ins, audio_outs, video_ins;
+ audio_ins.push_back(Device(imaginary, imaginary));
+ audio_outs.push_back(Device(imaginary, imaginary));
+ video_ins.push_back(Device(imaginary, imaginary));
+ EXPECT_TRUE(dm.Init());
+ EXPECT_TRUE(dm.GetAudioInputDevices(&audio_ins));
+ EXPECT_TRUE(dm.GetAudioOutputDevices(&audio_outs));
+ EXPECT_TRUE(dm.GetVideoCaptureDevices(&video_ins));
+ for (size_t i = 0; i < audio_ins.size(); ++i) {
+ EXPECT_NE(imaginary, audio_ins[i].name);
+ }
+ for (size_t i = 0; i < audio_outs.size(); ++i) {
+ EXPECT_NE(imaginary, audio_outs[i].name);
+ }
+ for (size_t i = 0; i < video_ins.size(); ++i) {
+ EXPECT_NE(imaginary, video_ins[i].name);
+ }
+}
diff --git a/talk/session/phone/fakemediaengine.h b/talk/session/phone/fakemediaengine.h
new file mode 100644
index 0000000..b772c9c
--- /dev/null
+++ b/talk/session/phone/fakemediaengine.h
@@ -0,0 +1,831 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_FAKEMEDIAENGINE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+class FakeMediaEngine;
+class FakeVideoEngine;
+class FakeVoiceEngine;
+
+// A common helper class that handles sending and receiving RTP/RTCP packets.
+template<class Base>
+class RtpHelper : public Base {
+ public:
+ RtpHelper()
+ : options_(0),
+ sending_(false),
+ playout_(false),
+ fail_set_send_codecs_(false),
+ fail_set_recv_codecs_(false),
+ send_ssrc_(0) {
+ }
+ const std::vector<RtpHeaderExtension>& recv_extensions() {
+ return recv_extensions_;
+ }
+ const std::vector<RtpHeaderExtension>& send_extensions() {
+ return send_extensions_;
+ }
+ bool sending() const { return sending_; }
+ bool playout() const { return playout_; }
+ const std::list<std::string>& rtp_packets() const { return rtp_packets_; }
+ const std::list<std::string>& rtcp_packets() const { return rtcp_packets_; }
+ int options() const { return options_; }
+
+ bool SendRtp(const void* data, int len) {
+ if (!sending_ || !Base::network_interface_) {
+ return false;
+ }
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return Base::network_interface_->SendPacket(&packet);
+ }
+ bool SendRtcp(const void* data, int len) {
+ if (!Base::network_interface_) {
+ return false;
+ }
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return Base::network_interface_->SendRtcp(&packet);
+ }
+
+ bool CheckRtp(const void* data, int len) {
+ bool success = !rtp_packets_.empty();
+ if (success) {
+ std::string packet = rtp_packets_.front();
+ rtp_packets_.pop_front();
+ success = (packet == std::string(static_cast<const char*>(data), len));
+ }
+ return success;
+ }
+ bool CheckRtcp(const void* data, int len) {
+ bool success = !rtcp_packets_.empty();
+ if (success) {
+ std::string packet = rtcp_packets_.front();
+ rtcp_packets_.pop_front();
+ success = (packet == std::string(static_cast<const char*>(data), len));
+ }
+ return success;
+ }
+ bool CheckNoRtp() {
+ return rtp_packets_.empty();
+ }
+ bool CheckNoRtcp() {
+ return rtcp_packets_.empty();
+ }
+ virtual bool SetOptions(int options) {
+ options_ = options;
+ return true;
+ }
+ virtual bool SetRecvRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ recv_extensions_ = extensions;
+ return true;
+ }
+ virtual bool SetSendRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ send_extensions_ = extensions;
+ return true;
+ }
+ void set_fail_set_send_codecs(bool fail) {
+ fail_set_send_codecs_ = fail;
+ }
+ void set_fail_set_recv_codecs(bool fail) {
+ fail_set_recv_codecs_ = fail;
+ }
+ virtual bool AddSendStream(const StreamParams& sp) {
+ if (std::find(send_streams_.begin(), send_streams_.end(), sp) !=
+ send_streams_.end()) {
+ return false;
+ }
+ send_streams_.push_back(sp);
+ return true;
+ }
+ virtual bool RemoveSendStream(uint32 ssrc) {
+ return RemoveStreamBySsrc(&send_streams_, ssrc);
+ }
+ virtual bool AddRecvStream(const StreamParams& sp) {
+ if (std::find(receive_streams_.begin(), receive_streams_.end(), sp) !=
+ receive_streams_.end()) {
+ return false;
+ }
+ receive_streams_.push_back(sp);
+ return true;
+ }
+ virtual bool RemoveRecvStream(uint32 ssrc) {
+ return RemoveStreamBySsrc(&receive_streams_, ssrc);
+ }
+ const std::vector<StreamParams>& send_streams() const {
+ return send_streams_;
+ }
+ const std::vector<StreamParams>& recv_streams() const {
+ return receive_streams_;
+ }
+ bool HasRecvStream(uint32 ssrc) const {
+ return GetStreamBySsrc(receive_streams_, ssrc, NULL);
+ }
+
+ // TODO: This is to support legacy unit test that only check one
+ // sending stream.
+ const uint32 send_ssrc() {
+ if (send_streams_.empty())
+ return 0;
+ return send_streams_[0].first_ssrc();
+ }
+
+ // TODO: This is to support legacy unit test that only check one
+ // sending stream.
+ const std::string rtcp_cname() {
+ if (send_streams_.empty())
+ return "";
+ return send_streams_[0].cname;
+ }
+
+ protected:
+ bool set_sending(bool send) {
+ sending_ = send;
+ return true;
+ }
+ void set_playout(bool playout) { playout_ = playout; }
+ virtual void OnPacketReceived(talk_base::Buffer* packet) {
+ rtp_packets_.push_back(std::string(packet->data(), packet->length()));
+ }
+ virtual void OnRtcpReceived(talk_base::Buffer* packet) {
+ rtcp_packets_.push_back(std::string(packet->data(), packet->length()));
+ }
+ bool fail_set_send_codecs() const {
+ return fail_set_send_codecs_;
+ }
+ bool fail_set_recv_codecs() const {
+ return fail_set_recv_codecs_;
+ }
+
+ private:
+ int options_;
+ bool sending_;
+ bool playout_;
+ std::vector<RtpHeaderExtension> recv_extensions_;
+ std::vector<RtpHeaderExtension> send_extensions_;
+ std::list<std::string> rtp_packets_;
+ std::list<std::string> rtcp_packets_;
+ std::vector<StreamParams> send_streams_;
+ std::vector<StreamParams> receive_streams_;
+ bool fail_set_send_codecs_;
+ bool fail_set_recv_codecs_;
+ uint32 send_ssrc_;
+ std::string rtcp_cname_;
+};
+
+class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
+ public:
+ typedef std::pair<int, bool> DtmfEvent;
+ explicit FakeVoiceMediaChannel(FakeVoiceEngine* engine)
+ : engine_(engine),
+ muted_(false),
+ fail_set_send_(false),
+ ringback_tone_ssrc_(0),
+ ringback_tone_play_(false),
+ ringback_tone_loop_(false) {
+ output_scalings_[0] = OutputScaling(); // For default channel.
+ }
+ ~FakeVoiceMediaChannel();
+ const std::vector<AudioCodec>& recv_codecs() const { return recv_codecs_; }
+ const std::vector<AudioCodec>& send_codecs() const { return send_codecs_; }
+ const std::vector<AudioCodec>& codecs() const { return send_codecs(); }
+ bool muted() const { return muted_; }
+ const std::vector<DtmfEvent>& dtmf_queue() const { return dtmf_queue_; }
+
+ uint32 ringback_tone_ssrc() const { return ringback_tone_ssrc_; }
+ bool ringback_tone_play() const { return ringback_tone_play_; }
+ bool ringback_tone_loop() const { return ringback_tone_loop_; }
+
+ virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs) {
+ if (fail_set_recv_codecs()) {
+ // Fake the failure in SetRecvCodecs.
+ return false;
+ }
+ recv_codecs_= codecs;
+ return true;
+ }
+ virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs) {
+ if (fail_set_send_codecs()) {
+ // Fake the failure in SetSendCodecs.
+ return false;
+ }
+ send_codecs_= codecs;
+ return true;
+ }
+ virtual bool SetPlayout(bool playout) {
+ set_playout(playout);
+ return true;
+ }
+ virtual bool SetSend(SendFlags flag) {
+ if (fail_set_send_) {
+ return false;
+ }
+ return set_sending(flag != SEND_NOTHING);
+ }
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+ virtual bool Mute(bool on) {
+ muted_ = on;
+ return true;
+ }
+ virtual bool AddRecvStream(const StreamParams& sp) {
+ if (!RtpHelper<VoiceMediaChannel>::AddRecvStream(sp))
+ return false;
+ output_scalings_[sp.first_ssrc()] = OutputScaling();
+ return true;
+ }
+ virtual bool RemoveRecvStream(uint32 ssrc) {
+ if (!RtpHelper<VoiceMediaChannel>::RemoveRecvStream(ssrc))
+ return false;
+ output_scalings_.erase(ssrc);
+ return true;
+ }
+
+ virtual bool GetActiveStreams(AudioInfo::StreamList* streams) {
+ return true;
+ }
+ virtual int GetOutputLevel() { return 0; }
+
+ virtual bool SetRingbackTone(const char *buf, int len) { return true; }
+ virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop) {
+ ringback_tone_ssrc_ = ssrc;
+ ringback_tone_play_ = play;
+ ringback_tone_loop_ = loop;
+ return true;
+ }
+
+ virtual bool PressDTMF(int event, bool playout) {
+ dtmf_queue_.push_back(std::make_pair(event, playout));
+ return true;
+ }
+
+ virtual bool SetOutputScaling(uint32 ssrc, double left, double right) {
+ if (0 == ssrc) {
+ std::map<uint32, OutputScaling>::iterator it;
+ for (it = output_scalings_.begin(); it != output_scalings_.end(); ++it) {
+ it->second.left = left;
+ it->second.right = right;
+ }
+ return true;
+ } else if (output_scalings_.find(ssrc) != output_scalings_.end()) {
+ output_scalings_[ssrc].left = left;
+ output_scalings_[ssrc].right = right;
+ return true;
+ }
+ return false;
+ }
+ virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right) {
+ if (output_scalings_.find(ssrc) == output_scalings_.end()) return false;
+ *left = output_scalings_[ssrc].left;
+ *right = output_scalings_[ssrc].right;
+ return true;
+ }
+
+ virtual bool GetStats(VoiceMediaInfo* info) { return false; }
+ virtual void GetLastMediaError(uint32* ssrc,
+ VoiceMediaChannel::Error* error) {
+ *ssrc = 0;
+ *error = fail_set_send_ ? VoiceMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED
+ : VoiceMediaChannel::ERROR_NONE;
+ }
+
+ void set_fail_set_send(bool fail) { fail_set_send_ = fail; }
+ void TriggerError(uint32 ssrc, VoiceMediaChannel::Error error) {
+ VoiceMediaChannel::SignalMediaError(ssrc, error);
+ }
+
+ private:
+ struct OutputScaling {
+ OutputScaling() : left(1.0), right(1.0) {}
+ double left, right;
+ };
+
+ FakeVoiceEngine* engine_;
+ std::vector<AudioCodec> recv_codecs_;
+ std::vector<AudioCodec> send_codecs_;
+ bool muted_;
+ std::map<uint32, OutputScaling> output_scalings_;
+ std::vector<DtmfEvent> dtmf_queue_;
+ bool fail_set_send_;
+ uint32 ringback_tone_ssrc_;
+ bool ringback_tone_play_;
+ bool ringback_tone_loop_;
+};
+
+class FakeVideoMediaChannel : public RtpHelper<VideoMediaChannel> {
+ public:
+ explicit FakeVideoMediaChannel(FakeVideoEngine* engine)
+ : engine_(engine),
+ muted_(false),
+ screen_casting_(false),
+ sent_intra_frame_(false),
+ requested_intra_frame_(false) {
+ }
+ ~FakeVideoMediaChannel();
+
+ const std::vector<VideoCodec>& recv_codecs() const { return recv_codecs_; }
+ const std::vector<VideoCodec>& send_codecs() const { return send_codecs_; }
+ const std::vector<VideoCodec>& codecs() const { return send_codecs(); }
+ bool muted() const { return muted_; }
+ bool rendering() const { return playout(); }
+ const std::map<uint32, VideoRenderer*>& renderers() const {
+ return renderers_;
+ }
+ bool GetSendStreamFormat(uint32 ssrc, VideoFormat* format) {
+ if (send_formats_.find(ssrc) == send_formats_.end()) {
+ return false;
+ }
+ *format = send_formats_[ssrc];
+ return true;
+ }
+ virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) {
+ if (send_formats_.find(ssrc) == send_formats_.end()) {
+ return false;
+ }
+ send_formats_[ssrc] = format;
+ return true;
+ }
+
+ virtual bool AddSendStream(const StreamParams& sp) {
+ if (!RtpHelper<VideoMediaChannel>::AddSendStream(sp)) {
+ return false;
+ }
+ SetSendStreamDefaultFormat(sp.first_ssrc());
+ return true;
+ }
+ virtual bool RemoveSendStream(uint32 ssrc) {
+ send_formats_.erase(ssrc);
+ return RtpHelper<VideoMediaChannel>::RemoveSendStream(ssrc);
+ }
+
+ virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) {
+ if (fail_set_recv_codecs()) {
+ // Fake the failure in SetRecvCodecs.
+ return false;
+ }
+ recv_codecs_= codecs;
+ return true;
+ }
+ virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs) {
+ if (fail_set_send_codecs()) {
+ // Fake the failure in SetSendCodecs.
+ return false;
+ }
+ send_codecs_= codecs;
+
+ for (std::vector<StreamParams>::const_iterator it = send_streams().begin();
+ it != send_streams().end(); ++it) {
+ SetSendStreamDefaultFormat(it->first_ssrc());
+ }
+ return true;
+ }
+ virtual bool SetRender(bool render) {
+ set_playout(render);
+ return true;
+ }
+ virtual bool SetRenderer(uint32 ssrc, VideoRenderer* r) {
+ if (ssrc != 0 && renderers_.find(ssrc) == renderers_.end()) {
+ return false;
+ }
+ if (ssrc != 0) {
+ renderers_[ssrc] = r;
+ }
+ return true;
+ }
+
+ virtual bool SetSend(bool send) {
+ return set_sending(send);
+ }
+ virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+ screen_casting_ = true;
+ return true;
+ }
+ virtual bool RemoveScreencast(uint32 ssrc) {
+ screen_casting_ = false;
+ return true;
+ }
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+ virtual bool Mute(bool on) {
+ muted_ = on;
+ return true;
+ }
+ virtual bool AddRecvStream(const StreamParams& sp) {
+ if (!RtpHelper<VideoMediaChannel>::AddRecvStream(sp))
+ return false;
+ renderers_[sp.first_ssrc()] = NULL;
+ return true;
+ }
+ virtual bool RemoveRecvStream(uint32 ssrc) {
+ if (!RtpHelper<VideoMediaChannel>::RemoveRecvStream(ssrc))
+ return false;
+ renderers_.erase(ssrc);
+ return true;
+ }
+
+ virtual bool GetStats(VideoMediaInfo* info) { return false; }
+ virtual bool SendIntraFrame() {
+ sent_intra_frame_= true;
+ return true;
+ }
+ virtual bool RequestIntraFrame() {
+ requested_intra_frame_ = true;
+ return true;
+ }
+ void set_sent_intra_frame(bool v) { sent_intra_frame_ = v; }
+ bool sent_intra_frame() const { return sent_intra_frame_; }
+ void set_requested_intra_frame(bool v) { requested_intra_frame_ = v; }
+ bool requested_intra_frame() const { return requested_intra_frame_; }
+ bool screen_casting() const { return screen_casting_; }
+
+ private:
+ // Be default, each send stream uses the first send codec format.
+ void SetSendStreamDefaultFormat(uint32 ssrc) {
+ if (!send_codecs_.empty()) {
+ send_formats_[ssrc] = VideoFormat(
+ send_codecs_[0].width,
+ send_codecs_[0].height,
+ cricket::VideoFormat::FpsToInterval(send_codecs_[0].framerate),
+ cricket::FOURCC_I420);
+ }
+ }
+
+ FakeVideoEngine* engine_;
+ std::vector<VideoCodec> recv_codecs_;
+ std::vector<VideoCodec> send_codecs_;
+ std::map<uint32, VideoRenderer*> renderers_;
+ std::map<uint32, VideoFormat> send_formats_;
+ bool muted_;
+ bool screen_casting_;
+ bool sent_intra_frame_;
+ bool requested_intra_frame_;
+};
+
+class FakeSoundclipMedia : public SoundclipMedia {
+ public:
+ virtual bool PlaySound(const char *buf, int len, int flags) {
+ return true;
+ }
+};
+
+// A base class for all of the shared parts between FakeVoiceEngine
+// and FakeVideoEngine.
+class FakeBaseEngine {
+ public:
+ FakeBaseEngine()
+ : loglevel_(-1),
+ options_(0),
+ options_changed_(false),
+ fail_create_channel_(false) {
+ }
+
+ bool Init() { return true; }
+ void Terminate() {}
+
+ bool SetOptions(int options) {
+ options_ = options;
+ options_changed_ = true;
+ return true;
+ }
+
+ void SetLogging(int level, const char* filter) {
+ loglevel_ = level;
+ logfilter_ = filter;
+ }
+
+ void set_fail_create_channel(bool fail) { fail_create_channel_ = fail; }
+
+ protected:
+ int loglevel_;
+ std::string logfilter_;
+ int options_;
+ // Flag used by optionsmessagehandler_unittest for checking whether any
+ // relevant setting has been updated.
+ // TODO: Replace with explicit checks of before & after values.
+ bool options_changed_;
+ bool fail_create_channel_;
+};
+
+class FakeVoiceEngine : public FakeBaseEngine {
+ public:
+ FakeVoiceEngine()
+ : output_volume_(-1),
+ rx_processor_(NULL),
+ tx_processor_(NULL) {
+ }
+
+ int GetCapabilities() {
+ return AUDIO_SEND | AUDIO_RECV;
+ }
+
+ VoiceMediaChannel* CreateChannel() {
+ if (fail_create_channel_) {
+ return NULL;
+ }
+
+ FakeVoiceMediaChannel* ch = new FakeVoiceMediaChannel(this);
+ channels_.push_back(ch);
+ return ch;
+ }
+ FakeVoiceMediaChannel* GetChannel(size_t index) {
+ return (channels_.size() > index) ? channels_[index] : NULL;
+ }
+ void UnregisterChannel(VoiceMediaChannel* channel) {
+ channels_.erase(std::find(channels_.begin(), channels_.end(), channel));
+ }
+ SoundclipMedia* CreateSoundclip() {
+ return new FakeSoundclipMedia();
+ }
+
+ const std::vector<AudioCodec>& codecs() {
+ return codecs_;
+ }
+ void SetCodecs(const std::vector<AudioCodec> codecs) {
+ codecs_ = codecs;
+ }
+
+ bool SetDevices(const Device* in_device,
+ const Device* out_device) {
+ in_device_ = (in_device) ? in_device->name : "";
+ out_device_ = (out_device) ? out_device->name : "";
+ options_changed_ = true;
+ return true;
+ }
+
+ bool GetOutputVolume(int* level) {
+ *level = output_volume_;
+ return true;
+ }
+
+ bool SetOutputVolume(int level) {
+ output_volume_ = level;
+ options_changed_ = true;
+ return true;
+ }
+
+ int GetInputLevel() {
+ return 0;
+ }
+
+ bool SetLocalMonitor(bool enable) {
+ return true;
+ }
+
+ bool RegisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) {
+ if (direction == MPD_RX) {
+ rx_processor_ = voice_processor;
+ return true;
+ } else if (direction == MPD_TX) {
+ tx_processor_ = voice_processor;
+ return true;
+ }
+ return false;
+ }
+
+ bool UnregisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) {
+ bool unregistered = false;
+ if (direction & MPD_RX) {
+ rx_processor_ = NULL;
+ unregistered = true;
+ }
+ if (direction & MPD_TX) {
+ tx_processor_ = NULL;
+ unregistered = true;
+ }
+ return unregistered;
+ }
+
+ private:
+ std::vector<FakeVoiceMediaChannel*> channels_;
+ std::vector<AudioCodec> codecs_;
+ int output_volume_;
+ std::string in_device_;
+ std::string out_device_;
+ VoiceProcessor* rx_processor_;
+ VoiceProcessor* tx_processor_;
+
+ friend class FakeMediaEngine;
+};
+
+class FakeVideoEngine : public FakeBaseEngine {
+ public:
+ FakeVideoEngine()
+ : renderer_(NULL),
+ capture_(false),
+ processor_(NULL) {
+ }
+
+ int GetCapabilities() {
+ return VIDEO_SEND | VIDEO_RECV;
+ }
+ bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+ default_encoder_config_ = config;
+ return true;
+ }
+ const VideoEncoderConfig& default_encoder_config() const {
+ return default_encoder_config_;
+ }
+
+ VideoMediaChannel* CreateChannel(VoiceMediaChannel* channel) {
+ if (fail_create_channel_) {
+ return NULL;
+ }
+
+ FakeVideoMediaChannel* ch = new FakeVideoMediaChannel(this);
+ channels_.push_back(ch);
+ return ch;
+ }
+ FakeVideoMediaChannel* GetChannel(size_t index) {
+ return (channels_.size() > index) ? channels_[index] : NULL;
+ }
+ void UnregisterChannel(VideoMediaChannel* channel) {
+ channels_.erase(std::find(channels_.begin(), channels_.end(), channel));
+ }
+
+ const std::vector<VideoCodec>& codecs() const {
+ return codecs_;
+ }
+ bool FindCodec(const VideoCodec& in) {
+ for (size_t i = 0; i < codecs_.size(); ++i) {
+ if (codecs_[i].Matches(in)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ void SetCodecs(const std::vector<VideoCodec> codecs) {
+ codecs_ = codecs;
+ }
+
+ bool SetCaptureDevice(const Device* device) {
+ in_device_ = (device) ? device->name : "";
+ options_changed_ = true;
+ return true;
+ }
+ bool SetLocalRenderer(VideoRenderer* r) {
+ renderer_ = r;
+ return true;
+ }
+ bool SetVideoCapturer(VideoCapturer* /*capturer*/, uint32 /*ssrc*/) {
+ return false;
+ }
+ CaptureResult SetCapture(bool capture) {
+ capture_ = capture;
+ return CR_SUCCESS;
+ }
+ bool RegisterProcessor(VideoProcessor* video_processor) {
+ processor_ = video_processor;
+ return true;
+ }
+
+ bool UnregisterProcessor(VideoProcessor* video_processor) {
+ processor_ = NULL;
+ return true;
+ }
+
+ sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+
+ private:
+ std::vector<FakeVideoMediaChannel*> channels_;
+ std::vector<VideoCodec> codecs_;
+ VideoEncoderConfig default_encoder_config_;
+ std::string in_device_;
+ VideoRenderer* renderer_;
+ bool capture_;
+ VideoProcessor* processor_;
+
+ friend class FakeMediaEngine;
+};
+
+class FakeMediaEngine
+ : public CompositeMediaEngine<FakeVoiceEngine, FakeVideoEngine> {
+ public:
+ FakeMediaEngine() {
+ voice_ = FakeVoiceEngine();
+ video_ = FakeVideoEngine();
+ }
+ virtual ~FakeMediaEngine() {}
+
+ virtual void SetAudioCodecs(const std::vector<AudioCodec> codecs) {
+ voice_.SetCodecs(codecs);
+ }
+
+ virtual void SetVideoCodecs(const std::vector<VideoCodec> codecs) {
+ video_.SetCodecs(codecs);
+ }
+
+ FakeVoiceMediaChannel* GetVoiceChannel(size_t index) {
+ return voice_.GetChannel(index);
+ }
+
+ FakeVideoMediaChannel* GetVideoChannel(size_t index) {
+ return video_.GetChannel(index);
+ }
+
+ int audio_options() const { return voice_.options_; }
+ int output_volume() const { return voice_.output_volume_; }
+ const VideoEncoderConfig& default_video_encoder_config() const {
+ return video_.default_encoder_config_;
+ }
+ const std::string& audio_in_device() const { return voice_.in_device_; }
+ const std::string& audio_out_device() const { return voice_.out_device_; }
+ const std::string& video_in_device() const { return video_.in_device_; }
+ VideoRenderer* local_renderer() { return video_.renderer_; }
+ int voice_loglevel() const { return voice_.loglevel_; }
+ const std::string& voice_logfilter() const { return voice_.logfilter_; }
+ int video_loglevel() const { return video_.loglevel_; }
+ const std::string& video_logfilter() const { return video_.logfilter_; }
+ bool capture() const { return video_.capture_; }
+ bool options_changed() const {
+ return voice_.options_changed_ || video_.options_changed_;
+ }
+ void clear_options_changed() {
+ video_.options_changed_ = false;
+ voice_.options_changed_ = false;
+ }
+ void set_fail_create_channel(bool fail) {
+ voice_.set_fail_create_channel(fail);
+ video_.set_fail_create_channel(fail);
+ }
+ bool video_processor_registered () const {return video_.processor_ != NULL;}
+ bool voice_processor_registered(MediaProcessorDirection direction) const {
+ if (direction == MPD_RX) {
+ return voice_.rx_processor_ != NULL;
+ } else if (direction == MPD_TX) {
+ return voice_.tx_processor_ != NULL;
+ }
+ return false;
+ }
+};
+
+// CompositeMediaEngine with FakeVoiceEngine to expose SetAudioCodecs to
+// establish a media connectionwith minimum set of audio codes required
+template<class VIDEO>
+class CompositeMediaEngineWithFakeVoiceEngine
+ : public CompositeMediaEngine<FakeVoiceEngine, VIDEO> {
+ public:
+ CompositeMediaEngineWithFakeVoiceEngine() {}
+ virtual ~CompositeMediaEngineWithFakeVoiceEngine() {}
+
+ virtual void SetAudioCodecs(const std::vector<AudioCodec>& codecs) {
+ CompositeMediaEngine<FakeVoiceEngine, VIDEO>::voice_.SetCodecs(codecs);
+ }
+};
+
+// Have to come afterwards due to declaration order
+inline FakeVoiceMediaChannel::~FakeVoiceMediaChannel() {
+ if (engine_) {
+ engine_->UnregisterChannel(this);
+ }
+}
+
+inline FakeVideoMediaChannel::~FakeVideoMediaChannel() {
+ if (engine_) {
+ engine_->UnregisterChannel(this);
+ }
+}
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEMEDIAENGINE_H_
diff --git a/talk/session/phone/fakemediaprocessor.h b/talk/session/phone/fakemediaprocessor.h
new file mode 100644
index 0000000..7b88595
--- /dev/null
+++ b/talk/session/phone/fakemediaprocessor.h
@@ -0,0 +1,68 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
+#define TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
+
+#include "talk/session/phone/videoprocessor.h"
+#include "talk/session/phone/voiceprocessor.h"
+
+namespace cricket {
+
+class AudioFrame;
+
+class FakeMediaProcessor : public VoiceProcessor, public VideoProcessor {
+ public:
+ FakeMediaProcessor()
+ : voice_frame_count_(0),
+ video_frame_count_(0) {
+ }
+ virtual ~FakeMediaProcessor() {}
+
+ virtual void OnFrame(uint32 ssrc,
+ AudioFrame* frame) {
+ ++voice_frame_count_;
+ }
+ virtual void OnFrame(uint32 ssrc,
+ VideoFrame* frame_ptr) {
+ ++video_frame_count_;
+ }
+ virtual void OnVoiceMute(uint32 ssrc, bool muted) {}
+ virtual void OnVideoMute(uint32 ssrc, bool muted) {}
+
+ int voice_frame_count() const { return voice_frame_count_; }
+ int video_frame_count() const { return video_frame_count_; }
+
+ private:
+ // TODO: make is a map so that we can multiple ssrcs
+ int voice_frame_count_;
+ int video_frame_count_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEMEDIAPROCESSOR_H_
diff --git a/talk/session/phone/fakenetworkinterface.h b/talk/session/phone/fakenetworkinterface.h
new file mode 100644
index 0000000..58b24a2
--- /dev/null
+++ b/talk/session/phone/fakenetworkinterface.h
@@ -0,0 +1,173 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKENETWORKINTERFACE_H_
+#define TALK_SESSION_PHONE_FAKENETWORKINTERFACE_H_
+
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediachannel.h"
+
+namespace cricket {
+
+// Fake NetworkInterface that sends/receives RTP/RTCP packets.
+class FakeNetworkInterface : public MediaChannel::NetworkInterface,
+ public talk_base::MessageHandler {
+ public:
+ FakeNetworkInterface()
+ : thread_(talk_base::Thread::Current()),
+ dest_(NULL),
+ conf_(false),
+ sendbuf_size_(-1),
+ recvbuf_size_(-1) {
+ }
+
+ void SetDestination(MediaChannel* dest) { dest_ = dest; }
+
+ // Conference mode is a mode where instead of simply forwarding the packets,
+ // the transport will send multiple copies of the packet with the specified
+ // SSRCs. This allows us to simulate receiving media from multiple sources.
+ void SetConferenceMode(bool conf, const std::vector<uint32>& ssrcs) {
+ conf_ = conf;
+ ssrcs_ = ssrcs;
+ }
+
+ int NumRtpBytes() {
+ talk_base::CritScope cs(&crit_);
+ int bytes = 0;
+ for (size_t i = 0; i < rtp_packets_.size(); ++i) {
+ bytes += rtp_packets_[i].length();
+ }
+ return bytes;
+ }
+
+ int NumRtpPackets() {
+ talk_base::CritScope cs(&crit_);
+ return rtp_packets_.size();
+ }
+
+ // Note: callers are responsible for deleting the returned buffer.
+ const talk_base::Buffer* GetRtpPacket(int index) {
+ talk_base::CritScope cs(&crit_);
+ if (index >= NumRtpPackets()) {
+ return NULL;
+ }
+ return new talk_base::Buffer(rtp_packets_[index]);
+ }
+
+ int NumRtcpPackets() {
+ talk_base::CritScope cs(&crit_);
+ return rtcp_packets_.size();
+ }
+
+ // Note: callers are responsible for deleting the returned buffer.
+ const talk_base::Buffer* GetRtcpPacket(int index) {
+ talk_base::CritScope cs(&crit_);
+ if (index >= NumRtcpPackets()) {
+ return NULL;
+ }
+ return new talk_base::Buffer(rtcp_packets_[index]);
+ }
+
+ int sendbuf_size() const { return sendbuf_size_; }
+ int recvbuf_size() const { return recvbuf_size_; }
+
+ protected:
+ virtual bool SendPacket(talk_base::Buffer* packet) {
+ talk_base::CritScope cs(&crit_);
+ rtp_packets_.push_back(*packet);
+ if (conf_) {
+ talk_base::Buffer buffer_copy(*packet);
+ for (size_t i = 0; i < ssrcs_.size(); ++i) {
+ talk_base::SetBE32(buffer_copy.data() + 8, ssrcs_[i]);
+ PostMessage(ST_RTP, buffer_copy);
+ }
+ } else {
+ PostMessage(ST_RTP, *packet);
+ }
+ return true;
+ }
+
+ virtual bool SendRtcp(talk_base::Buffer* packet) {
+ talk_base::CritScope cs(&crit_);
+ rtcp_packets_.push_back(*packet);
+ if (!conf_) {
+ // don't worry about RTCP in conf mode for now
+ PostMessage(ST_RTCP, *packet);
+ }
+ return true;
+ }
+
+ virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+ int option) {
+ if (opt == talk_base::Socket::OPT_SNDBUF) {
+ sendbuf_size_ = option;
+ } else if (opt == talk_base::Socket::OPT_RCVBUF) {
+ recvbuf_size_ = option;
+ }
+ return 0;
+ }
+
+ void PostMessage(int id, const talk_base::Buffer& packet) {
+ thread_->Post(this, id, talk_base::WrapMessageData(packet));
+ }
+
+ virtual void OnMessage(talk_base::Message* msg) {
+ talk_base::TypedMessageData<talk_base::Buffer>* msg_data =
+ static_cast<talk_base::TypedMessageData<talk_base::Buffer>*>(
+ msg->pdata);
+ if (dest_) {
+ if (msg->message_id == ST_RTP) {
+ dest_->OnPacketReceived(&msg_data->data());
+ } else {
+ dest_->OnRtcpReceived(&msg_data->data());
+ }
+ }
+ delete msg_data;
+ }
+
+ private:
+ talk_base::Thread* thread_;
+ MediaChannel* dest_;
+ bool conf_;
+ std::vector<uint32> ssrcs_;
+ talk_base::CriticalSection crit_;
+ std::vector<talk_base::Buffer> rtp_packets_;
+ std::vector<talk_base::Buffer> rtcp_packets_;
+ int sendbuf_size_;
+ int recvbuf_size_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKENETWORKINTERFACE_H_
diff --git a/talk/session/phone/fakertp.h b/talk/session/phone/fakertp.h
new file mode 100644
index 0000000..30e3793
--- /dev/null
+++ b/talk/session/phone/fakertp.h
@@ -0,0 +1,97 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Fake RTP and RTCP packets to use in unit tests.
+
+#ifndef TALK_SESSION_PHONE_FAKERTP_H_
+#define TALK_SESSION_PHONE_FAKERTP_H_
+
+// A typical PCMU RTP packet.
+// PT=0, SN=1, TS=0, SSRC=1
+// all data FF
+static const unsigned char kPcmuFrame[] = {
+ 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
+
+// A typical Receiver Report RTCP packet.
+// PT=RR, LN=1, SSRC=1
+// send SSRC=2, all other fields 0
+static const unsigned char kRtcpReport[] = {
+ 0x80, 0xc9, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+// PT = 97, TS = 0, Seq = 1, SSRC = 2
+// H264 - NRI = 1, Type = 1, bit stream = FF
+
+static const unsigned char kH264Packet[] = {
+ 0x80, 0x61, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+};
+
+#endif // TALK_SESSION_PHONE_FAKERTP_H_
diff --git a/talk/session/phone/fakevideocapturer.h b/talk/session/phone/fakevideocapturer.h
new file mode 100644
index 0000000..335bc8d
--- /dev/null
+++ b/talk/session/phone/fakevideocapturer.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
+
+#include <vector>
+
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+// Fake video capturer that allows the test to manually pump in frames.
+class FakeVideoCapturer : public cricket::VideoCapturer {
+ public:
+ FakeVideoCapturer()
+ : running_(false),
+ next_timestamp_(0) {
+ // Default supported formats. Use ResetSupportedFormats to over write.
+ std::vector<cricket::VideoFormat> formats;
+ formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(160, 120,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ ResetSupportedFormats(formats);
+ }
+ ~FakeVideoCapturer() {
+ SignalDestroyed(this);
+ }
+
+ void ResetSupportedFormats(const std::vector<cricket::VideoFormat>& formats) {
+ SetSupportedFormats(formats);
+ }
+ bool CaptureFrame() {
+ if (!GetCaptureFormat()) {
+ return false;
+ }
+ return CaptureCustomFrame(GetCaptureFormat()->width,
+ GetCaptureFormat()->height,
+ GetCaptureFormat()->fourcc);
+ }
+ bool CaptureCustomFrame(int width, int height, uint32 fourcc) {
+ if (!running_) {
+ return false;
+ }
+
+ // Currently, |fourcc| is always I420.
+ uint32 size = cricket::VideoFrame::SizeOf(width, height);
+ cricket::CapturedFrame frame;
+ frame.width = width;
+ frame.height = height;
+ frame.fourcc = fourcc;
+ frame.data_size = size;
+ frame.elapsed_time = frame.time_stamp = next_timestamp_;
+ next_timestamp_ += 33333333; // 30 fps
+
+ talk_base::scoped_array<char> data(new char[size]);
+ memset(data.get(), 0, size);
+ frame.data = data.get();
+ SignalFrameCaptured(this, &frame);
+ return true;
+ }
+ sigslot::signal1<FakeVideoCapturer*> SignalDestroyed;
+
+ virtual cricket::CaptureResult Start(const cricket::VideoFormat& format) {
+ cricket::VideoFormat supported;
+ if (GetBestCaptureFormat(format, &supported)) {
+ SetCaptureFormat(&supported);
+ }
+ running_ = true;
+ return cricket::CR_SUCCESS;
+ }
+ virtual void Stop() {
+ running_ = false;
+ SetCaptureFormat(NULL);
+ }
+ virtual bool IsRunning() { return running_; }
+ bool GetPreferredFourccs(std::vector<uint32>* fourccs) {
+ fourccs->push_back(cricket::FOURCC_I420);
+ return true;
+ }
+
+ private:
+ bool running_;
+ int64 next_timestamp_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEVIDEOCAPTURER_H_
diff --git a/talk/session/phone/fakevideorenderer.h b/talk/session/phone/fakevideorenderer.h
new file mode 100644
index 0000000..3227fa4
--- /dev/null
+++ b/talk/session/phone/fakevideorenderer.h
@@ -0,0 +1,77 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEVIDEORENDERER_H_
+#define TALK_SESSION_PHONE_FAKEVIDEORENDERER_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videorenderer.h"
+
+namespace cricket {
+
+// Faked video renderer that has a callback for actions on rendering.
+class FakeVideoRenderer : public VideoRenderer {
+ public:
+ FakeVideoRenderer()
+ : width_(0),
+ height_(0),
+ num_set_sizes_(0),
+ num_rendered_frames_(0) {
+ }
+
+ virtual bool SetSize(int width, int height, int reserved) {
+ width_ = width;
+ height_ = height;
+ ++num_set_sizes_;
+ SignalSetSize(width, height, reserved);
+ return true;
+ }
+
+ virtual bool RenderFrame(const VideoFrame* frame) {
+ ++num_rendered_frames_;
+ SignalRenderFrame(frame);
+ return true;
+ }
+
+ int width() const { return width_; }
+ int height() const { return height_; }
+ int num_set_sizes() const { return num_set_sizes_; }
+ int num_rendered_frames() const { return num_rendered_frames_; }
+
+ sigslot::signal3<int, int, int> SignalSetSize;
+ sigslot::signal1<const VideoFrame*> SignalRenderFrame;
+
+ private:
+ int width_;
+ int height_;
+ int num_set_sizes_;
+ int num_rendered_frames_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEVIDEORENDERER_H_
diff --git a/talk/session/phone/fakewebrtccommon.h b/talk/session/phone/fakewebrtccommon.h
new file mode 100644
index 0000000..a960e8b
--- /dev/null
+++ b/talk/session/phone/fakewebrtccommon.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCCOMMON_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCCOMMON_H_
+
+namespace cricket {
+
+#define WEBRTC_STUB(method, args) \
+ virtual int method args { return 0; }
+
+#define WEBRTC_STUB_CONST(method, args) \
+ virtual int method args const { return 0; }
+
+#define WEBRTC_FUNC(method, args) \
+ virtual int method args
+
+#define WEBRTC_FUNC_CONST(method, args) \
+ virtual int method args const
+
+#define WEBRTC_BOOL_FUNC(method, args) \
+ virtual bool method args
+
+#define WEBRTC_CHECK_CHANNEL(channel) \
+ if (channels_.find(channel) == channels_.end()) return -1;
+
+#define WEBRTC_ASSERT_CHANNEL(channel) \
+ ASSERT(channels_.find(channel) != channels_.end());
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCCOMMON_H_
diff --git a/talk/session/phone/fakewebrtcdeviceinfo.h b/talk/session/phone/fakewebrtcdeviceinfo.h
new file mode 100644
index 0000000..c969236
--- /dev/null
+++ b/talk/session/phone/fakewebrtcdeviceinfo.h
@@ -0,0 +1,123 @@
+// libjingle
+// Copyright 2004--2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
+
+#include <vector>
+
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+// Fake class for mocking out webrtc::VideoCaptureModule::DeviceInfo.
+class FakeWebRtcDeviceInfo : public webrtc::VideoCaptureModule::DeviceInfo {
+ public:
+ struct Device {
+ Device(const std::string& n, const std::string& i) : name(n), id(i) {}
+ std::string name;
+ std::string id;
+ std::string product;
+ std::vector<webrtc::VideoCaptureCapability> caps;
+ };
+ FakeWebRtcDeviceInfo() {}
+ void AddDevice(const std::string& device_name, const std::string& device_id) {
+ devices_.push_back(Device(device_name, device_id));
+ }
+ void AddCapability(const std::string& device_id,
+ const webrtc::VideoCaptureCapability& cap) {
+ Device* dev = GetDeviceById(
+ reinterpret_cast<const WebRtc_UWord8*>(device_id.c_str()));
+ if (!dev) return;
+ dev->caps.push_back(cap);
+ }
+ virtual WebRtc_UWord32 NumberOfDevices() {
+ return devices_.size();
+ }
+ virtual WebRtc_Word32 GetDeviceName(WebRtc_UWord32 device_num,
+ WebRtc_UWord8* device_name,
+ WebRtc_UWord32 device_name_len,
+ WebRtc_UWord8* device_id,
+ WebRtc_UWord32 device_id_len,
+ WebRtc_UWord8* product_id,
+ WebRtc_UWord32 product_id_len) {
+ Device* dev = GetDeviceByIndex(device_num);
+ if (!dev) return -1;
+ talk_base::strcpyn(reinterpret_cast<char*>(device_name), device_name_len,
+ dev->name.c_str());
+ talk_base::strcpyn(reinterpret_cast<char*>(device_id), device_id_len,
+ dev->id.c_str());
+ if (product_id) {
+ talk_base::strcpyn(reinterpret_cast<char*>(product_id), product_id_len,
+ dev->product.c_str());
+ }
+ return 0;
+ }
+ virtual WebRtc_Word32 NumberOfCapabilities(const WebRtc_UWord8* device_id) {
+ Device* dev = GetDeviceById(device_id);
+ if (!dev) return -1;
+ return dev->caps.size();
+ }
+ virtual WebRtc_Word32 GetCapability(const WebRtc_UWord8* device_id,
+ const WebRtc_UWord32 device_cap_num,
+ webrtc::VideoCaptureCapability& cap) {
+ Device* dev = GetDeviceById(device_id);
+ if (!dev) return -1;
+ if (device_cap_num >= dev->caps.size()) return -1;
+ cap = dev->caps[device_cap_num];
+ return 0;
+ }
+ virtual WebRtc_Word32 GetOrientation(const WebRtc_UWord8* device_id,
+ webrtc::VideoCaptureRotation& rotation) {
+ return -1; // not implemented
+ }
+ virtual WebRtc_Word32 GetBestMatchedCapability(
+ const WebRtc_UWord8* device_id,
+ const webrtc::VideoCaptureCapability requested,
+ webrtc::VideoCaptureCapability& resulting) {
+ return -1; // not implemented
+ }
+ virtual WebRtc_Word32 DisplayCaptureSettingsDialogBox(
+ const WebRtc_UWord8* device_id, const WebRtc_UWord8* dialog_title,
+ void* parent, WebRtc_UWord32 x, WebRtc_UWord32 y) {
+ return -1; // not implemented
+ }
+
+ Device* GetDeviceByIndex(size_t num) {
+ return (num < devices_.size()) ? &devices_[num] : NULL;
+ }
+ Device* GetDeviceById(const WebRtc_UWord8* device_id) {
+ for (size_t i = 0; i < devices_.size(); ++i) {
+ if (devices_[i].id == reinterpret_cast<const char*>(device_id)) {
+ return &devices_[i];
+ }
+ }
+ return NULL;
+ }
+
+ private:
+ std::vector<Device> devices_;
+};
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCDEVICEINFO_H_
diff --git a/talk/session/phone/fakewebrtcvcmfactory.h b/talk/session/phone/fakewebrtcvcmfactory.h
new file mode 100644
index 0000000..e5c660c
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvcmfactory.h
@@ -0,0 +1,63 @@
+// libjingle
+// Copyright 2004--2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
+
+#include <vector>
+
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+// Factory class to allow the fakes above to be injected into
+// WebRtcVideoCapturer.
+class FakeWebRtcVcmFactory : public cricket::WebRtcVcmFactoryInterface {
+ public:
+ virtual webrtc::VideoCaptureModule* Create(int module_id,
+ const WebRtc_UWord8* device_id) {
+ if (!device_info.GetDeviceById(device_id)) return NULL;
+ FakeWebRtcVideoCaptureModule* module =
+ new FakeWebRtcVideoCaptureModule(this, module_id);
+ modules.push_back(module);
+ return module;
+ }
+ virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
+ return &device_info;
+ }
+ virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
+ }
+ void OnDestroyed(webrtc::VideoCaptureModule* module) {
+ std::remove(modules.begin(), modules.end(), module);
+ }
+ FakeWebRtcDeviceInfo device_info;
+ std::vector<FakeWebRtcVideoCaptureModule*> modules;
+};
+
+FakeWebRtcVideoCaptureModule::~FakeWebRtcVideoCaptureModule() {
+ if (factory_)
+ factory_->OnDestroyed(this);
+}
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCVCMFACTORY_H_
diff --git a/talk/session/phone/fakewebrtcvideocapturemodule.h b/talk/session/phone/fakewebrtcvideocapturemodule.h
new file mode 100644
index 0000000..e3d7db1
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvideocapturemodule.h
@@ -0,0 +1,169 @@
+// libjingle
+// Copyright 2004--2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
+
+#include <vector>
+
+#include "talk/session/phone/testutils.h"
+#include "talk/session/phone/fakewebrtcdeviceinfo.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+class FakeWebRtcVcmFactory;
+
+// Fake class for mocking out webrtc::VideoCaptureModule.
+class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule {
+ public:
+ FakeWebRtcVideoCaptureModule(FakeWebRtcVcmFactory* factory, WebRtc_Word32 id)
+ : factory_(factory),
+ id_(id),
+ callback_(NULL),
+ running_(false),
+ delay_(0) {
+ }
+ virtual int32_t Version(char* version,
+ uint32_t& remaining_buffer_in_bytes,
+ uint32_t& position) const {
+ return 0;
+ }
+ virtual int32_t TimeUntilNextProcess() {
+ return 0;
+ }
+ virtual int32_t Process() {
+ return 0;
+ }
+ virtual WebRtc_Word32 ChangeUniqueId(const WebRtc_Word32 id) {
+ id_ = id;
+ return 0;
+ }
+ virtual WebRtc_Word32 RegisterCaptureDataCallback(
+ webrtc::VideoCaptureDataCallback& callback) {
+ callback_ = &callback;
+ return 0;
+ }
+ virtual WebRtc_Word32 DeRegisterCaptureDataCallback() {
+ callback_ = NULL;
+ return 0;
+ }
+ virtual WebRtc_Word32 RegisterCaptureCallback(
+ webrtc::VideoCaptureFeedBack& callback) {
+ return -1; // not implemented
+ }
+ virtual WebRtc_Word32 DeRegisterCaptureCallback() {
+ return 0;
+ }
+ virtual WebRtc_Word32 StartCapture(
+ const webrtc::VideoCaptureCapability& cap) {
+ if (running_) return -1;
+ cap_ = cap;
+ running_ = true;
+ return 0;
+ }
+ virtual WebRtc_Word32 StopCapture() {
+ running_ = false;
+ return 0;
+ }
+ virtual WebRtc_Word32 StartSendImage(const webrtc::VideoFrame& frame,
+ WebRtc_Word32 framerate) {
+ return -1; // not implemented
+ }
+ virtual WebRtc_Word32 StopSendImage() {
+ return 0;
+ }
+ virtual const WebRtc_UWord8* CurrentDeviceName() const {
+ return NULL; // not implemented
+ }
+ virtual bool CaptureStarted() {
+ return running_;
+ }
+ virtual WebRtc_Word32 CaptureSettings(
+ webrtc::VideoCaptureCapability& settings) {
+ if (!running_) return -1;
+ settings = cap_;
+ return 0;
+ }
+ virtual WebRtc_Word32 SetCaptureDelay(WebRtc_Word32 delay) {
+ delay_ = delay;
+ return 0;
+ }
+ virtual WebRtc_Word32 CaptureDelay() {
+ return delay_;
+ }
+ virtual WebRtc_Word32 SetCaptureRotation(
+ webrtc::VideoCaptureRotation rotation) {
+ return -1; // not implemented
+ }
+ virtual VideoCaptureEncodeInterface* GetEncodeInterface(
+ const webrtc::VideoCodec& codec) {
+ return NULL; // not implemented
+ }
+ virtual WebRtc_Word32 EnableFrameRateCallback(const bool enable) {
+ return -1; // not implemented
+ }
+ virtual WebRtc_Word32 EnableNoPictureAlarm(const bool enable) {
+ return -1; // not implemented
+ }
+ virtual int32_t AddRef() {
+ return 0;
+ }
+ virtual int32_t Release() {
+ delete this;
+ return 0;
+ }
+
+ bool SendFrame(int w, int h) {
+ if (!running_) return false;
+ webrtc::VideoFrame sample;
+ sample.SetWidth(w);
+ sample.SetHeight(h);
+ if (sample.VerifyAndAllocate(I420_SIZE(w, h)) == -1 ||
+ sample.SetLength(sample.Size()) == -1) {
+ return false;
+ }
+ if (callback_) {
+ callback_->OnIncomingCapturedFrame(id_, sample,
+ webrtc::kVideoCodecUnknown);
+ }
+ return true;
+ }
+
+ const webrtc::VideoCaptureCapability& cap() const {
+ return cap_;
+ }
+
+ private:
+ // Ref-counted, use Release() instead.
+ ~FakeWebRtcVideoCaptureModule();
+
+ FakeWebRtcVcmFactory* factory_;
+ int id_;
+ webrtc::VideoCaptureDataCallback* callback_;
+ bool running_;
+ webrtc::VideoCaptureCapability cap_;
+ int delay_;
+};
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCVIDEOCAPTUREMODULE_H_
diff --git a/talk/session/phone/fakewebrtcvideoengine.h b/talk/session/phone/fakewebrtcvideoengine.h
new file mode 100644
index 0000000..cbac785
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvideoengine.h
@@ -0,0 +1,628 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/fakewebrtccommon.h"
+#include "talk/session/phone/webrtcvie.h"
+
+namespace webrtc {
+
+bool operator==(const webrtc::VideoCodec& c1, const webrtc::VideoCodec& c2) {
+ return memcmp(&c1, &c2, sizeof(c1)) == 0;
+}
+
+}
+
+namespace cricket {
+
+#define WEBRTC_CHECK_CAPTURER(capturer) \
+ if (capturers_.find(capturer) == capturers_.end()) return -1;
+
+#define WEBRTC_ASSERT_CAPTURER(capturer) \
+ ASSERT(capturers_.find(capturer) != capturers_.end());
+
+static const int kStartVideoBitrate = 300;
+static const int kMaxVideoBitrate = 1000;
+
+// WebRtc channel id and capture id share the same number space.
+// This is how AddRenderer(renderId, ...) is able to tell if it is adding a
+// renderer for a channel or it is adding a renderer for a capturer.
+static const int kViEChannelIdBase = 0;
+static const int kViEChannelIdMax = 1000;
+static const int kViECaptureIdBase = 10000; // Make sure there is a gap.
+static const int kViECaptureIdMax = 11000;
+
+class FakeWebRtcVideoEngine
+ : public webrtc::ViEBase,
+ public webrtc::ViECodec,
+ public webrtc::ViECapture,
+ public webrtc::ViENetwork,
+ public webrtc::ViERender,
+ public webrtc::ViERTP_RTCP,
+ public webrtc::ViEImageProcess {
+ public:
+ struct Channel {
+ Channel()
+ : capture_id_(-1),
+ has_renderer_(false),
+ render_started_(false),
+ send(false),
+ local_ssrc_(0),
+ rtcp_status_(webrtc::kRtcpNone),
+ key_frame_request_method_(webrtc::kViEKeyFrameRequestNone),
+ tmmbr_(false),
+ remb_send_(false),
+ remb_(false),
+ nack_(false),
+ hybrid_nack_fec_(false) {
+ memset(&send_codec, 0, sizeof(send_codec));
+ }
+ int capture_id_;
+ bool has_renderer_;
+ bool render_started_;
+ bool send;
+ int local_ssrc_;
+ std::string cname_;
+ webrtc::ViERTCPMode rtcp_status_;
+ webrtc::ViEKeyFrameRequestMethod key_frame_request_method_;
+ bool tmmbr_;
+ bool remb_send_; // This channel send REMB packets.
+ bool remb_; // This channel report BWE using remb.
+ bool nack_;
+ bool hybrid_nack_fec_;
+ std::vector<webrtc::VideoCodec> recv_codecs;
+ webrtc::VideoCodec send_codec;
+ };
+ class Capturer : public webrtc::ViEExternalCapture {
+ public:
+ Capturer() : channel_id_(-1) { }
+ int channel_id() const { return channel_id_; }
+ void set_channel_id(int channel_id) { channel_id_ = channel_id; }
+
+ // From ViEExternalCapture
+ virtual int IncomingFrame(unsigned char* videoFrame,
+ unsigned int videoFrameLength,
+ unsigned short width,
+ unsigned short height,
+ webrtc::RawVideoType videoType,
+ unsigned long long captureTime) {
+ return 0;
+ }
+ virtual int IncomingFrameI420(
+ const webrtc::ViEVideoFrameI420& video_frame,
+ unsigned long long captureTime) {
+ return 0;
+ }
+
+ private:
+ int channel_id_;
+ };
+
+ FakeWebRtcVideoEngine(const cricket::VideoCodec* const* codecs,
+ int num_codecs)
+ : inited_(false),
+ last_channel_(kViEChannelIdBase - 1),
+ fail_create_channel_(false),
+ last_capturer_(kViECaptureIdBase - 1),
+ fail_alloc_capturer_(false),
+ codecs_(codecs),
+ num_codecs_(num_codecs) {
+ }
+
+ ~FakeWebRtcVideoEngine() {
+ ASSERT(0 == channels_.size());
+ ASSERT(0 == capturers_.size());
+ }
+ bool IsInited() const { return inited_; }
+
+ int GetLastChannel() const { return last_channel_; }
+ int GetNumChannels() const { return channels_.size(); }
+ void set_fail_create_channel(bool fail_create_channel) {
+ fail_create_channel_ = fail_create_channel;
+ }
+
+ int GetLastCapturer() const { return last_capturer_; }
+ int GetNumCapturers() const { return capturers_.size(); }
+ void set_fail_alloc_capturer(bool fail_alloc_capturer) {
+ fail_alloc_capturer_ = fail_alloc_capturer;
+ }
+
+ int GetCaptureId(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->capture_id_;
+ }
+ bool GetHasRenderer(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->has_renderer_;
+ }
+ bool GetRenderStarted(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->render_started_;
+ }
+ bool GetSend(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->send;
+ }
+ int GetCaptureChannelId(int capture_id) const {
+ WEBRTC_ASSERT_CAPTURER(capture_id);
+ return capturers_.find(capture_id)->second->channel_id();
+ }
+ webrtc::ViERTCPMode GetRtcpStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->rtcp_status_;
+ }
+ webrtc::ViEKeyFrameRequestMethod GetKeyFrameRequestMethod(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->key_frame_request_method_;
+ }
+ bool GetTmmbrStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->tmmbr_;
+ }
+ bool GetRembStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->remb_;
+ }
+ bool GetRembStatusSend(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->remb_send_;
+ }
+ bool GetNackStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->nack_;
+ }
+ bool GetHybridNackFecStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->hybrid_nack_fec_;
+ }
+
+ bool ReceiveCodecRegistered(int channel,
+ const webrtc::VideoCodec& codec) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ const std::vector<webrtc::VideoCodec>& codecs =
+ channels_.find(channel)->second->recv_codecs;
+ return std::find(codecs.begin(), codecs.end(), codec) != codecs.end();
+ };
+
+ WEBRTC_STUB(Release, ());
+
+ // webrtc::ViEBase
+ WEBRTC_FUNC(Init, ()) {
+ inited_ = true;
+ return 0;
+ };
+ WEBRTC_STUB(SetVoiceEngine, (webrtc::VoiceEngine*));
+ WEBRTC_FUNC(CreateChannel, (int& channel)) { // NOLINT
+ if (fail_create_channel_) {
+ return -1;
+ }
+ if (kViEChannelIdMax == last_channel_) {
+ return -1;
+ }
+ Channel* ch = new Channel();
+ channels_[++last_channel_] = ch;
+ channel = last_channel_;
+ return 0;
+ };
+ WEBRTC_STUB(CreateChannel, (int&, int));
+ WEBRTC_FUNC(DeleteChannel, (const int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ delete channels_[channel];
+ channels_.erase(channel);
+ return 0;
+ }
+ WEBRTC_STUB(ConnectAudioChannel, (const int, const int));
+ WEBRTC_STUB(DisconnectAudioChannel, (const int));
+ WEBRTC_FUNC(StartSend, (const int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send = true;
+ return 0;
+ }
+ WEBRTC_FUNC(StopSend, (const int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send = false;
+ return 0;
+ }
+ WEBRTC_STUB(StartReceive, (const int));
+ WEBRTC_STUB(StopReceive, (const int));
+ WEBRTC_STUB(RegisterObserver, (webrtc::ViEBaseObserver&));
+ WEBRTC_STUB(DeregisterObserver, ());
+ WEBRTC_STUB(GetVersion, (char version[1024]));
+ WEBRTC_STUB(LastError, ());
+
+ // webrtc::ViECodec
+ WEBRTC_FUNC(NumberOfCodecs, ()) const {
+ return num_codecs_;
+ };
+ WEBRTC_FUNC(GetCodec, (const unsigned char list_number,
+ webrtc::VideoCodec& out_codec)) const {
+ if (list_number >= NumberOfCodecs()) {
+ return -1;
+ }
+ memset(&out_codec, 0, sizeof(out_codec));
+ const cricket::VideoCodec& c(*codecs_[list_number]);
+ if ("I420" == c.name) {
+ out_codec.codecType = webrtc::kVideoCodecI420;
+ } else if ("VP8" == c.name) {
+ out_codec.codecType = webrtc::kVideoCodecVP8;
+ } else if ("red" == c.name) {
+ out_codec.codecType = webrtc::kVideoCodecRED;
+ } else if ("ulpfec" == c.name) {
+ out_codec.codecType = webrtc::kVideoCodecULPFEC;
+ } else {
+ out_codec.codecType = webrtc::kVideoCodecUnknown;
+ }
+ talk_base::strcpyn(out_codec.plName, sizeof(out_codec.plName),
+ c.name.c_str());
+ out_codec.plType = c.id;
+ out_codec.width = c.width;
+ out_codec.height = c.height;
+ out_codec.startBitrate = kStartVideoBitrate;
+ out_codec.maxBitrate = kMaxVideoBitrate;
+ out_codec.minBitrate = kStartVideoBitrate;
+ out_codec.maxFramerate = c.framerate;
+ return 0;
+ };
+ WEBRTC_FUNC(SetSendCodec, (const int channel,
+ const webrtc::VideoCodec& codec)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send_codec = codec;
+ return 0;
+ };
+ WEBRTC_FUNC(GetSendCodec, (const int channel,
+ webrtc::VideoCodec& codec)) const { // NOLINT
+ WEBRTC_CHECK_CHANNEL(channel);
+ codec = channels_.find(channel)->second->send_codec;
+ return 0;
+ };
+ WEBRTC_FUNC(SetReceiveCodec, (const int channel,
+ const webrtc::VideoCodec& codec)) { // NOLINT
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->recv_codecs.push_back(codec);
+ return 0;
+ };
+ WEBRTC_STUB_CONST(GetReceiveCodec, (const int, webrtc::VideoCodec&));
+ WEBRTC_STUB_CONST(GetCodecConfigParameters, (const int,
+ unsigned char*, unsigned char&));
+ WEBRTC_STUB(SetImageScaleStatus, (const int, const bool));
+ WEBRTC_STUB_CONST(GetSendCodecStastistics, (const int,
+ unsigned int&, unsigned int&));
+ WEBRTC_STUB_CONST(GetReceiveCodecStastistics, (const int,
+ unsigned int&, unsigned int&));
+ virtual unsigned int GetDiscardedPackets(const int channel) const {
+ return 0;
+ }
+
+ WEBRTC_STUB(SetKeyFrameRequestCallbackStatus, (const int, const bool));
+ WEBRTC_STUB(SetSignalKeyPacketLossStatus, (const int, const bool,
+ const bool));
+ WEBRTC_STUB(RegisterEncoderObserver, (const int,
+ webrtc::ViEEncoderObserver&));
+ WEBRTC_STUB(DeregisterEncoderObserver, (const int));
+ WEBRTC_STUB(RegisterDecoderObserver, (const int,
+ webrtc::ViEDecoderObserver&));
+ WEBRTC_STUB(DeregisterDecoderObserver, (const int));
+ WEBRTC_STUB(SendKeyFrame, (const int));
+ WEBRTC_STUB(WaitForFirstKeyFrame, (const int, const bool));
+ WEBRTC_STUB(SetInverseH263Logic, (int, bool));
+
+ // webrtc::ViECapture
+ WEBRTC_STUB(NumberOfCaptureDevices, ());
+ WEBRTC_STUB(GetCaptureDevice, (unsigned int, char*,
+ const unsigned int, char*, const unsigned int));
+ WEBRTC_STUB(AllocateCaptureDevice, (const char*, const unsigned int, int&));
+ WEBRTC_FUNC(AllocateExternalCaptureDevice,
+ (int& capture_id, webrtc::ViEExternalCapture*& capture)) {
+ if (fail_alloc_capturer_) {
+ return -1;
+ }
+ if (kViECaptureIdMax == last_capturer_) {
+ return -1;
+ }
+ Capturer* cap = new Capturer();
+ capturers_[++last_capturer_] = cap;
+ capture_id = last_capturer_;
+ capture = cap;
+ return 0;
+ }
+ WEBRTC_STUB(AllocateCaptureDevice, (webrtc::VideoCaptureModule&, int&));
+ WEBRTC_FUNC(ReleaseCaptureDevice, (const int capture_id)) {
+ WEBRTC_CHECK_CAPTURER(capture_id);
+ delete capturers_[capture_id];
+ capturers_.erase(capture_id);
+ return 0;
+ }
+ WEBRTC_FUNC(ConnectCaptureDevice, (const int capture_id,
+ const int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ WEBRTC_CHECK_CAPTURER(capture_id);
+ channels_[channel]->capture_id_ = capture_id;
+ capturers_[capture_id]->set_channel_id(channel);
+ return 0;
+ }
+ WEBRTC_FUNC(DisconnectCaptureDevice, (const int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ int capture_id = channels_[channel]->capture_id_;
+ WEBRTC_CHECK_CAPTURER(capture_id);
+ channels_[channel]->capture_id_ = -1;
+ capturers_[capture_id]->set_channel_id(-1);
+ return 0;
+ }
+ WEBRTC_STUB(StartCapture, (const int, const webrtc::CaptureCapability));
+ WEBRTC_STUB(StopCapture, (const int));
+ WEBRTC_STUB(SetRotateCapturedFrames, (const int,
+ const webrtc::RotateCapturedFrame));
+ WEBRTC_STUB(SetCaptureDelay, (const int, const unsigned int));
+ WEBRTC_STUB(NumberOfCapabilities, (const char*, const unsigned int));
+ WEBRTC_STUB(GetCaptureCapability, (const char*, const unsigned int,
+ const unsigned int, webrtc::CaptureCapability&));
+ WEBRTC_STUB(ShowCaptureSettingsDialogBox, (const char*, const unsigned int,
+ const char*, void*, const unsigned int, const unsigned int));
+ WEBRTC_STUB(GetOrientation, (const char*, webrtc::RotateCapturedFrame&));
+ WEBRTC_STUB(EnableBrightnessAlarm, (const int, const bool));
+ WEBRTC_STUB(RegisterObserver, (const int, webrtc::ViECaptureObserver&));
+ WEBRTC_STUB(DeregisterObserver, (const int));
+
+ // webrtc::ViENetwork
+ WEBRTC_STUB(SetLocalReceiver, (const int, const unsigned short,
+ const unsigned short, const char*));
+ WEBRTC_STUB(GetLocalReceiver, (const int, unsigned short&,
+ unsigned short&, char*));
+ WEBRTC_STUB(SetSendDestination, (const int, const char*, const unsigned short,
+ const unsigned short, const unsigned short, const unsigned short));
+ WEBRTC_STUB(GetSendDestination, (const int, char*, unsigned short&,
+ unsigned short&, unsigned short&, unsigned short&));
+ WEBRTC_STUB(RegisterSendTransport, (const int, webrtc::Transport&));
+ WEBRTC_STUB(DeregisterSendTransport, (const int));
+ WEBRTC_STUB(ReceivedRTPPacket, (const int, const void*, const int));
+ WEBRTC_STUB(ReceivedRTCPPacket, (const int, const void*, const int));
+ WEBRTC_STUB(GetSourceInfo, (const int, unsigned short&, unsigned short&,
+ char*, unsigned int));
+ WEBRTC_STUB(GetLocalIP, (char*, bool));
+ WEBRTC_STUB(EnableIPv6, (int));
+ // Not using WEBRTC_STUB due to bool return value
+ virtual bool IsIPv6Enabled(int channel) { return true; }
+ WEBRTC_STUB(SetSourceFilter, (const int, const unsigned short,
+ const unsigned short, const char*));
+ WEBRTC_STUB(GetSourceFilter, (const int, unsigned short&,
+ unsigned short&, char*));
+ WEBRTC_STUB(SetSendToS, (const int, const int, const bool));
+ WEBRTC_STUB(GetSendToS, (const int, int&, bool&));
+ WEBRTC_STUB(SetSendGQoS, (const int, const bool, const int, const int));
+ WEBRTC_STUB(GetSendGQoS, (const int, bool&, int&, int&));
+ WEBRTC_STUB(SetMTU, (int, unsigned int));
+ WEBRTC_STUB(SetPacketTimeoutNotification, (const int, bool, int));
+ WEBRTC_STUB(RegisterObserver, (const int, webrtc::ViENetworkObserver&));
+ WEBRTC_STUB(SetPeriodicDeadOrAliveStatus, (const int, const bool,
+ const unsigned int));
+ WEBRTC_STUB(SendUDPPacket, (const int, const void*, const unsigned int,
+ int&, bool));
+
+ // webrtc::ViERender
+ WEBRTC_STUB(RegisterVideoRenderModule, (webrtc::VideoRender&));
+ WEBRTC_STUB(DeRegisterVideoRenderModule, (webrtc::VideoRender&));
+ WEBRTC_STUB(AddRenderer, (const int, void*, const unsigned int, const float,
+ const float, const float, const float));
+ WEBRTC_FUNC(RemoveRenderer, (const int render_id)) {
+ if (IsCapturerId(render_id)) {
+ WEBRTC_CHECK_CAPTURER(render_id);
+ return 0;
+ } else if (IsChannelId(render_id)) {
+ WEBRTC_CHECK_CHANNEL(render_id);
+ channels_[render_id]->has_renderer_ = false;
+ return 0;
+ }
+ return -1;
+ }
+ WEBRTC_FUNC(StartRender, (const int render_id)) {
+ if (IsCapturerId(render_id)) {
+ WEBRTC_CHECK_CAPTURER(render_id);
+ return 0;
+ } else if (IsChannelId(render_id)) {
+ WEBRTC_CHECK_CHANNEL(render_id);
+ channels_[render_id]->render_started_ = true;
+ return 0;
+ }
+ return -1;
+ }
+ WEBRTC_FUNC(StopRender, (const int render_id)) {
+ if (IsCapturerId(render_id)) {
+ WEBRTC_CHECK_CAPTURER(render_id);
+ return 0;
+ } else if (IsChannelId(render_id)) {
+ WEBRTC_CHECK_CHANNEL(render_id);
+ channels_[render_id]->render_started_ = false;
+ return 0;
+ }
+ return -1;
+ }
+ WEBRTC_STUB(ConfigureRender, (int, const unsigned int, const float,
+ const float, const float, const float));
+ WEBRTC_STUB(MirrorRenderStream, (const int, const bool, const bool,
+ const bool));
+ WEBRTC_FUNC(AddRenderer, (const int render_id,
+ webrtc::RawVideoType video_type,
+ webrtc::ExternalRenderer* renderer)) {
+ if (IsCapturerId(render_id)) {
+ WEBRTC_CHECK_CAPTURER(render_id);
+ return 0;
+ } else if (IsChannelId(render_id)) {
+ WEBRTC_CHECK_CHANNEL(render_id);
+ channels_[render_id]->has_renderer_ = true;
+ return 0;
+ }
+ return -1;
+ }
+
+ // webrtc::ViERTP_RTCP
+ WEBRTC_FUNC(SetLocalSSRC, (const int channel,
+ const unsigned int ssrc,
+ const webrtc::StreamType usage,
+ const unsigned char simulcast_idx)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->local_ssrc_ = ssrc;
+ return 0;
+ }
+ WEBRTC_STUB_CONST(SetRemoteSSRCType, (const int,
+ const webrtc::StreamType, const unsigned int));
+
+ WEBRTC_FUNC_CONST(GetLocalSSRC, (const int channel,
+ unsigned int& ssrc)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ ssrc = channels_.find(channel)->second->local_ssrc_;
+ return 0;
+ }
+ WEBRTC_STUB_CONST(GetRemoteSSRC, (const int, unsigned int&));
+ WEBRTC_STUB_CONST(GetRemoteCSRCs, (const int, unsigned int*));
+ WEBRTC_STUB(SetStartSequenceNumber, (const int, unsigned short));
+ WEBRTC_FUNC(SetRTCPStatus,
+ (const int channel, const webrtc::ViERTCPMode mode)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->rtcp_status_ = mode;
+ return 0;
+ }
+ WEBRTC_STUB_CONST(GetRTCPStatus, (const int, webrtc::ViERTCPMode&));
+ WEBRTC_FUNC(SetRTCPCName, (const int channel,
+ const char rtcp_cname[KMaxRTCPCNameLength])) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->cname_.assign(rtcp_cname);
+ return 0;
+ }
+ WEBRTC_FUNC_CONST(GetRTCPCName, (const int channel,
+ char rtcp_cname[KMaxRTCPCNameLength])) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ talk_base::strcpyn(rtcp_cname, KMaxRTCPCNameLength,
+ channels_.find(channel)->second->cname_.c_str());
+ return 0;
+ }
+ WEBRTC_STUB_CONST(GetRemoteRTCPCName, (const int, char*));
+ WEBRTC_STUB(SendApplicationDefinedRTCPPacket, (const int, const unsigned char,
+ unsigned int, const char*, unsigned short));
+ WEBRTC_FUNC(SetNACKStatus, (const int channel, const bool enable)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->nack_ = enable;
+ channels_[channel]->hybrid_nack_fec_ = false;
+ return 0;
+ }
+ WEBRTC_STUB(SetFECStatus, (const int, const bool, const unsigned char,
+ const unsigned char));
+ WEBRTC_FUNC(SetHybridNACKFECStatus, (const int channel, const bool enable,
+ const unsigned char red_type, const unsigned char fec_type)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ if (red_type == fec_type ||
+ red_type == channels_[channel]->send_codec.plType ||
+ fec_type == channels_[channel]->send_codec.plType) {
+ return -1;
+ }
+ channels_[channel]->nack_ = false;
+ channels_[channel]->hybrid_nack_fec_ = true;
+ return 0;
+ }
+ WEBRTC_FUNC(SetKeyFrameRequestMethod,
+ (const int channel,
+ const webrtc::ViEKeyFrameRequestMethod method)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->key_frame_request_method_ = method;
+ return 0;
+ }
+ WEBRTC_BOOL_FUNC(SetRembStatus, (int channel, bool send, bool receive)) {
+ if (channels_.find(channel) == channels_.end())
+ return false;
+ channels_[channel]->remb_send_ = send;
+ channels_[channel]->remb_ = receive;
+ return true;
+ }
+ WEBRTC_FUNC(SetTMMBRStatus, (const int channel, const bool enable)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->tmmbr_ = enable;
+ return 0;
+ }
+ WEBRTC_STUB_CONST(GetReceivedRTCPStatistics, (const int, unsigned short&,
+ unsigned int&, unsigned int&, unsigned int&, int&));
+ WEBRTC_STUB_CONST(GetSentRTCPStatistics, (const int, unsigned short&,
+ unsigned int&, unsigned int&, unsigned int&, int&));
+ WEBRTC_STUB_CONST(GetRTPStatistics, (const int, unsigned int&, unsigned int&,
+ unsigned int&, unsigned int&));
+ WEBRTC_STUB_CONST(GetBandwidthUsage, (const int, unsigned int&,
+ unsigned int&, unsigned int&, unsigned int&));
+
+ WEBRTC_STUB(SetRTPKeepAliveStatus, (const int, bool, const char,
+ const unsigned int));
+ WEBRTC_STUB_CONST(GetRTPKeepAliveStatus,
+ (const int, bool&, char&, unsigned int&));
+ WEBRTC_STUB(StartRTPDump, (const int, const char*, webrtc::RTPDirections));
+ WEBRTC_STUB(StopRTPDump, (const int, webrtc::RTPDirections));
+ WEBRTC_STUB(RegisterRTPObserver, (const int, webrtc::ViERTPObserver&));
+ WEBRTC_STUB(DeregisterRTPObserver, (const int));
+ WEBRTC_STUB(RegisterRTCPObserver, (const int, webrtc::ViERTCPObserver&));
+ WEBRTC_STUB(DeregisterRTCPObserver, (const int));
+
+ // webrtc::ViEImageProcess
+ WEBRTC_STUB(RegisterCaptureEffectFilter, (const int,
+ webrtc::ViEEffectFilter&));
+ WEBRTC_STUB(DeregisterCaptureEffectFilter, (const int));
+ WEBRTC_STUB(RegisterSendEffectFilter, (const int,
+ webrtc::ViEEffectFilter&));
+ WEBRTC_STUB(DeregisterSendEffectFilter, (const int));
+ WEBRTC_STUB(RegisterRenderEffectFilter, (const int,
+ webrtc::ViEEffectFilter&));
+ WEBRTC_STUB(DeregisterRenderEffectFilter, (const int));
+ WEBRTC_STUB(EnableDeflickering, (const int, const bool));
+ WEBRTC_STUB(EnableDenoising, (const int, const bool));
+ WEBRTC_STUB(EnableColorEnhancement, (const int, const bool));
+
+ private:
+ bool IsChannelId(int id) const {
+ return (id >= kViEChannelIdBase && id <= kViEChannelIdMax);
+ }
+ bool IsCapturerId(int id) const {
+ return (id >= kViECaptureIdBase && id <= kViECaptureIdMax);
+ }
+
+ bool inited_;
+ int last_channel_;
+ std::map<int, Channel*> channels_;
+ bool fail_create_channel_;
+ int last_capturer_;
+ std::map<int, Capturer*> capturers_;
+ bool fail_alloc_capturer_;
+ const cricket::VideoCodec* const* codecs_;
+ int num_codecs_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCVIDEOENGINE_H_
diff --git a/talk/session/phone/fakewebrtcvoiceengine.h b/talk/session/phone/fakewebrtcvoiceengine.h
new file mode 100644
index 0000000..568d408
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvoiceengine.h
@@ -0,0 +1,851 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
+#define TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
+
+#include <list>
+#include <map>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/fakewebrtccommon.h"
+#include "talk/session/phone/voiceprocessor.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+namespace cricket {
+
+static const char kFakeDefaultDeviceName[] = "Fake Default";
+static const int kFakeDefaultDeviceId = -1;
+static const char kFakeDeviceName[] = "Fake Device";
+#ifdef WIN32
+static const int kFakeDeviceId = 0;
+#else
+static const int kFakeDeviceId = 1;
+#endif
+
+class FakeWebRtcVoiceEngine
+ : public webrtc::VoEAudioProcessing,
+ public webrtc::VoEBase, public webrtc::VoECodec, public webrtc::VoEDtmf,
+ public webrtc::VoEFile, public webrtc::VoEHardware,
+ public webrtc::VoEExternalMedia, public webrtc::VoENetEqStats,
+ public webrtc::VoENetwork, public webrtc::VoERTP_RTCP,
+ public webrtc::VoEVideoSync, public webrtc::VoEVolumeControl {
+ public:
+ struct Channel {
+ Channel()
+ : external_transport(false),
+ send(false),
+ playout(false),
+ file(false),
+ vad(false),
+ fec(false),
+ cn8_type(13),
+ cn16_type(105),
+ dtmf_type(106),
+ fec_type(117),
+ send_ssrc(0),
+ level_header_ext_(-1) {
+ memset(&send_codec, 0, sizeof(send_codec));
+ }
+ bool external_transport;
+ bool send;
+ bool playout;
+ bool file;
+ bool vad;
+ bool fec;
+ int cn8_type;
+ int cn16_type;
+ int dtmf_type;
+ int fec_type;
+ uint32 send_ssrc;
+ int level_header_ext_;
+ std::vector<webrtc::CodecInst> recv_codecs;
+ webrtc::CodecInst send_codec;
+ std::list<std::string> packets;
+ };
+
+ FakeWebRtcVoiceEngine(const cricket::AudioCodec* const* codecs,
+ int num_codecs)
+ : inited_(false),
+ last_channel_(-1),
+ fail_create_channel_(false),
+ codecs_(codecs),
+ num_codecs_(num_codecs),
+ ec_enabled_(false),
+ ns_enabled_(false),
+ ec_mode_(webrtc::kEcDefault),
+ ns_mode_(webrtc::kNsDefault),
+ observer_(NULL),
+ playout_fail_channel_(-1),
+ send_fail_channel_(-1),
+ fail_start_recording_microphone_(false),
+ recording_microphone_(false),
+ media_processor_(NULL) {
+ memset(&agc_config_, 0, sizeof(agc_config_));
+ }
+ ~FakeWebRtcVoiceEngine() {
+ // Ought to have all been deleted by the WebRtcVoiceMediaChannel
+ // destructors, but just in case ...
+ for (std::map<int, Channel*>::const_iterator i = channels_.begin();
+ i != channels_.end(); ++i) {
+ delete i->second;
+ }
+ }
+ bool IsExternalMediaProcessorRegistered() const {
+ return media_processor_ != NULL;
+ }
+ bool IsInited() const { return inited_; }
+ int GetLastChannel() const { return last_channel_; }
+ int GetNumChannels() const { return channels_.size(); }
+ bool GetPlayout(int channel) {
+ return channels_[channel]->playout;
+ }
+ bool GetSend(int channel) {
+ return channels_[channel]->send;
+ }
+ bool GetRecordingMicrophone() {
+ return recording_microphone_;
+ }
+ bool GetVAD(int channel) {
+ return channels_[channel]->vad;
+ }
+ bool GetFEC(int channel) {
+ return channels_[channel]->fec;
+ }
+ int GetSendCNPayloadType(int channel, bool wideband) {
+ return (wideband) ?
+ channels_[channel]->cn16_type :
+ channels_[channel]->cn8_type;
+ }
+ int GetSendTelephoneEventPayloadType(int channel) {
+ return channels_[channel]->dtmf_type;
+ }
+ int GetSendFECPayloadType(int channel) {
+ return channels_[channel]->fec_type;
+ }
+ bool CheckPacket(int channel, const void* data, size_t len) {
+ bool result = !CheckNoPacket(channel);
+ if (result) {
+ std::string packet = channels_[channel]->packets.front();
+ result = (packet == std::string(static_cast<const char*>(data), len));
+ channels_[channel]->packets.pop_front();
+ }
+ return result;
+ }
+ bool CheckNoPacket(int channel) {
+ return channels_[channel]->packets.empty();
+ }
+ void TriggerCallbackOnError(int channel_num, int err_code) {
+ ASSERT(observer_ != NULL);
+ observer_->CallbackOnError(channel_num, err_code);
+ }
+ void set_playout_fail_channel(int channel) {
+ playout_fail_channel_ = channel;
+ }
+ void set_send_fail_channel(int channel) {
+ send_fail_channel_ = channel;
+ }
+ void set_fail_start_recording_microphone(
+ bool fail_start_recording_microphone) {
+ fail_start_recording_microphone_ = fail_start_recording_microphone;
+ }
+ void set_fail_create_channel(bool fail_create_channel) {
+ fail_create_channel_ = fail_create_channel;
+ }
+ void TriggerProcessPacket(MediaProcessorDirection direction) {
+ webrtc::ProcessingTypes pt =
+ (direction == cricket::MPD_TX) ?
+ webrtc::kRecordingPerChannel : webrtc::kPlaybackPerChannel;
+ if (media_processor_ != NULL) {
+ media_processor_->Process(0,
+ pt,
+ NULL,
+ 0,
+ 0,
+ true);
+ }
+ }
+
+ WEBRTC_STUB(Release, ());
+
+ // webrtc::VoEBase
+ WEBRTC_FUNC(RegisterVoiceEngineObserver, (
+ webrtc::VoiceEngineObserver& observer)) {
+ observer_ = &observer;
+ return 0;
+ }
+ WEBRTC_STUB(DeRegisterVoiceEngineObserver, ());
+ WEBRTC_STUB(RegisterAudioDeviceModule, (webrtc::AudioDeviceModule& adm));
+ WEBRTC_STUB(DeRegisterAudioDeviceModule, ());
+
+ WEBRTC_FUNC(Init, (webrtc::AudioDeviceModule* adm)) {
+ inited_ = true;
+ return 0;
+ }
+ WEBRTC_FUNC(Terminate, ()) {
+ inited_ = false;
+ return 0;
+ }
+ WEBRTC_STUB(MaxNumOfChannels, ());
+ WEBRTC_FUNC(CreateChannel, ()) {
+ if (fail_create_channel_) {
+ return -1;
+ }
+ Channel* ch = new Channel();
+ for (int i = 0; i < NumOfCodecs(); ++i) {
+ webrtc::CodecInst codec;
+ GetCodec(i, codec);
+ ch->recv_codecs.push_back(codec);
+ }
+ channels_[++last_channel_] = ch;
+ return last_channel_;
+ }
+ WEBRTC_FUNC(DeleteChannel, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ delete channels_[channel];
+ channels_.erase(channel);
+ return 0;
+ }
+ WEBRTC_STUB(SetLocalReceiver, (int channel, int port, int RTCPport,
+ const char ipaddr[64],
+ const char multiCastAddr[64]));
+ WEBRTC_STUB(GetLocalReceiver, (int channel, int& port, int& RTCPport,
+ char ipaddr[64]));
+ WEBRTC_STUB(SetSendDestination, (int channel, int port,
+ const char ipaddr[64],
+ int sourcePort, int RTCPport));
+ WEBRTC_STUB(GetSendDestination, (int channel, int& port, char ipaddr[64],
+ int& sourcePort, int& RTCPport));
+ WEBRTC_STUB(StartReceive, (int channel));
+ WEBRTC_FUNC(StartPlayout, (int channel)) {
+ if (playout_fail_channel_ != channel) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->playout = true;
+ return 0;
+ } else {
+ // When playout_fail_channel_ == channel, fail the StartPlayout on this
+ // channel.
+ return -1;
+ }
+ }
+ WEBRTC_FUNC(StartSend, (int channel)) {
+ if (send_fail_channel_ != channel) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send = true;
+ return 0;
+ } else {
+ // When send_fail_channel_ == channel, fail the StartSend on this
+ // channel.
+ return -1;
+ }
+ }
+ WEBRTC_STUB(StopReceive, (int channel));
+ WEBRTC_FUNC(StopPlayout, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->playout = false;
+ return 0;
+ }
+ WEBRTC_FUNC(StopSend, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send = false;
+ return 0;
+ }
+ WEBRTC_STUB(GetVersion, (char version[1024]));
+ WEBRTC_STUB(LastError, ());
+ WEBRTC_STUB(SetOnHoldStatus, (int, bool, webrtc::OnHoldModes));
+ WEBRTC_STUB(GetOnHoldStatus, (int, bool&, webrtc::OnHoldModes&));
+ WEBRTC_STUB(SetNetEQPlayoutMode, (int, webrtc::NetEqModes));
+ WEBRTC_STUB(GetNetEQPlayoutMode, (int, webrtc::NetEqModes&));
+ WEBRTC_STUB(SetNetEQBGNMode, (int, webrtc::NetEqBgnModes));
+ WEBRTC_STUB(GetNetEQBGNMode, (int, webrtc::NetEqBgnModes&));
+
+ // webrtc::VoECodec
+ WEBRTC_FUNC(NumOfCodecs, ()) {
+ return num_codecs_;
+ }
+ WEBRTC_FUNC(GetCodec, (int index, webrtc::CodecInst& codec)) {
+ if (index < 0 || index >= NumOfCodecs()) {
+ return -1;
+ }
+ const cricket::AudioCodec& c(*codecs_[index]);
+ codec.pltype = c.id;
+ talk_base::strcpyn(codec.plname, sizeof(codec.plname), c.name.c_str());
+ codec.plfreq = c.clockrate;
+ codec.pacsize = 0;
+ codec.channels = c.channels;
+ codec.rate = c.bitrate;
+ return 0;
+ }
+ WEBRTC_FUNC(SetSendCodec, (int channel, const webrtc::CodecInst& codec)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send_codec = codec;
+ return 0;
+ }
+ WEBRTC_FUNC(GetSendCodec, (int channel, webrtc::CodecInst& codec)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ codec = channels_[channel]->send_codec;
+ return 0;
+ }
+ WEBRTC_STUB(GetRecCodec, (int channel, webrtc::CodecInst& codec));
+ WEBRTC_STUB(SetAMREncFormat, (int channel, webrtc::AmrMode mode));
+ WEBRTC_STUB(SetAMRDecFormat, (int channel, webrtc::AmrMode mode));
+ WEBRTC_STUB(SetAMRWbEncFormat, (int channel, webrtc::AmrMode mode));
+ WEBRTC_STUB(SetAMRWbDecFormat, (int channel, webrtc::AmrMode mode));
+ WEBRTC_STUB(SetISACInitTargetRate, (int channel, int rateBps,
+ bool useFixedFrameSize));
+ WEBRTC_STUB(SetISACMaxRate, (int channel, int rateBps));
+ WEBRTC_STUB(SetISACMaxPayloadSize, (int channel, int sizeBytes));
+ WEBRTC_FUNC(SetRecPayloadType, (int channel,
+ const webrtc::CodecInst& codec)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ Channel* ch = channels_[channel];
+ // Check if something else already has this slot.
+ if (codec.pltype != -1) {
+ for (std::vector<webrtc::CodecInst>::iterator it =
+ ch->recv_codecs.begin(); it != ch->recv_codecs.end(); ++it) {
+ if (it->pltype == codec.pltype) {
+ return -1;
+ }
+ }
+ }
+ // Otherwise try to find this codec and update its payload type.
+ for (std::vector<webrtc::CodecInst>::iterator it = ch->recv_codecs.begin();
+ it != ch->recv_codecs.end(); ++it) {
+ if (strcmp(it->plname, codec.plname) == 0 &&
+ it->plfreq == codec.plfreq) {
+ it->pltype = codec.pltype;
+ return 0;
+ }
+ }
+ return -1; // not found
+ }
+ WEBRTC_FUNC(SetSendCNPayloadType, (int channel, int type,
+ webrtc::PayloadFrequencies frequency)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ if (frequency == webrtc::kFreq8000Hz) {
+ channels_[channel]->cn8_type = type;
+ } else if (frequency == webrtc::kFreq16000Hz) {
+ channels_[channel]->cn16_type = type;
+ }
+ return 0;
+ }
+ WEBRTC_FUNC(GetRecPayloadType, (int channel, webrtc::CodecInst& codec)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ Channel* ch = channels_[channel];
+ for (std::vector<webrtc::CodecInst>::iterator it = ch->recv_codecs.begin();
+ it != ch->recv_codecs.end(); ++it) {
+ if (strcmp(it->plname, codec.plname) == 0 &&
+ it->plfreq == codec.plfreq &&
+ it->pltype != -1) {
+ codec.pltype = it->pltype;
+ return 0;
+ }
+ }
+ return -1; // not found
+ }
+ WEBRTC_FUNC(SetVADStatus, (int channel, bool enable, webrtc::VadModes mode,
+ bool disableDTX)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->vad = enable;
+ return 0;
+ }
+ WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled,
+ webrtc::VadModes& mode, bool& disabledDTX));
+
+ // webrtc::VoEDtmf
+ WEBRTC_STUB(SendTelephoneEvent, (int channel, int eventCode,
+ bool outOfBand = true, int lengthMs = 160, int attenuationDb = 10));
+
+ WEBRTC_FUNC(SetSendTelephoneEventPayloadType,
+ (int channel, unsigned char type)) {
+ channels_[channel]->dtmf_type = type;
+ return 0;
+ };
+ WEBRTC_STUB(GetSendTelephoneEventPayloadType,
+ (int channel, unsigned char& type));
+
+ WEBRTC_STUB(SetDtmfFeedbackStatus, (bool enable, bool directFeedback));
+ WEBRTC_STUB(GetDtmfFeedbackStatus, (bool& enabled, bool& directFeedback));
+ WEBRTC_STUB(RegisterTelephoneEventDetection, (int channel,
+ webrtc::TelephoneEventDetectionMethods detectionMethod,
+ webrtc::VoETelephoneEventObserver& observer));
+ WEBRTC_STUB(DeRegisterTelephoneEventDetection, (int channel));
+ WEBRTC_STUB(SetDtmfPlayoutStatus, (int channel, bool enable));
+ WEBRTC_STUB(GetDtmfPlayoutStatus, (int channel, bool& enabled));
+
+
+ WEBRTC_STUB(PlayDtmfTone,
+ (int eventCode, int lengthMs = 200, int attenuationDb = 10));
+ WEBRTC_STUB(StartPlayingDtmfTone,
+ (int eventCode, int attenuationDb = 10));
+ WEBRTC_STUB(StopPlayingDtmfTone, ());
+ WEBRTC_STUB(GetTelephoneEventDetectionStatus, (int channel,
+ bool& enabled, webrtc::TelephoneEventDetectionMethods& detectionMethod));
+
+ // webrtc::VoEFile
+ WEBRTC_FUNC(StartPlayingFileLocally, (int channel, const char* fileNameUTF8,
+ bool loop, webrtc::FileFormats format,
+ float volumeScaling, int startPointMs,
+ int stopPointMs)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->file = true;
+ return 0;
+ }
+ WEBRTC_FUNC(StartPlayingFileLocally, (int channel, webrtc::InStream* stream,
+ webrtc::FileFormats format,
+ float volumeScaling, int startPointMs,
+ int stopPointMs)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->file = true;
+ return 0;
+ }
+ WEBRTC_FUNC(StopPlayingFileLocally, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->file = false;
+ return 0;
+ }
+ WEBRTC_FUNC(IsPlayingFileLocally, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ return (channels_[channel]->file) ? 1 : 0;
+ }
+ WEBRTC_STUB(ScaleLocalFilePlayout, (int channel, float scale));
+ WEBRTC_STUB(StartPlayingFileAsMicrophone, (int channel,
+ const char* fileNameUTF8,
+ bool loop,
+ bool mixWithMicrophone,
+ webrtc::FileFormats format,
+ float volumeScaling));
+ WEBRTC_STUB(StartPlayingFileAsMicrophone, (int channel,
+ webrtc::InStream* stream,
+ bool mixWithMicrophone,
+ webrtc::FileFormats format,
+ float volumeScaling));
+ WEBRTC_STUB(StopPlayingFileAsMicrophone, (int channel));
+ WEBRTC_STUB(IsPlayingFileAsMicrophone, (int channel));
+ WEBRTC_STUB(ScaleFileAsMicrophonePlayout, (int channel, float scale));
+ WEBRTC_STUB(StartRecordingPlayout, (int channel, const char* fileNameUTF8,
+ webrtc::CodecInst* compression,
+ int maxSizeBytes));
+ WEBRTC_STUB(StartRecordingPlayout, (int channel, webrtc::OutStream* stream,
+ webrtc::CodecInst* compression));
+ WEBRTC_STUB(StopRecordingPlayout, (int channel));
+ WEBRTC_FUNC(StartRecordingMicrophone, (const char* fileNameUTF8,
+ webrtc::CodecInst* compression,
+ int maxSizeBytes)) {
+ if (fail_start_recording_microphone_) {
+ return -1;
+ }
+ recording_microphone_ = true;
+ return 0;
+ }
+ WEBRTC_FUNC(StartRecordingMicrophone, (webrtc::OutStream* stream,
+ webrtc::CodecInst* compression)) {
+ if (fail_start_recording_microphone_) {
+ return -1;
+ }
+ recording_microphone_ = true;
+ return 0;
+ }
+ WEBRTC_FUNC(StopRecordingMicrophone, ()) {
+ if (!recording_microphone_) {
+ return -1;
+ }
+ recording_microphone_ = false;
+ return 0;
+ }
+ WEBRTC_STUB(ConvertPCMToWAV, (const char* fileNameInUTF8,
+ const char* fileNameOutUTF8));
+ WEBRTC_STUB(ConvertPCMToWAV, (webrtc::InStream* streamIn,
+ webrtc::OutStream* streamOut));
+ WEBRTC_STUB(ConvertWAVToPCM, (const char* fileNameInUTF8,
+ const char* fileNameOutUTF8));
+ WEBRTC_STUB(ConvertWAVToPCM, (webrtc::InStream* streamIn,
+ webrtc::OutStream* streamOut));
+ WEBRTC_STUB(ConvertPCMToCompressed, (const char* fileNameInUTF8,
+ const char* fileNameOutUTF8,
+ webrtc::CodecInst* compression));
+ WEBRTC_STUB(ConvertPCMToCompressed, (webrtc::InStream* streamIn,
+ webrtc::OutStream* streamOut,
+ webrtc::CodecInst* compression));
+ WEBRTC_STUB(ConvertCompressedToPCM, (const char* fileNameInUTF8,
+ const char* fileNameOutUTF8));
+ WEBRTC_STUB(ConvertCompressedToPCM, (webrtc::InStream* streamIn,
+ webrtc::OutStream* streamOut));
+ WEBRTC_STUB(GetFileDuration, (const char* fileNameUTF8, int& durationMs,
+ webrtc::FileFormats format));
+ WEBRTC_STUB(GetPlaybackPosition, (int channel, int& positionMs));
+
+ // webrtc::VoEHardware
+ WEBRTC_STUB(GetCPULoad, (int&));
+ WEBRTC_STUB(GetSystemCPULoad, (int&));
+ WEBRTC_FUNC(GetNumOfRecordingDevices, (int& num)) {
+ return GetNumDevices(num);
+ }
+ WEBRTC_FUNC(GetNumOfPlayoutDevices, (int& num)) {
+ return GetNumDevices(num);
+ }
+ WEBRTC_FUNC(GetRecordingDeviceName, (int i, char* name, char* guid)) {
+ return GetDeviceName(i, name, guid);
+ }
+ WEBRTC_FUNC(GetPlayoutDeviceName, (int i, char* name, char* guid)) {
+ return GetDeviceName(i, name, guid);
+ }
+ WEBRTC_STUB(SetRecordingDevice, (int, webrtc::StereoChannel));
+ WEBRTC_STUB(SetPlayoutDevice, (int));
+ WEBRTC_STUB(SetAudioDeviceLayer, (webrtc::AudioLayers));
+ WEBRTC_STUB(GetAudioDeviceLayer, (webrtc::AudioLayers&));
+ WEBRTC_STUB(GetPlayoutDeviceStatus, (bool&));
+ WEBRTC_STUB(GetRecordingDeviceStatus, (bool&));
+ WEBRTC_STUB(ResetAudioDevice, ());
+ WEBRTC_STUB(AudioDeviceControl, (unsigned int, unsigned int, unsigned int));
+ WEBRTC_STUB(NeedMorePlayData, (short int*, int, int, int, int&));
+ WEBRTC_STUB(RecordedDataIsAvailable, (short int*, int, int, int, int&));
+ WEBRTC_STUB(GetDevice, (char*, unsigned int));
+ WEBRTC_STUB(GetPlatform, (char*, unsigned int));
+ WEBRTC_STUB(GetOS, (char*, unsigned int));
+ WEBRTC_STUB(SetGrabPlayout, (bool));
+ WEBRTC_STUB(SetGrabRecording, (bool));
+ WEBRTC_STUB(SetLoudspeakerStatus, (bool enable));
+ WEBRTC_STUB(GetLoudspeakerStatus, (bool& enabled));
+ WEBRTC_STUB(EnableBuiltInAEC, (bool enable));
+ virtual bool BuiltInAECIsEnabled() const { return true; }
+ WEBRTC_STUB(SetSamplingRate, (int));
+ WEBRTC_STUB(GetSamplingRate, (int&));
+
+ // webrtc::VoENetEqStats
+ WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
+ WEBRTC_STUB(GetPreferredBufferSize, (int, short unsigned int&));
+ WEBRTC_STUB(ResetJitterStatistics, (int));
+
+ // webrtc::VoENetwork
+ WEBRTC_FUNC(RegisterExternalTransport, (int channel,
+ webrtc::Transport& transport)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->external_transport = true;
+ return 0;
+ }
+ WEBRTC_FUNC(DeRegisterExternalTransport, (int channel)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->external_transport = false;
+ return 0;
+ }
+ WEBRTC_FUNC(ReceivedRTPPacket, (int channel, const void* data,
+ unsigned int length)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ if (!channels_[channel]->external_transport) return -1;
+ channels_[channel]->packets.push_back(
+ std::string(static_cast<const char*>(data), length));
+ return 0;
+ }
+ WEBRTC_STUB(ReceivedRTCPPacket, (int channel, const void* data,
+ unsigned int length));
+ WEBRTC_STUB(GetSourceInfo, (int channel, int& rtpPort, int& rtcpPort,
+ char ipaddr[64]));
+ WEBRTC_STUB(GetLocalIP, (char ipaddr[64], bool ipv6));
+ WEBRTC_STUB(EnableIPv6, (int channel));
+ // Not using WEBRTC_STUB due to bool return value
+ virtual bool IPv6IsEnabled(int channel) { return true; }
+ WEBRTC_STUB(SetSourceFilter, (int channel, int rtpPort, int rtcpPort,
+ const char ipaddr[64]));
+ WEBRTC_STUB(GetSourceFilter, (int channel, int& rtpPort, int& rtcpPort,
+ char ipaddr[64]));
+ WEBRTC_STUB(SetSendTOS, (int channel, int priority,
+ int DSCP, bool useSetSockopt));
+ WEBRTC_STUB(GetSendTOS, (int channel, int& priority,
+ int& DSCP, bool& useSetSockopt));
+ WEBRTC_STUB(SetSendGQoS, (int channel, bool enable, int serviceType,
+ int overrideDSCP));
+ WEBRTC_STUB(GetSendGQoS, (int channel, bool& enabled, int& serviceType,
+ int& overrideDSCP));
+ WEBRTC_STUB(SetPacketTimeoutNotification, (int channel, bool enable,
+ int timeoutSeconds));
+ WEBRTC_STUB(GetPacketTimeoutNotification, (int channel, bool& enable,
+ int& timeoutSeconds));
+ WEBRTC_STUB(RegisterDeadOrAliveObserver, (int channel,
+ webrtc::VoEConnectionObserver& observer));
+ WEBRTC_STUB(DeRegisterDeadOrAliveObserver, (int channel));
+ WEBRTC_STUB(GetPeriodicDeadOrAliveStatus, (int channel, bool& enabled,
+ int& sampleTimeSeconds));
+ WEBRTC_STUB(SetPeriodicDeadOrAliveStatus, (int channel, bool enable,
+ int sampleTimeSeconds));
+ WEBRTC_STUB(SendUDPPacket, (int channel, const void* data,
+ unsigned int length, int& transmittedBytes,
+ bool useRtcpSocket));
+
+ // webrtc::VoERTP_RTCP
+ WEBRTC_STUB(RegisterRTPObserver, (int channel,
+ webrtc::VoERTPObserver& observer));
+ WEBRTC_STUB(DeRegisterRTPObserver, (int channel));
+ WEBRTC_STUB(RegisterRTCPObserver, (int channel,
+ webrtc::VoERTCPObserver& observer));
+ WEBRTC_STUB(DeRegisterRTCPObserver, (int channel));
+ WEBRTC_FUNC(SetLocalSSRC, (int channel, unsigned int ssrc)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->send_ssrc = ssrc;
+ return 0;
+ }
+ WEBRTC_FUNC(GetLocalSSRC, (int channel, unsigned int& ssrc)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ ssrc = channels_[channel]->send_ssrc;
+ return 0;
+ }
+ WEBRTC_STUB(GetRemoteSSRC, (int channel, unsigned int& ssrc));
+ WEBRTC_FUNC(SetRTPAudioLevelIndicationStatus, (int channel, bool enable,
+ unsigned char ID)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->level_header_ext_ = (enable) ? ID : -1;
+ return 0;
+ }
+ WEBRTC_FUNC(GetRTPAudioLevelIndicationStatus, (int channel, bool& enabled,
+ unsigned char& ID)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ enabled = (channels_[channel]->level_header_ext_ != -1);
+ ID = channels_[channel]->level_header_ext_;
+ return 0;
+ }
+ WEBRTC_STUB(GetRemoteCSRCs, (int channel, unsigned int arrCSRC[15]));
+ WEBRTC_STUB(GetRemoteEnergy, (int channel, unsigned char arrEnergy[15]));
+ WEBRTC_STUB(SetRTCPStatus, (int channel, bool enable));
+ WEBRTC_STUB(GetRTCPStatus, (int channel, bool& enabled));
+ WEBRTC_STUB(SetRTCP_CNAME, (int channel, const char cname[256]));
+ WEBRTC_STUB(GetRTCP_CNAME, (int channel, char cname[256]));
+ WEBRTC_STUB(GetRemoteRTCP_CNAME, (int channel, char* cname));
+ WEBRTC_STUB(GetRemoteRTCPData, (int channel, unsigned int& NTPHigh,
+ unsigned int& NTPLow,
+ unsigned int& timestamp,
+ unsigned int& playoutTimestamp,
+ unsigned int* jitter,
+ unsigned short* fractionLost));
+ WEBRTC_STUB(SendApplicationDefinedRTCPPacket, (int channel,
+ const unsigned char subType,
+ unsigned int name,
+ const char* data,
+ unsigned short dataLength));
+ WEBRTC_STUB(GetRTPStatistics, (int channel, unsigned int& averageJitterMs,
+ unsigned int& maxJitterMs,
+ unsigned int& discardedPackets));
+ WEBRTC_STUB(GetRTCPStatistics, (int channel, unsigned short& fractionLost,
+ unsigned int& cumulativeLost,
+ unsigned int& extendedMax,
+ unsigned int& jitterSamples, int& rttMs));
+ WEBRTC_STUB(GetRTCPStatistics, (int channel, webrtc::CallStatistics& stats));
+ WEBRTC_FUNC(SetFECStatus, (int channel, bool enable, int redPayloadtype)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->fec = enable;
+ channels_[channel]->fec_type = redPayloadtype;
+ return 0;
+ }
+ WEBRTC_FUNC(GetFECStatus, (int channel, bool& enable, int& redPayloadtype)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ enable = channels_[channel]->fec;
+ redPayloadtype = channels_[channel]->fec_type;
+ return 0;
+ }
+ WEBRTC_STUB(SetRTPKeepaliveStatus, (int channel, bool enable,
+ unsigned char unknownPayloadType,
+ int deltaTransmitTimeSeconds));
+ WEBRTC_STUB(GetRTPKeepaliveStatus, (int channel, bool& enabled,
+ unsigned char& unknownPayloadType,
+ int& deltaTransmitTimeSeconds));
+ WEBRTC_STUB(StartRTPDump, (int channel, const char* fileNameUTF8,
+ webrtc::RTPDirections direction));
+ WEBRTC_STUB(StopRTPDump, (int channel, webrtc::RTPDirections direction));
+ WEBRTC_STUB(RTPDumpIsActive, (int channel, webrtc::RTPDirections direction));
+ WEBRTC_STUB(InsertExtraRTPPacket, (int channel, unsigned char payloadType,
+ bool markerBit, const char* payloadData,
+ unsigned short payloadSize));
+
+ // webrtc::VoEVideoSync
+ WEBRTC_STUB(GetPlayoutBufferSize, (int& bufferMs));
+ WEBRTC_STUB(GetPlayoutTimestamp, (int channel, unsigned int& timestamp));
+ WEBRTC_STUB(GetRtpRtcp, (int, webrtc::RtpRtcp*&));
+ WEBRTC_STUB(SetInitTimestamp, (int channel, unsigned int timestamp));
+ WEBRTC_STUB(SetInitSequenceNumber, (int channel, short sequenceNumber));
+ WEBRTC_STUB(SetMinimumPlayoutDelay, (int channel, int delayMs));
+ WEBRTC_STUB(GetDelayEstimate, (int channel, int& delayMs));
+ WEBRTC_STUB(GetSoundcardBufferSize, (int& bufferMs));
+
+ // webrtc::VoEVolumeControl
+ WEBRTC_STUB(SetSpeakerVolume, (unsigned int));
+ WEBRTC_STUB(GetSpeakerVolume, (unsigned int&));
+ WEBRTC_STUB(SetSystemOutputMute, (bool));
+ WEBRTC_STUB(GetSystemOutputMute, (bool&));
+ WEBRTC_STUB(SetMicVolume, (unsigned int));
+ WEBRTC_STUB(GetMicVolume, (unsigned int&));
+ WEBRTC_STUB(SetInputMute, (int, bool));
+ WEBRTC_STUB(GetInputMute, (int, bool&));
+ WEBRTC_STUB(SetSystemInputMute, (bool));
+ WEBRTC_STUB(GetSystemInputMute, (bool&));
+ WEBRTC_STUB(GetSpeechInputLevel, (unsigned int&));
+ WEBRTC_STUB(GetSpeechOutputLevel, (int, unsigned int&));
+ WEBRTC_STUB(GetSpeechInputLevelFullRange, (unsigned int&));
+ WEBRTC_STUB(GetSpeechOutputLevelFullRange, (int, unsigned int&));
+ WEBRTC_STUB(SetChannelOutputVolumeScaling, (int, float));
+ WEBRTC_STUB(GetChannelOutputVolumeScaling, (int, float&));
+ WEBRTC_STUB(SetOutputVolumePan, (int, float, float));
+ WEBRTC_STUB(GetOutputVolumePan, (int, float&, float&));
+
+ // webrtc::VoEAudioProcessing
+ WEBRTC_FUNC(SetNsStatus, (bool enable, webrtc::NsModes mode)) {
+ ns_enabled_ = enable;
+ ns_mode_ = mode;
+ return 0;
+ }
+ WEBRTC_FUNC(GetNsStatus, (bool& enabled, webrtc::NsModes& mode)) {
+ enabled = ns_enabled_;
+ mode = ns_mode_;
+ return 0;
+ }
+ WEBRTC_STUB(SetAgcStatus, (bool enable, webrtc::AgcModes mode));
+ WEBRTC_STUB(GetAgcStatus, (bool& enabled, webrtc::AgcModes& mode));
+
+ WEBRTC_FUNC(SetAgcConfig, (const webrtc::AgcConfig config)) {
+ agc_config_ = config;
+ return 0;
+ }
+ WEBRTC_FUNC(GetAgcConfig, (webrtc::AgcConfig& config)) {
+ config = agc_config_;
+ return 0;
+ }
+ WEBRTC_FUNC(SetEcStatus, (bool enable, webrtc::EcModes mode)) {
+ ec_enabled_ = enable;
+ ec_mode_ = mode;
+ return 0;
+ }
+ WEBRTC_FUNC(GetEcStatus, (bool& enabled, webrtc::EcModes& mode)) {
+ enabled = ec_enabled_;
+ mode = ec_mode_;
+ return 0;
+ }
+ WEBRTC_STUB(SetAecmMode, (webrtc::AecmModes mode, bool enableCNG));
+ WEBRTC_STUB(GetAecmMode, (webrtc::AecmModes& mode, bool& enabledCNG));
+ WEBRTC_STUB(SetRxNsStatus, (int channel, bool enable, webrtc::NsModes mode));
+ WEBRTC_STUB(GetRxNsStatus, (int channel, bool& enabled,
+ webrtc::NsModes& mode));
+ WEBRTC_STUB(SetRxAgcStatus, (int channel, bool enable,
+ webrtc::AgcModes mode));
+ WEBRTC_STUB(GetRxAgcStatus, (int channel, bool& enabled,
+ webrtc::AgcModes& mode));
+ WEBRTC_STUB(SetRxAgcConfig, (int channel, const webrtc::AgcConfig config));
+ WEBRTC_STUB(GetRxAgcConfig, (int channel, webrtc::AgcConfig& config));
+
+ WEBRTC_STUB(RegisterRxVadObserver, (int, webrtc::VoERxVadCallback&));
+ WEBRTC_STUB(DeRegisterRxVadObserver, (int channel));
+ WEBRTC_STUB(VoiceActivityIndicator, (int channel));
+ WEBRTC_STUB(SetEcMetricsStatus, (bool enable));
+ WEBRTC_STUB(GetEcMetricsStatus, (bool& enable));
+ WEBRTC_STUB(GetEchoMetrics, (int& ERL, int& ERLE, int& RERL, int& A_NLP));
+ WEBRTC_STUB(GetEcDelayMetrics, (int& delay_median, int& delay_std));
+
+ WEBRTC_STUB(StartDebugRecording, (const char* fileNameUTF8));
+ WEBRTC_STUB(StopDebugRecording, ());
+
+ WEBRTC_STUB(SetTypingDetectionStatus, (bool enable));
+ WEBRTC_STUB(GetTypingDetectionStatus, (bool& enabled));
+
+ // webrtc::VoEExternalMedia
+ WEBRTC_FUNC(RegisterExternalMediaProcessing,
+ (int channel, webrtc::ProcessingTypes type,
+ webrtc::VoEMediaProcess& processObject)) {
+ media_processor_ = &processObject;
+ return 0;
+ }
+ WEBRTC_FUNC(DeRegisterExternalMediaProcessing,
+ (int channel, webrtc::ProcessingTypes type)) {
+ media_processor_ = NULL;
+ return 0;
+ }
+ WEBRTC_STUB(SetExternalRecordingStatus, (bool enable));
+ WEBRTC_STUB(SetExternalPlayoutStatus, (bool enable));
+ WEBRTC_STUB(ExternalRecordingInsertData,
+ (const WebRtc_Word16 speechData10ms[], int lengthSamples,
+ int samplingFreqHz, int current_delay_ms));
+ WEBRTC_STUB(ExternalPlayoutGetData,
+ (WebRtc_Word16 speechData10ms[], int samplingFreqHz,
+ int current_delay_ms, int& lengthSamples));
+
+ private:
+ int GetNumDevices(int& num) {
+#ifdef WIN32
+ num = 1;
+#else
+ // On non-Windows platforms VE adds a special entry for the default device,
+ // so if there is one physical device then there are two entries in the
+ // list.
+ num = 2;
+#endif
+ return 0;
+ }
+
+ int GetDeviceName(int i, char* name, char* guid) {
+ const char *s;
+#ifdef WIN32
+ if (0 == i) {
+ s = kFakeDeviceName;
+ } else {
+ return -1;
+ }
+#else
+ // See comment above.
+ if (0 == i) {
+ s = kFakeDefaultDeviceName;
+ } else if (1 == i) {
+ s = kFakeDeviceName;
+ } else {
+ return -1;
+ }
+#endif
+ strcpy(name, s);
+ guid[0] = '\0';
+ return 0;
+ }
+
+ bool inited_;
+ int last_channel_;
+ std::map<int, Channel*> channels_;
+ bool fail_create_channel_;
+ const cricket::AudioCodec* const* codecs_;
+ int num_codecs_;
+ bool ec_enabled_;
+ bool ns_enabled_;
+ webrtc::EcModes ec_mode_;
+ webrtc::NsModes ns_mode_;
+ webrtc::AgcConfig agc_config_;
+ webrtc::VoiceEngineObserver* observer_;
+ int playout_fail_channel_;
+ int send_fail_channel_;
+ bool fail_start_recording_microphone_;
+ bool recording_microphone_;
+ webrtc::VoEMediaProcess* media_processor_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FAKEWEBRTCVOICEENGINE_H_
diff --git a/talk/session/phone/filemediaengine.cc b/talk/session/phone/filemediaengine.cc
index 2a7b8d8..6f2c5e1 100644
--- a/talk/session/phone/filemediaengine.cc
+++ b/talk/session/phone/filemediaengine.cc
@@ -34,6 +34,7 @@
#include "talk/base/stream.h"
#include "talk/session/phone/rtpdump.h"
#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/streamparams.h"
namespace cricket {
@@ -258,7 +259,8 @@
FileVoiceChannel::FileVoiceChannel(
talk_base::StreamInterface* input_file_stream,
talk_base::StreamInterface* output_file_stream)
- : rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+ : send_ssrc_(0),
+ rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
output_file_stream)) {}
FileVoiceChannel::~FileVoiceChannel() {}
@@ -272,8 +274,22 @@
return rtp_sender_receiver_->SetSend(flag != SEND_NOTHING);
}
-void FileVoiceChannel::SetSendSsrc(uint32 ssrc) {
- rtp_sender_receiver_->SetSendSsrc(ssrc);
+bool FileVoiceChannel::AddSendStream(const StreamParams& sp) {
+ if (send_ssrc_ != 0 || sp.ssrcs.size() != 1) {
+ LOG(LS_ERROR) << "FileVoiceChannel only supports one send stream.";
+ return false;
+ }
+ send_ssrc_ = sp.ssrcs[0];
+ rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+ return true;
+}
+
+bool FileVoiceChannel::RemoveSendStream(uint32 ssrc) {
+ if (ssrc != send_ssrc_)
+ return false;
+ send_ssrc_ = 0;
+ rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+ return true;
}
void FileVoiceChannel::OnPacketReceived(talk_base::Buffer* packet) {
@@ -286,7 +302,8 @@
FileVideoChannel::FileVideoChannel(
talk_base::StreamInterface* input_file_stream,
talk_base::StreamInterface* output_file_stream)
- : rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
+ : send_ssrc_(0),
+ rtp_sender_receiver_(new RtpSenderReceiver(this, input_file_stream,
output_file_stream)) {}
FileVideoChannel::~FileVideoChannel() {}
@@ -300,8 +317,22 @@
return rtp_sender_receiver_->SetSend(send);
}
-void FileVideoChannel::SetSendSsrc(uint32 ssrc) {
- rtp_sender_receiver_->SetSendSsrc(ssrc);
+bool FileVideoChannel::AddSendStream(const StreamParams& sp) {
+ if (send_ssrc_ != 0 || sp.ssrcs.size() != 1) {
+ LOG(LS_ERROR) << "FileVideoChannel only support one send stream.";
+ return false;
+ }
+ send_ssrc_ = sp.ssrcs[0];
+ rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+ return true;
+}
+
+bool FileVideoChannel::RemoveSendStream(uint32 ssrc) {
+ if (ssrc != send_ssrc_)
+ return false;
+ send_ssrc_ = 0;
+ rtp_sender_receiver_->SetSendSsrc(send_ssrc_);
+ return true;
}
void FileVideoChannel::OnPacketReceived(talk_base::Buffer* packet) {
diff --git a/talk/session/phone/filemediaengine.h b/talk/session/phone/filemediaengine.h
index b20e0f2..03d91ca 100644
--- a/talk/session/phone/filemediaengine.h
+++ b/talk/session/phone/filemediaengine.h
@@ -93,7 +93,13 @@
return true;
}
virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
- virtual bool GetOutputVolume(int* level) { *level = 0; return true; }
+ virtual bool SetVideoCapturer(VideoCapturer* /*capturer*/, uint32 /*ssrc*/) {
+ return true;
+ }
+ virtual bool GetOutputVolume(int* level) {
+ *level = 0;
+ return true;
+ }
virtual bool SetOutputVolume(int level) { return true; }
virtual int GetInputLevel() { return 0; }
virtual bool SetLocalMonitor(bool enable) { return true; }
@@ -111,6 +117,23 @@
virtual void SetVoiceLogging(int min_sev, const char* filter) {}
virtual void SetVideoLogging(int min_sev, const char* filter) {}
+ virtual bool RegisterVideoProcessor(VideoProcessor* processor) {
+ return true;
+ }
+ virtual bool UnregisterVideoProcessor(VideoProcessor* processor) {
+ return true;
+ }
+ virtual bool RegisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return true;
+ }
+ virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return true;
+ }
+
private:
std::string voice_input_filename_;
std::string voice_output_filename_;
@@ -145,8 +168,6 @@
}
virtual bool SetPlayout(bool playout) { return true; }
virtual bool SetSend(SendFlags flag);
- virtual bool AddStream(uint32 ssrc) { return true; }
- virtual bool RemoveStream(uint32 ssrc) { return true; }
virtual bool GetActiveStreams(AudioInfo::StreamList* actives) { return true; }
virtual int GetOutputLevel() { return 0; }
virtual bool SetOutputScaling(uint32 ssrc, double left, double right) {
@@ -165,13 +186,16 @@
// Implement pure virtual methods of MediaChannel.
virtual void OnPacketReceived(talk_base::Buffer* packet);
virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
- virtual void SetSendSsrc(uint32 ssrc);
- virtual bool SetRtcpCName(const std::string& cname) { return true; }
+ virtual bool AddSendStream(const StreamParams& sp);
+ virtual bool RemoveSendStream(uint32 ssrc);
+ virtual bool AddRecvStream(const StreamParams& sp) { return true; }
+ virtual bool RemoveRecvStream(uint32 ssrc) { return true; }
virtual bool Mute(bool on) { return false; }
virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
virtual bool SetOptions(int options) { return true; }
private:
+ uint32 send_ssrc_;
talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
DISALLOW_COPY_AND_ASSIGN(FileVoiceChannel);
};
@@ -187,6 +211,9 @@
return true;
}
virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+ virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) {
+ return true;
+ }
virtual bool SetRecvRtpHeaderExtensions(
const std::vector<RtpHeaderExtension>& extensions) {
return true;
@@ -197,11 +224,13 @@
}
virtual bool SetRender(bool render) { return true; }
virtual bool SetSend(bool send);
- virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) { return true; }
- virtual bool RemoveStream(uint32 ssrc) { return true; }
virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
return true;
}
+ virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+ return true;
+ }
+ virtual bool RemoveScreencast(uint32 ssrc) { return true; }
virtual bool GetStats(VideoMediaInfo* info) { return true; }
virtual bool SendIntraFrame() { return false; }
virtual bool RequestIntraFrame() { return false; }
@@ -209,13 +238,16 @@
// Implement pure virtual methods of MediaChannel.
virtual void OnPacketReceived(talk_base::Buffer* packet);
virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
- virtual void SetSendSsrc(uint32 ssrc);
- virtual bool SetRtcpCName(const std::string& cname) { return true; }
+ virtual bool AddSendStream(const StreamParams& sp);
+ virtual bool RemoveSendStream(uint32 ssrc);
+ virtual bool AddRecvStream(const StreamParams& sp) { return true; }
+ virtual bool RemoveRecvStream(uint32 ssrc) { return true; }
virtual bool Mute(bool on) { return false; }
virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
virtual bool SetOptions(int options) { return true; }
private:
+ uint32 send_ssrc_;
talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
DISALLOW_COPY_AND_ASSIGN(FileVideoChannel);
};
diff --git a/talk/session/phone/filemediaengine_unittest.cc b/talk/session/phone/filemediaengine_unittest.cc
new file mode 100644
index 0000000..c915251
--- /dev/null
+++ b/talk/session/phone/filemediaengine_unittest.cc
@@ -0,0 +1,463 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <set>
+
+#include "talk/base/buffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/filemediaengine.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+static const int kWaitTimeMs = 100;
+static const std::string kFakeFileName = "foobar";
+
+//////////////////////////////////////////////////////////////////////////////
+// Media channel sends RTP packets via NetworkInterface. Rather than sending
+// packets to the network, FileNetworkInterface writes packets to a stream and
+// feeds packets back to the channel via OnPacketReceived.
+//////////////////////////////////////////////////////////////////////////////
+class FileNetworkInterface : public MediaChannel::NetworkInterface {
+ public:
+ FileNetworkInterface(talk_base::StreamInterface* output, MediaChannel* ch)
+ : media_channel_(ch),
+ num_sent_packets_(0) {
+ if (output) {
+ dump_writer_.reset(new RtpDumpWriter(output));
+ }
+ }
+
+ // Implement pure virtual methods of NetworkInterface.
+ virtual bool SendPacket(talk_base::Buffer* packet) {
+ if (!packet) return false;
+
+ if (media_channel_) {
+ media_channel_->OnPacketReceived(packet);
+ }
+ if (dump_writer_.get() &&
+ talk_base::SR_SUCCESS != dump_writer_->WriteRtpPacket(
+ packet->data(), packet->length())) {
+ return false;
+ }
+
+ ++num_sent_packets_;
+ return true;
+ }
+
+ virtual bool SendRtcp(talk_base::Buffer* packet) { return false; }
+ virtual int SetOption(MediaChannel::NetworkInterface::SocketType type,
+ talk_base::Socket::Option opt, int option) {
+ return 0;
+ }
+
+ size_t num_sent_packets() const { return num_sent_packets_; }
+
+ private:
+ MediaChannel* media_channel_;
+ talk_base::scoped_ptr<RtpDumpWriter> dump_writer_;
+ size_t num_sent_packets_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileNetworkInterface);
+};
+
+class FileMediaEngineTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ setup_ok_ = true;
+ setup_ok_ &= GetTempFilename(&voice_input_filename_);
+ setup_ok_ &= GetTempFilename(&voice_output_filename_);
+ setup_ok_ &= GetTempFilename(&video_input_filename_);
+ setup_ok_ &= GetTempFilename(&video_output_filename_);
+ }
+ virtual void TearDown() {
+ // Force to close the dump files, if opened.
+ voice_channel_.reset();
+ video_channel_.reset();
+
+ DeleteTempFile(voice_input_filename_);
+ DeleteTempFile(voice_output_filename_);
+ DeleteTempFile(video_input_filename_);
+ DeleteTempFile(video_output_filename_);
+ }
+
+ protected:
+ bool CreateEngineAndChannels(const std::string& voice_in,
+ const std::string& voice_out,
+ const std::string& video_in,
+ const std::string& video_out,
+ size_t ssrc_count) {
+ // Force to close the dump files, if opened.
+ voice_channel_.reset();
+ video_channel_.reset();
+
+ bool ret = setup_ok_;
+ if (!voice_in.empty()) {
+ ret &= WriteTestPacketsToFile(voice_in, ssrc_count);
+ }
+ if (!video_in.empty()) {
+ ret &= WriteTestPacketsToFile(video_in, ssrc_count);
+ }
+
+ engine_.reset(new FileMediaEngine);
+ engine_->set_voice_input_filename(voice_in);
+ engine_->set_voice_output_filename(voice_out);
+ engine_->set_video_input_filename(video_in);
+ engine_->set_video_output_filename(video_out);
+
+ voice_channel_.reset(engine_->CreateChannel());
+ video_channel_.reset(engine_->CreateVideoChannel(NULL));
+
+ return ret;
+ }
+
+ bool GetTempFilename(std::string* filename) {
+ talk_base::Pathname temp_path;
+ if (!talk_base::Filesystem::GetTemporaryFolder(temp_path, true, NULL)) {
+ return false;
+ }
+ temp_path.SetPathname(
+ talk_base::Filesystem::TempFilename(temp_path, "fme-test-"));
+
+ if (filename) {
+ *filename = temp_path.pathname();
+ }
+ return true;
+ }
+
+ bool WriteTestPacketsToFile(const std::string& filename, size_t ssrc_count) {
+ talk_base::scoped_ptr<talk_base::StreamInterface> stream(
+ talk_base::Filesystem::OpenFile(talk_base::Pathname(filename), "wb"));
+ bool ret = (NULL != stream.get());
+ RtpDumpWriter writer(stream.get());
+
+ for (size_t i = 0; i < ssrc_count; ++i) {
+ ret &= RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), false,
+ RtpTestUtility::kDefaultSsrc + i, &writer);
+ }
+ return ret;
+ }
+
+ void DeleteTempFile(std::string filename) {
+ talk_base::Pathname pathname(filename);
+ if (talk_base::Filesystem::IsFile(talk_base::Pathname(pathname))) {
+ talk_base::Filesystem::DeleteFile(pathname);
+ }
+ }
+
+ bool GetSsrcAndPacketCounts(talk_base::StreamInterface* stream,
+ size_t* ssrc_count, size_t* packet_count) {
+ talk_base::scoped_ptr<RtpDumpReader> reader(new RtpDumpReader(stream));
+ size_t count = 0;
+ RtpDumpPacket packet;
+ std::set<uint32> ssrcs;
+ while (talk_base::SR_SUCCESS == reader->ReadPacket(&packet)) {
+ count++;
+ uint32 ssrc;
+ if (!packet.GetRtpSsrc(&ssrc)) {
+ return false;
+ }
+ ssrcs.insert(ssrc);
+ }
+ if (ssrc_count) {
+ *ssrc_count = ssrcs.size();
+ }
+ if (packet_count) {
+ *packet_count = count;
+ }
+ return true;
+ }
+
+ static const uint32 kWaitTimeout = 3000;
+ bool setup_ok_;
+ std::string voice_input_filename_;
+ std::string voice_output_filename_;
+ std::string video_input_filename_;
+ std::string video_output_filename_;
+ talk_base::scoped_ptr<FileMediaEngine> engine_;
+ talk_base::scoped_ptr<VoiceMediaChannel> voice_channel_;
+ talk_base::scoped_ptr<VideoMediaChannel> video_channel_;
+};
+
+TEST_F(FileMediaEngineTest, TestDefaultImplementation) {
+ EXPECT_TRUE(CreateEngineAndChannels("", "", "", "", 1));
+ EXPECT_TRUE(engine_->Init());
+ EXPECT_EQ(0, engine_->GetCapabilities());
+ EXPECT_TRUE(NULL == voice_channel_.get());
+ EXPECT_TRUE(NULL == video_channel_.get());
+ EXPECT_TRUE(NULL == engine_->CreateSoundclip());
+ EXPECT_TRUE(engine_->SetAudioOptions(0));
+ EXPECT_TRUE(engine_->SetVideoOptions(0));
+ VideoEncoderConfig video_encoder_config;
+ EXPECT_TRUE(engine_->SetDefaultVideoEncoderConfig(video_encoder_config));
+ EXPECT_TRUE(engine_->SetSoundDevices(NULL, NULL));
+ EXPECT_TRUE(engine_->SetVideoCaptureDevice(NULL));
+ EXPECT_TRUE(engine_->SetOutputVolume(0));
+ EXPECT_EQ(0, engine_->GetInputLevel());
+ EXPECT_TRUE(engine_->SetLocalMonitor(true));
+ EXPECT_TRUE(engine_->SetLocalRenderer(NULL));
+ EXPECT_EQ(CR_SUCCESS, engine_->SetVideoCapture(true));
+ EXPECT_EQ(0U, engine_->audio_codecs().size());
+ EXPECT_EQ(0U, engine_->video_codecs().size());
+ AudioCodec voice_codec;
+ EXPECT_TRUE(engine_->FindAudioCodec(voice_codec));
+ VideoCodec video_codec;
+ EXPECT_TRUE(engine_->FindVideoCodec(video_codec));
+ engine_->Terminate();
+}
+
+// Test that when file path is not pointing to a valid stream file, the channel
+// creation function should fail and return NULL.
+TEST_F(FileMediaEngineTest, TestBadFilePath) {
+ engine_.reset(new FileMediaEngine);
+ engine_->set_voice_input_filename(kFakeFileName);
+ engine_->set_video_input_filename(kFakeFileName);
+ EXPECT_TRUE(engine_->CreateChannel() == NULL);
+ EXPECT_TRUE(engine_->CreateVideoChannel(NULL) == NULL);
+}
+
+TEST_F(FileMediaEngineTest, TestCodecs) {
+ EXPECT_TRUE(CreateEngineAndChannels("", "", "", "", 1));
+ std::vector<AudioCodec> voice_codecs = engine_->audio_codecs();
+ std::vector<VideoCodec> video_codecs = engine_->video_codecs();
+ EXPECT_EQ(0U, voice_codecs.size());
+ EXPECT_EQ(0U, video_codecs.size());
+
+ AudioCodec voice_codec(103, "ISAC", 16000, 0, 1, 0);
+ voice_codecs.push_back(voice_codec);
+ engine_->set_voice_codecs(voice_codecs);
+ voice_codecs = engine_->audio_codecs();
+ ASSERT_EQ(1U, voice_codecs.size());
+ EXPECT_EQ(voice_codec, voice_codecs[0]);
+
+ VideoCodec video_codec(96, "H264-SVC", 320, 240, 30, 0);
+ video_codecs.push_back(video_codec);
+ engine_->set_video_codecs(video_codecs);
+ video_codecs = engine_->video_codecs();
+ ASSERT_EQ(1U, video_codecs.size());
+ EXPECT_EQ(video_codec, video_codecs[0]);
+}
+
+// Test that the capabilities and channel creation of the Filemedia engine
+// depend on the stream parameters passed to its constructor.
+TEST_F(FileMediaEngineTest, TestGetCapabilities) {
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_, "", "", "", 1));
+ EXPECT_EQ(AUDIO_SEND, engine_->GetCapabilities());
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ EXPECT_TRUE(NULL == video_channel_.get());
+
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_, "", "", 1));
+ EXPECT_EQ(AUDIO_SEND | AUDIO_RECV, engine_->GetCapabilities());
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ EXPECT_TRUE(NULL == video_channel_.get());
+
+ EXPECT_TRUE(CreateEngineAndChannels("", "", video_input_filename_, "", 1));
+ EXPECT_EQ(VIDEO_SEND, engine_->GetCapabilities());
+ EXPECT_TRUE(NULL == voice_channel_.get());
+ EXPECT_TRUE(NULL != video_channel_.get());
+
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_,
+ video_input_filename_,
+ video_output_filename_,
+ 1));
+ EXPECT_EQ(AUDIO_SEND | AUDIO_RECV | VIDEO_SEND | VIDEO_RECV,
+ engine_->GetCapabilities());
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ EXPECT_TRUE(NULL != video_channel_.get());
+}
+
+// FileVideoChannel is the same as FileVoiceChannel in terms of receiving and
+// sending the RTP packets. We therefore test only FileVoiceChannel.
+
+// Test that SetSend() controls whether a voice channel sends RTP packets.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSetSend) {
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_, "", "", 1));
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ talk_base::MemoryStream net_dump;
+ FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+ voice_channel_->SetInterface(&net_interface);
+
+ // The channel is not sending yet.
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ EXPECT_EQ(0U, net_interface.num_sent_packets());
+
+ // The channel starts sending.
+ voice_channel_->SetSend(SEND_MICROPHONE);
+ EXPECT_TRUE_WAIT(net_interface.num_sent_packets() >= 1U, kWaitTimeout);
+
+ // The channel stops sending.
+ voice_channel_->SetSend(SEND_NOTHING);
+ // Wait until packets are all delivered.
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ size_t old_number = net_interface.num_sent_packets();
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ EXPECT_EQ(old_number, net_interface.num_sent_packets());
+
+ // The channel starts sending again.
+ voice_channel_->SetSend(SEND_MICROPHONE);
+ EXPECT_TRUE_WAIT(net_interface.num_sent_packets() > old_number, kWaitTimeout);
+
+ // When the function exits, the net_interface object is released. The sender
+ // thread may call net_interface to send packets, which results in a segment
+ // fault. We hence stop sending and wait until all packets are delivered
+ // before we exit this function.
+ voice_channel_->SetSend(SEND_NOTHING);
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+}
+
+// Test the sender thread of the channel. The sender sends RTP packets
+// continuously with proper sequence number, timestamp, and payload.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSenderThread) {
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_, "", "", 1));
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ talk_base::MemoryStream net_dump;
+ FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+ voice_channel_->SetInterface(&net_interface);
+
+ voice_channel_->SetSend(SEND_MICROPHONE);
+ // Wait until the number of sent packets is no less than 2 * kPacketNumber.
+ EXPECT_TRUE_WAIT(
+ net_interface.num_sent_packets() >=
+ 2 * RtpTestUtility::GetTestPacketCount(),
+ kWaitTimeout);
+ voice_channel_->SetSend(SEND_NOTHING);
+ // Wait until packets are all delivered.
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 2 * RtpTestUtility::GetTestPacketCount(), &net_dump,
+ RtpTestUtility::kDefaultSsrc));
+
+ // Each sent packet is dumped to net_dump and is also feed to the channel
+ // via OnPacketReceived, which in turn writes the packets into voice_output_.
+ // We next verify the packets in voice_output_.
+ voice_channel_.reset(); // Force to close the files.
+ talk_base::scoped_ptr<talk_base::StreamInterface> voice_output_;
+ voice_output_.reset(talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(voice_output_filename_), "rb"));
+ EXPECT_TRUE(voice_output_.get() != NULL);
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 2 * RtpTestUtility::GetTestPacketCount(), voice_output_.get(),
+ RtpTestUtility::kDefaultSsrc));
+}
+
+// Test that we can specify the ssrc for outgoing RTP packets.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSendSsrc) {
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_, "", "", 1));
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ const uint32 send_ssrc = RtpTestUtility::kDefaultSsrc + 1;
+ voice_channel_->AddSendStream(StreamParams::CreateLegacy(send_ssrc));
+
+ talk_base::MemoryStream net_dump;
+ FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+ voice_channel_->SetInterface(&net_interface);
+
+ voice_channel_->SetSend(SEND_MICROPHONE);
+ // Wait until the number of sent packets is no less than 2 * kPacketNumber.
+ EXPECT_TRUE_WAIT(
+ net_interface.num_sent_packets() >=
+ 2 * RtpTestUtility::GetTestPacketCount(),
+ kWaitTimeout);
+ voice_channel_->SetSend(SEND_NOTHING);
+ // Wait until packets are all delivered.
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 2 * RtpTestUtility::GetTestPacketCount(), &net_dump, send_ssrc));
+
+ // Each sent packet is dumped to net_dump and is also feed to the channel
+ // via OnPacketReceived, which in turn writes the packets into voice_output_.
+ // We next verify the packets in voice_output_.
+ voice_channel_.reset(); // Force to close the files.
+ talk_base::scoped_ptr<talk_base::StreamInterface> voice_output_;
+ voice_output_.reset(talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(voice_output_filename_), "rb"));
+ EXPECT_TRUE(voice_output_.get() != NULL);
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 2 * RtpTestUtility::GetTestPacketCount(), voice_output_.get(),
+ send_ssrc));
+}
+
+// Test the sender thread of the channel, where the input rtpdump has two SSRCs.
+TEST_F(FileMediaEngineTest, TestVoiceChannelSenderThreadTwoSsrcs) {
+ EXPECT_TRUE(CreateEngineAndChannels(voice_input_filename_,
+ voice_output_filename_, "", "", 2));
+ // Verify that voice_input_filename_ contains 2 *
+ // RtpTestUtility::GetTestPacketCount() packets
+ // with different SSRCs.
+ talk_base::scoped_ptr<talk_base::StreamInterface> input_stream(
+ talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(voice_input_filename_), "rb"));
+ ASSERT_TRUE(NULL != input_stream.get());
+ size_t ssrc_count;
+ size_t packet_count;
+ EXPECT_TRUE(GetSsrcAndPacketCounts(input_stream.get(), &ssrc_count,
+ &packet_count));
+ EXPECT_EQ(2U, ssrc_count);
+ EXPECT_EQ(2 * RtpTestUtility::GetTestPacketCount(), packet_count);
+ input_stream.reset();
+
+ // Send 2 * RtpTestUtility::GetTestPacketCount() packets and verify that all
+ // these packets have the same SSRCs (that is, the packets with different
+ // SSRCs are skipped by the filemediaengine).
+ EXPECT_TRUE(NULL != voice_channel_.get());
+ talk_base::MemoryStream net_dump;
+ FileNetworkInterface net_interface(&net_dump, voice_channel_.get());
+ voice_channel_->SetInterface(&net_interface);
+ voice_channel_->SetSend(SEND_MICROPHONE);
+ EXPECT_TRUE_WAIT(
+ net_interface.num_sent_packets() >=
+ 2 * RtpTestUtility::GetTestPacketCount(),
+ kWaitTimeout);
+ voice_channel_->SetSend(SEND_NOTHING);
+ // Wait until packets are all delivered.
+ talk_base::Thread::Current()->ProcessMessages(kWaitTimeMs);
+ net_dump.Rewind();
+ EXPECT_TRUE(GetSsrcAndPacketCounts(&net_dump, &ssrc_count, &packet_count));
+ EXPECT_EQ(1U, ssrc_count);
+ EXPECT_GE(packet_count, 2 * RtpTestUtility::GetTestPacketCount());
+}
+
+// Test SendIntraFrame() and RequestIntraFrame() of video channel.
+TEST_F(FileMediaEngineTest, TestVideoChannelIntraFrame) {
+ EXPECT_TRUE(CreateEngineAndChannels("", "", video_input_filename_,
+ video_output_filename_, 1));
+ EXPECT_TRUE(NULL != video_channel_.get());
+ EXPECT_FALSE(video_channel_->SendIntraFrame());
+ EXPECT_FALSE(video_channel_->RequestIntraFrame());
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/filevideocapturer.cc b/talk/session/phone/filevideocapturer.cc
new file mode 100644
index 0000000..00272c5
--- /dev/null
+++ b/talk/session/phone/filevideocapturer.cc
@@ -0,0 +1,350 @@
+// libjingle
+// Copyright 2004--2005, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Implementation of VideoRecorder and FileVideoCapturer.
+
+#include "talk/session/phone/filevideocapturer.h"
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of class VideoRecorder
+/////////////////////////////////////////////////////////////////////
+bool VideoRecorder::Start(const std::string& filename, bool write_header) {
+ Stop();
+ write_header_ = write_header;
+ int err;
+ if (!video_file_.Open(filename, "wb", &err)) {
+ LOG(LS_ERROR) << "Unable to open file " << filename << " err=" << err;
+ return false;
+ }
+ return true;
+}
+
+void VideoRecorder::Stop() {
+ video_file_.Close();
+}
+
+bool VideoRecorder::RecordFrame(const CapturedFrame& frame) {
+ if (talk_base::SS_CLOSED == video_file_.GetState()) {
+ LOG(LS_ERROR) << "File not opened yet";
+ return false;
+ }
+
+ uint32 size = 0;
+ if (!frame.GetDataSize(&size)) {
+ LOG(LS_ERROR) << "Unable to calculate the data size of the frame";
+ return false;
+ }
+
+ if (write_header_) {
+ // Convert the frame header to bytebuffer.
+ talk_base::ByteBuffer buffer;
+ buffer.WriteUInt32(frame.width);
+ buffer.WriteUInt32(frame.height);
+ buffer.WriteUInt32(frame.fourcc);
+ buffer.WriteUInt32(frame.pixel_width);
+ buffer.WriteUInt32(frame.pixel_height);
+ buffer.WriteUInt64(frame.elapsed_time);
+ buffer.WriteUInt64(frame.time_stamp);
+ buffer.WriteUInt32(size);
+
+ // Write the bytebuffer to file.
+ if (talk_base::SR_SUCCESS != video_file_.Write(buffer.Data(),
+ buffer.Length(),
+ NULL,
+ NULL)) {
+ LOG(LS_ERROR) << "Failed to write frame header";
+ return false;
+ }
+ }
+ // Write the frame data to file.
+ if (talk_base::SR_SUCCESS != video_file_.Write(frame.data,
+ size,
+ NULL,
+ NULL)) {
+ LOG(LS_ERROR) << "Failed to write frame data";
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Definition of private class FileReadThread that periodically reads
+// frames from a file.
+///////////////////////////////////////////////////////////////////////
+class FileVideoCapturer::FileReadThread
+ : public talk_base::Thread, public talk_base::MessageHandler {
+ public:
+ explicit FileReadThread(FileVideoCapturer* capturer)
+ : capturer_(capturer),
+ finished_(false) {
+ }
+
+ // Override virtual method of parent Thread. Context: Worker Thread.
+ virtual void Run() {
+ // Read the first frame and start the message pump. The pump runs until
+ // Stop() is called externally or Quit() is called by OnMessage().
+ int waiting_time_ms = 0;
+ if (capturer_ && capturer_->ReadFrame(true, &waiting_time_ms)) {
+ PostDelayed(waiting_time_ms, this);
+ Thread::Run();
+ }
+ finished_ = true;
+ }
+
+ // Override virtual method of parent MessageHandler. Context: Worker Thread.
+ virtual void OnMessage(talk_base::Message* /*pmsg*/) {
+ int waiting_time_ms = 0;
+ if (capturer_ && capturer_->ReadFrame(false, &waiting_time_ms)) {
+ PostDelayed(waiting_time_ms, this);
+ } else {
+ Quit();
+ }
+ }
+
+ // Check if Run() is finished.
+ bool Finished() const { return finished_; }
+
+ private:
+ FileVideoCapturer* capturer_;
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileReadThread);
+};
+
+/////////////////////////////////////////////////////////////////////
+// Implementation of class FileVideoCapturer
+/////////////////////////////////////////////////////////////////////
+static const int64 kNumNanoSecsPerMilliSec = 1000000;
+
+FileVideoCapturer::FileVideoCapturer()
+ : frame_buffer_size_(0),
+ file_read_thread_(NULL),
+ repeat_(0),
+ start_time_ns_(0),
+ last_frame_timestamp_ns_(0),
+ ignore_framerate_(false) {
+}
+
+FileVideoCapturer::~FileVideoCapturer() {
+ Stop();
+ delete[] static_cast<char*> (captured_frame_.data);
+}
+
+bool FileVideoCapturer::Init(const std::string& filename) {
+ if (IsRunning()) {
+ LOG(LS_ERROR) << "The file video capturer is already running";
+ return false;
+ }
+ // Open the file.
+ int err;
+ if (!video_file_.Open(filename, "rb", &err)) {
+ LOG(LS_ERROR) << "Unable to open the file " << filename << " err=" << err;
+ return false;
+ }
+ // Read the first frame's header to determine the supported format.
+ CapturedFrame frame;
+ if (talk_base::SR_SUCCESS != ReadFrameHeader(&frame)) {
+ LOG(LS_ERROR) << "Failed to read the first frame header";
+ video_file_.Close();
+ return false;
+ }
+ // Seek back to the start of the file.
+ if (!video_file_.SetPosition(0)) {
+ LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
+ video_file_.Close();
+ return false;
+ }
+
+ // Enumerate the supported formats. We have only one supported format. We set
+ // the frame interval to kMinimumInterval here. In Start(), if the capture
+ // format's interval is greater than kMinimumInterval, we use the interval;
+ // otherwise, we use the timestamp in the file to control the interval.
+ VideoFormat format(frame.width, frame.height, VideoFormat::kMinimumInterval,
+ frame.fourcc);
+ std::vector<VideoFormat> supported;
+ supported.push_back(format);
+
+ SetId(filename);
+ SetSupportedFormats(supported);
+ return true;
+}
+
+CaptureResult FileVideoCapturer::Start(const VideoFormat& capture_format) {
+ if (IsRunning()) {
+ LOG(LS_ERROR) << "The file video capturer is already running";
+ return CR_FAILURE;
+ }
+
+ if (talk_base::SS_CLOSED == video_file_.GetState()) {
+ LOG(LS_ERROR) << "File not opened yet";
+ return CR_NO_DEVICE;
+ } else if (!video_file_.SetPosition(0)) {
+ LOG(LS_ERROR) << "Failed to seek back to beginning of the file";
+ return CR_FAILURE;
+ }
+
+ SetCaptureFormat(&capture_format);
+ // Create a thread to read the file.
+ file_read_thread_ = new FileReadThread(this);
+ bool ret = file_read_thread_->Start();
+ start_time_ns_ = kNumNanoSecsPerMilliSec *
+ static_cast<int64>(talk_base::Time());
+ if (ret) {
+ LOG(LS_INFO) << "File video capturer '" << GetId() << "' started";
+ return CR_SUCCESS;
+ } else {
+ LOG(LS_ERROR) << "File video capturer '" << GetId() << "' failed to start";
+ return CR_FAILURE;
+ }
+}
+
+bool FileVideoCapturer::IsRunning() {
+ return file_read_thread_ && !file_read_thread_->Finished();
+}
+
+void FileVideoCapturer::Stop() {
+ if (file_read_thread_) {
+ file_read_thread_->Stop();
+ file_read_thread_ = NULL;
+ LOG(LS_INFO) << "File video capturer '" << GetId() << "' stopped";
+ }
+ SetCaptureFormat(NULL);
+}
+
+bool FileVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
+ if (!fourccs) {
+ return false;
+ }
+
+ fourccs->push_back(GetSupportedFormats()->at(0).fourcc);
+ return true;
+}
+
+talk_base::StreamResult FileVideoCapturer::ReadFrameHeader(
+ CapturedFrame* frame) {
+ // We first read kFrameHeaderSize bytes from the file stream to a memory
+ // buffer, then construct a bytebuffer from the memory buffer, and finally
+ // read the frame header from the bytebuffer.
+ char header[CapturedFrame::kFrameHeaderSize];
+ talk_base::StreamResult sr;
+ sr = video_file_.Read(header,
+ CapturedFrame::kFrameHeaderSize,
+ NULL,
+ NULL);
+ if (talk_base::SR_SUCCESS == sr) {
+ talk_base::ByteBuffer buffer(header, CapturedFrame::kFrameHeaderSize);
+ buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->width));
+ buffer.ReadUInt32(reinterpret_cast<uint32*>(&frame->height));
+ buffer.ReadUInt32(&frame->fourcc);
+ buffer.ReadUInt32(&frame->pixel_width);
+ buffer.ReadUInt32(&frame->pixel_height);
+ buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->elapsed_time));
+ buffer.ReadUInt64(reinterpret_cast<uint64*>(&frame->time_stamp));
+ buffer.ReadUInt32(&frame->data_size);
+ }
+
+ return sr;
+}
+
+// Executed in the context of FileReadThread.
+bool FileVideoCapturer::ReadFrame(bool first_frame, int* wait_time_ms) {
+ uint32 start_read_time_ms = talk_base::Time();
+
+ // 1. Signal the previously read frame to downstream.
+ if (!first_frame) {
+ captured_frame_.time_stamp = kNumNanoSecsPerMilliSec *
+ static_cast<int64>(start_read_time_ms);
+ captured_frame_.elapsed_time = captured_frame_.time_stamp - start_time_ns_;
+ SignalFrameCaptured(this, &captured_frame_);
+ }
+
+ // 2. Read the next frame.
+ if (talk_base::SS_CLOSED == video_file_.GetState()) {
+ LOG(LS_ERROR) << "File not opened yet";
+ return false;
+ }
+ // 2.1 Read the frame header.
+ talk_base::StreamResult result = ReadFrameHeader(&captured_frame_);
+ if (talk_base::SR_EOS == result) { // Loop back if repeat.
+ if (repeat_ != talk_base::kForever) {
+ if (repeat_ > 0) {
+ --repeat_;
+ } else {
+ return false;
+ }
+ }
+
+ if (video_file_.SetPosition(0)) {
+ result = ReadFrameHeader(&captured_frame_);
+ }
+ }
+ if (talk_base::SR_SUCCESS != result) {
+ LOG(LS_ERROR) << "Failed to read the frame header";
+ return false;
+ }
+ // 2.2 Reallocate memory for the frame data if necessary.
+ if (frame_buffer_size_ < captured_frame_.data_size) {
+ frame_buffer_size_ = captured_frame_.data_size;
+ delete[] static_cast<char*> (captured_frame_.data);
+ captured_frame_.data = new char[frame_buffer_size_];
+ }
+ // 2.3 Read the frame adata.
+ if (talk_base::SR_SUCCESS != video_file_.Read(captured_frame_.data,
+ captured_frame_.data_size,
+ NULL, NULL)) {
+ LOG(LS_ERROR) << "Failed to read frame data";
+ return false;
+ }
+
+ // 3. Decide how long to wait for the next frame.
+ *wait_time_ms = 0;
+
+ // If the capture format's interval is not kMinimumInterval, we use it to
+ // control the rate; otherwise, we use the timestamp in the file to control
+ // the rate.
+ if (!first_frame && !ignore_framerate_) {
+ int64 interval_ns =
+ GetCaptureFormat()->interval > VideoFormat::kMinimumInterval ?
+ GetCaptureFormat()->interval :
+ captured_frame_.time_stamp - last_frame_timestamp_ns_;
+ int interval_ms = static_cast<int>(interval_ns / kNumNanoSecsPerMilliSec);
+ interval_ms -= talk_base::Time() - start_read_time_ms;
+ if (interval_ms > 0) {
+ *wait_time_ms = interval_ms;
+ }
+ }
+ // Keep the original timestamp read from the file.
+ last_frame_timestamp_ns_ = captured_frame_.time_stamp;
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/filevideocapturer.h b/talk/session/phone/filevideocapturer.h
new file mode 100644
index 0000000..6423c02
--- /dev/null
+++ b/talk/session/phone/filevideocapturer.h
@@ -0,0 +1,135 @@
+// libjingle
+// Copyright 2004--2005, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file contains two classes, VideoRecorder and FileVideoCapturer.
+// VideoRecorder records the captured frames into a file. The file stores a
+// sequence of captured frames; each frame has a header defined in struct
+// CapturedFrame, followed by the frame data.
+//
+// FileVideoCapturer, a subclass of VideoCapturer, is a simulated video capturer
+// that periodically reads images from a previously recorded file.
+
+#ifndef TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/stream.h"
+#include "talk/session/phone/videocapturer.h"
+
+namespace talk_base {
+class FileStream;
+}
+
+namespace cricket {
+
+// Utility class to record the frames captured by a video capturer into a file.
+class VideoRecorder {
+ public:
+ VideoRecorder() {}
+ ~VideoRecorder() { Stop(); }
+
+ // Start the recorder by opening the specified file. Return true if the file
+ // is opened successfully. write_header should normally be true; false means
+ // write raw frame pixel data to file without any headers.
+ bool Start(const std::string& filename, bool write_header);
+ // Stop the recorder by closing the file.
+ void Stop();
+ // Record a video frame to the file. Return true if the frame is written to
+ // the file successfully. This method needs to be called after Start() and
+ // before Stop().
+ bool RecordFrame(const CapturedFrame& frame);
+
+ private:
+ talk_base::FileStream video_file_;
+ bool write_header_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoRecorder);
+};
+
+// Simulated video capturer that periodically reads frames from a file.
+class FileVideoCapturer : public VideoCapturer {
+ public:
+ FileVideoCapturer();
+ virtual ~FileVideoCapturer();
+
+ // Set how many times to repeat reading the file. Repeat forever if the
+ // parameter is talk_base::kForever(-1); no repeat if the parameter is 0 or
+ // less than -1.
+ void set_repeat(int repeat) { repeat_ = repeat; }
+
+ // If ignore_framerate is true, file is read as quickly as possible. If
+ // false, read rate is controlled by the timestamps in the video file
+ // (thus simulating camera capture). Default value set to false.
+ void set_ignore_framerate(bool ignore_framerate) {
+ ignore_framerate_ = ignore_framerate;
+ }
+
+ bool Init(const std::string& filename);
+
+ // Override virtual methods of parent class VideoCapturer.
+ virtual CaptureResult Start(const VideoFormat& capture_format);
+ virtual void Stop();
+ virtual bool IsRunning();
+
+ protected:
+ // Override virtual methods of parent class VideoCapturer.
+ virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+ // Read the frame header from the file stream, video_file_.
+ talk_base::StreamResult ReadFrameHeader(CapturedFrame* frame);
+
+ // Read a frame and determine how long to wait for the next frame. If the
+ // frame is read successfully, Set the output parameter, wait_time_ms and
+ // return true. Otherwise, do not change wait_time_ms and return false.
+ bool ReadFrame(bool first_frame, int* wait_time_ms);
+
+ // Return the CapturedFrame - useful for extracting contents after reading
+ // a frame. Should be used only while still reading a file (i.e. only while
+ // the CapturedFrame object still exists).
+ const CapturedFrame* frame() const {
+ return &captured_frame_;
+ }
+
+ private:
+ class FileReadThread; // Forward declaration, defined in .cc.
+
+ talk_base::FileStream video_file_;
+ CapturedFrame captured_frame_;
+ // The number of bytes allocated buffer for captured_frame_.data.
+ uint32 frame_buffer_size_;
+ FileReadThread* file_read_thread_;
+ int repeat_; // How many times to repeat the file.
+ int64 start_time_ns_; // Time when the file video capturer starts.
+ int64 last_frame_timestamp_ns_; // Timestamp of last read frame.
+ bool ignore_framerate_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileVideoCapturer);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FILEVIDEOCAPTURER_H_
diff --git a/talk/session/phone/filevideocapturer_unittest.cc b/talk/session/phone/filevideocapturer_unittest.cc
new file mode 100644
index 0000000..8692703
--- /dev/null
+++ b/talk/session/phone/filevideocapturer_unittest.cc
@@ -0,0 +1,175 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/filevideocapturer.h"
+#include "talk/session/phone/testutils.h"
+
+namespace {
+
+class FileVideoCapturerTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ capturer_.reset(new cricket::FileVideoCapturer);
+ }
+
+ bool OpenFile(const std::string& filename) {
+ return capturer_->Init(cricket::GetTestFilePath(filename));
+ }
+
+ protected:
+ class VideoCapturerListener : public sigslot::has_slots<> {
+ public:
+ VideoCapturerListener()
+ : frame_count_(0),
+ frame_width_(0),
+ frame_height_(0),
+ resolution_changed_(false) {
+ }
+
+ void OnFrameCaptured(cricket::VideoCapturer* capturer,
+ const cricket::CapturedFrame* frame) {
+ ++frame_count_;
+ if (1 == frame_count_) {
+ frame_width_ = frame->width;
+ frame_height_ = frame->height;
+ } else if (frame_width_ != frame->width ||
+ frame_height_ != frame->height) {
+ resolution_changed_ = true;
+ }
+ }
+
+ int frame_count() const { return frame_count_; }
+ int frame_width() const { return frame_width_; }
+ int frame_height() const { return frame_height_; }
+ bool resolution_changed() const { return resolution_changed_; }
+
+ private:
+ int frame_count_;
+ int frame_width_;
+ int frame_height_;
+ bool resolution_changed_;
+ };
+
+ talk_base::scoped_ptr<cricket::FileVideoCapturer> capturer_;
+ cricket::VideoFormat capture_format_;
+};
+
+TEST_F(FileVideoCapturerTest, TestNotOpened) {
+ EXPECT_EQ("", capturer_->GetId());
+ EXPECT_EQ(NULL, capturer_->GetSupportedFormats());
+ EXPECT_EQ(NULL, capturer_->GetCaptureFormat());
+ EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(FileVideoCapturerTest, TestInvalidOpen) {
+ EXPECT_FALSE(OpenFile("NotmeNotme"));
+}
+
+TEST_F(FileVideoCapturerTest, TestOpen) {
+ EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+ EXPECT_NE("", capturer_->GetId());
+ EXPECT_TRUE(NULL != capturer_->GetSupportedFormats());
+ EXPECT_EQ(1U, capturer_->GetSupportedFormats()->size());
+ EXPECT_EQ(NULL, capturer_->GetCaptureFormat()); // not started yet
+ EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(FileVideoCapturerTest, TestLargeSmallDesiredFormat) {
+ EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+ // desired format with large resolution.
+ cricket::VideoFormat desired(
+ 3200, 2400, cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY);
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+ EXPECT_EQ(320, capture_format_.width);
+ EXPECT_EQ(240, capture_format_.height);
+
+ // Desired format with small resolution.
+ desired.width = 0;
+ desired.height = 0;
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+ EXPECT_EQ(320, capture_format_.width);
+ EXPECT_EQ(240, capture_format_.height);
+}
+
+TEST_F(FileVideoCapturerTest, TestSupportedAsDesiredFormat) {
+ EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+ // desired format same as the capture format supported by the file
+ cricket::VideoFormat desired = capturer_->GetSupportedFormats()->at(0);
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+ EXPECT_TRUE(desired == capture_format_);
+
+ // desired format same as the supported capture format except the fourcc
+ desired.fourcc = cricket::FOURCC_ANY;
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+ EXPECT_NE(capture_format_.fourcc, desired.fourcc);
+
+ // desired format with minimum interval
+ desired.interval = cricket::VideoFormat::kMinimumInterval;
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(desired, &capture_format_));
+}
+
+TEST_F(FileVideoCapturerTest, TestNoRepeat) {
+ EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+ VideoCapturerListener listener;
+ capturer_->SignalFrameCaptured.connect(
+ &listener, &VideoCapturerListener::OnFrameCaptured);
+ capturer_->set_repeat(0);
+ capture_format_ = capturer_->GetSupportedFormats()->at(0);
+ EXPECT_EQ(cricket::CR_SUCCESS, capturer_->Start(capture_format_));
+ EXPECT_TRUE_WAIT(!capturer_->IsRunning(), 20000);
+ EXPECT_EQ(48, listener.frame_count());
+}
+
+TEST_F(FileVideoCapturerTest, TestRepeatForever) {
+ // Start the capturer_ with 50 fps and read no less than 150 frames.
+ EXPECT_TRUE(OpenFile("captured-320x240-2s-48.frames"));
+ VideoCapturerListener listener;
+ capturer_->SignalFrameCaptured.connect(
+ &listener, &VideoCapturerListener::OnFrameCaptured);
+ capturer_->set_repeat(talk_base::kForever);
+ capture_format_ = capturer_->GetSupportedFormats()->at(0);
+ capture_format_.interval = cricket::VideoFormat::FpsToInterval(50);
+ EXPECT_EQ(cricket::CR_SUCCESS, capturer_->Start(capture_format_));
+ EXPECT_TRUE(NULL != capturer_->GetCaptureFormat());
+ EXPECT_TRUE(capture_format_ == *capturer_->GetCaptureFormat());
+ EXPECT_TRUE_WAIT(!capturer_->IsRunning() ||
+ listener.frame_count() >= 150, 20000);
+ capturer_->Stop();
+ EXPECT_FALSE(capturer_->IsRunning());
+ EXPECT_GE(listener.frame_count(), 150);
+ EXPECT_FALSE(listener.resolution_changed());
+ EXPECT_EQ(listener.frame_width(), capture_format_.width);
+ EXPECT_EQ(listener.frame_height(), capture_format_.height);
+}
+
+} // unnamed namespace
diff --git a/talk/session/phone/linuxdevicemanager.cc b/talk/session/phone/linuxdevicemanager.cc
new file mode 100644
index 0000000..6bad677
--- /dev/null
+++ b/talk/session/phone/linuxdevicemanager.cc
@@ -0,0 +1,410 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/linuxdevicemanager.h"
+
+#include <libudev.h>
+#include <unistd.h>
+#include "talk/base/linux.h"
+#include "talk/base/logging.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/libudevsymboltable.h"
+#include "talk/session/phone/mediacommon.h"
+#include "talk/session/phone/v4llookup.h"
+#include "talk/sound/platformsoundsystem.h"
+#include "talk/sound/platformsoundsystemfactory.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+ return new LinuxDeviceManager();
+}
+
+class LinuxDeviceWatcher
+ : public DeviceWatcher,
+ private talk_base::Dispatcher {
+ public:
+ explicit LinuxDeviceWatcher(DeviceManagerInterface* dm);
+ virtual ~LinuxDeviceWatcher();
+ virtual bool Start();
+ virtual void Stop();
+
+ private:
+ virtual uint32 GetRequestedEvents();
+ virtual void OnPreEvent(uint32 ff);
+ virtual void OnEvent(uint32 ff, int err);
+ virtual int GetDescriptor();
+ virtual bool IsDescriptorClosed();
+
+ DeviceManagerInterface* manager_;
+ LibUDevSymbolTable libudev_;
+ struct udev* udev_;
+ struct udev_monitor* udev_monitor_;
+ bool registered_;
+};
+
+#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
+
+static const char* const kFilteredAudioDevicesName[] = {
+#if defined(CHROMEOS)
+ "surround40:",
+ "surround41:",
+ "surround50:",
+ "surround51:",
+ "surround71:",
+ "iec958:", // S/PDIF
+#endif
+ NULL,
+};
+static const char* kFilteredVideoDevicesName[] = {
+ NULL,
+};
+
+LinuxDeviceManager::LinuxDeviceManager()
+ : sound_system_(new PlatformSoundSystemFactory()) {
+ set_watcher(new LinuxDeviceWatcher(this));
+}
+
+LinuxDeviceManager::~LinuxDeviceManager() {
+}
+
+bool LinuxDeviceManager::GetAudioDevices(bool input,
+ std::vector<Device>* devs) {
+ devs->clear();
+ if (!sound_system_.get()) {
+ return false;
+ }
+ SoundSystemInterface::SoundDeviceLocatorList list;
+ bool success;
+ if (input) {
+ success = sound_system_->EnumerateCaptureDevices(&list);
+ } else {
+ success = sound_system_->EnumeratePlaybackDevices(&list);
+ }
+ if (!success) {
+ LOG(LS_ERROR) << "Can't enumerate devices";
+ sound_system_.release();
+ return false;
+ }
+ // We have to start the index at 1 because GIPS VoiceEngine puts the default
+ // device at index 0, but Enumerate(Capture|Playback)Devices does not include
+ // a locator for the default device.
+ int index = 1;
+ for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
+ i != list.end();
+ ++i, ++index) {
+ devs->push_back(Device((*i)->name(), index));
+ }
+ SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
+ sound_system_.release();
+ return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
+static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
+
+enum MetaType { M2_4, M2_6, NONE };
+
+static void ScanDeviceDirectory(const std::string& devdir,
+ std::vector<Device>* devices) {
+ talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+ talk_base::Filesystem::IterateDirectory());
+
+ if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
+ do {
+ std::string filename = directoryIterator->Name();
+ std::string device_name = devdir + filename;
+ if (!directoryIterator->IsDots()) {
+ if (filename.find("video") == 0 &&
+ V4LLookup::IsV4L2Device(device_name)) {
+ devices->push_back(Device(device_name, device_name));
+ }
+ }
+ } while (directoryIterator->Next());
+ }
+}
+
+static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
+ std::string device_name;
+
+ talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
+ talk_base::Filesystem::OpenFile(device_meta_path, "r"));
+
+ if (device_meta_stream.get() != NULL) {
+ if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
+ LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
+ }
+ device_meta_stream->Close();
+ }
+
+ return device_name;
+}
+
+static std::string Trim(const std::string& s, const std::string& drop = " \t") {
+ std::string::size_type first = s.find_first_not_of(drop);
+ std::string::size_type last = s.find_last_not_of(drop);
+
+ if (first == std::string::npos || last == std::string::npos)
+ return std::string("");
+
+ return s.substr(first, last - first + 1);
+}
+
+static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
+ talk_base::ConfigParser::MapVector all_values;
+
+ talk_base::ConfigParser config_parser;
+ talk_base::FileStream* file_stream =
+ talk_base::Filesystem::OpenFile(device_meta_path, "r");
+
+ if (file_stream == NULL) return "";
+
+ config_parser.Attach(file_stream);
+ config_parser.Parse(&all_values);
+
+ for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
+ i != all_values.end(); ++i) {
+ talk_base::ConfigParser::SimpleMap::iterator device_name_i =
+ i->find("name");
+
+ if (device_name_i != i->end()) {
+ return device_name_i->second;
+ }
+ }
+
+ return "";
+}
+
+static std::string GetVideoDeviceName(MetaType meta,
+ const std::string& device_file_name) {
+ std::string device_meta_path;
+ std::string device_name;
+ std::string meta_file_path;
+
+ if (meta == M2_6) {
+ meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
+
+ LOG(LS_INFO) << "Trying " + meta_file_path;
+ device_name = GetVideoDeviceNameK2_6(meta_file_path);
+
+ if (device_name.empty()) {
+ meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
+
+ LOG(LS_INFO) << "Trying " << meta_file_path;
+ device_name = GetVideoDeviceNameK2_6(meta_file_path);
+ }
+ } else {
+ meta_file_path = kVideoMetaPathK2_4 + device_file_name;
+ LOG(LS_INFO) << "Trying " << meta_file_path;
+ device_name = GetVideoDeviceNameK2_4(meta_file_path);
+ }
+
+ if (device_name.empty()) {
+ device_name = "/dev/" + device_file_name;
+ LOG(LS_ERROR)
+ << "Device name not found, defaulting to device path " << device_name;
+ }
+
+ LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
+
+ return Trim(device_name);
+}
+
+static void ScanV4L2Devices(std::vector<Device>* devices) {
+ LOG(LS_INFO) << ("Enumerating V4L2 devices");
+
+ MetaType meta;
+ std::string metadata_dir;
+
+ talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+ talk_base::Filesystem::IterateDirectory());
+
+ // Try and guess kernel version
+ if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
+ meta = M2_6;
+ metadata_dir = kVideoMetaPathK2_6;
+ } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
+ meta = M2_4;
+ metadata_dir = kVideoMetaPathK2_4;
+ } else {
+ meta = NONE;
+ }
+
+ if (meta != NONE) {
+ LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
+
+ do {
+ std::string filename = directoryIterator->Name();
+
+ if (filename.find("video") == 0) {
+ std::string device_path = "/dev/" + filename;
+
+ if (V4LLookup::IsV4L2Device(device_path)) {
+ devices->push_back(
+ Device(GetVideoDeviceName(meta, filename), device_path));
+ }
+ }
+ } while (directoryIterator->Next());
+ } else {
+ LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
+ }
+
+ if (devices->size() == 0) {
+ LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
+ ScanDeviceDirectory("/dev/", devices);
+ }
+
+ LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
+}
+
+bool LinuxDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+ devices->clear();
+ ScanV4L2Devices(devices);
+ return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+LinuxDeviceWatcher::LinuxDeviceWatcher(DeviceManagerInterface* dm)
+ : DeviceWatcher(dm),
+ manager_(dm),
+ udev_(NULL),
+ udev_monitor_(NULL),
+ registered_(false) {
+}
+
+LinuxDeviceWatcher::~LinuxDeviceWatcher() {
+}
+
+bool LinuxDeviceWatcher::Start() {
+ // We deliberately return true in the failure paths here because libudev is
+ // not a critical component of a Linux system so it may not be present/usable,
+ // and we don't want to halt LinuxDeviceManager initialization in such a case.
+ if (!libudev_.Load()) {
+ LOG(LS_WARNING) << "libudev not present/usable; LinuxDeviceWatcher disabled";
+ return true;
+ }
+ udev_ = LATE(udev_new)();
+ if (!udev_) {
+ LOG_ERR(LS_ERROR) << "udev_new()";
+ return true;
+ }
+ // The second argument here is the event source. It can be either "kernel" or
+ // "udev", but "udev" is the only correct choice. Apps listen on udev and the
+ // udev daemon in turn listens on the kernel.
+ udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
+ if (!udev_monitor_) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
+ return true;
+ }
+ // We only listen for changes in the video devices. Audio devices are more or
+ // less unimportant because receiving device change notifications really only
+ // matters for broadcasting updated send/recv capabilities based on whether
+ // there is at least one device available, and almost all computers have at
+ // least one audio device. Also, PulseAudio device notifications don't come
+ // from the udev daemon, they come from the PulseAudio daemon, so we'd only
+ // want to listen for audio device changes from udev if using ALSA. For
+ // simplicity, we don't bother with any audio stuff at all.
+ if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
+ "video4linux",
+ NULL) < 0) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
+ return true;
+ }
+ if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
+ return true;
+ }
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Add(this);
+ registered_ = true;
+ return true;
+}
+
+void LinuxDeviceWatcher::Stop() {
+ if (registered_) {
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Remove(this);
+ registered_ = false;
+ }
+ if (udev_monitor_) {
+ LATE(udev_monitor_unref)(udev_monitor_);
+ udev_monitor_ = NULL;
+ }
+ if (udev_) {
+ LATE(udev_unref)(udev_);
+ udev_ = NULL;
+ }
+ libudev_.Unload();
+}
+
+uint32 LinuxDeviceWatcher::GetRequestedEvents() {
+ return talk_base::DE_READ;
+}
+
+void LinuxDeviceWatcher::OnPreEvent(uint32 ff) {
+ // Nothing to do.
+}
+
+void LinuxDeviceWatcher::OnEvent(uint32 ff, int err) {
+ udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
+ if (!device) {
+ // Probably the socket connection to the udev daemon was terminated (perhaps
+ // the daemon crashed or is being restarted?).
+ LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
+ // Stop listening to avoid potential livelock (an fd with EOF in it is
+ // always considered readable).
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Remove(this);
+ registered_ = false;
+ return;
+ }
+ // Else we read the device successfully.
+
+ // Since we already have our own filesystem-based device enumeration code, we
+ // simply re-enumerate rather than inspecting the device event.
+ LATE(udev_device_unref)(device);
+ manager_->SignalDevicesChange();
+}
+
+int LinuxDeviceWatcher::GetDescriptor() {
+ return LATE(udev_monitor_get_fd)(udev_monitor_);
+}
+
+bool LinuxDeviceWatcher::IsDescriptorClosed() {
+ // If it is closed then we will just get an error in
+ // udev_monitor_receive_device and unregister, so we don't need to check for
+ // it separately.
+ return false;
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/linuxdevicemanager.h b/talk/session/phone/linuxdevicemanager.h
new file mode 100644
index 0000000..dce3286
--- /dev/null
+++ b/talk/session/phone/linuxdevicemanager.h
@@ -0,0 +1,55 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/sound/soundsystemfactory.h"
+
+namespace cricket {
+
+class LinuxDeviceManager : public DeviceManager {
+ public:
+ LinuxDeviceManager();
+ virtual ~LinuxDeviceManager();
+
+ virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+ virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+ SoundSystemHandle sound_system_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_LINUXDEVICEMANAGER_H_
diff --git a/talk/session/phone/macdevicemanager.cc b/talk/session/phone/macdevicemanager.cc
new file mode 100644
index 0000000..bad5ea9
--- /dev/null
+++ b/talk/session/phone/macdevicemanager.cc
@@ -0,0 +1,198 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/macdevicemanager.h"
+
+#include <CoreAudio/CoreAudio.h>
+#include <QuickTime/QuickTime.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediacommon.h"
+
+class DeviceWatcherImpl;
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+ return new MacDeviceManager();
+}
+
+class MacDeviceWatcher : public DeviceWatcher {
+ public:
+ explicit MacDeviceWatcher(DeviceManagerInterface* dm);
+ virtual ~MacDeviceWatcher();
+ virtual bool Start();
+ virtual void Stop();
+
+ private:
+ DeviceManagerInterface* manager_;
+ DeviceWatcherImpl* impl_;
+};
+
+static const char* kFilteredAudioDevicesName[] = {
+ NULL,
+};
+// TODO: Try to get hold of a copy of Final Cut to understand why we
+// crash while scanning their components on OS X.
+static const char* const kFilteredVideoDevicesName[] = {
+ "Google Camera Adapter", // Our own magiccams
+ "DVCPRO HD", // Final cut
+ "Sonix SN9C201p", // Crashes in OpenAComponent and CloseComponent
+ NULL,
+};
+static const int kVideoDeviceOpenAttempts = 3;
+static const UInt32 kAudioDeviceNameLength = 64;
+// Obj-C functions defined in macdevicemanagermm.mm
+// TODO: have a shared header for these function defines.
+extern DeviceWatcherImpl* CreateDeviceWatcherCallback(
+ DeviceManagerInterface* dm);
+extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl);
+extern bool GetQTKitVideoDevices(std::vector<Device>* out);
+static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
+static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
+
+MacDeviceManager::MacDeviceManager() {
+ set_watcher(new MacDeviceWatcher(this));
+}
+
+MacDeviceManager::~MacDeviceManager() {
+}
+
+bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+ devices->clear();
+ if (!GetQTKitVideoDevices(devices)) {
+ return false;
+ }
+ return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+bool MacDeviceManager::GetAudioDevices(bool input,
+ std::vector<Device>* devs) {
+ devs->clear();
+ std::vector<AudioDeviceID> dev_ids;
+ bool ret = GetAudioDeviceIDs(input, &dev_ids);
+ if (!ret) {
+ return false;
+ }
+ for (size_t i = 0; i < dev_ids.size(); ++i) {
+ std::string name;
+ if (GetAudioDeviceName(dev_ids[i], input, &name)) {
+ devs->push_back(Device(name, dev_ids[i]));
+ }
+ }
+ return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+static bool GetAudioDeviceIDs(bool input,
+ std::vector<AudioDeviceID>* out_dev_ids) {
+ UInt32 propsize;
+ OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &propsize, NULL);
+ if (0 != err) {
+ LOG(LS_ERROR) << "Couldn't get information about property, "
+ << "so no device list acquired.";
+ return false;
+ }
+
+ size_t num_devices = propsize / sizeof(AudioDeviceID);
+ talk_base::scoped_array<AudioDeviceID> device_ids(
+ new AudioDeviceID[num_devices]);
+
+ err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &propsize, device_ids.get());
+ if (0 != err) {
+ LOG(LS_ERROR) << "Failed to get device ids, "
+ << "so no device listing acquired.";
+ return false;
+ }
+
+ for (size_t i = 0; i < num_devices; ++i) {
+ AudioDeviceID an_id = device_ids[i];
+ // find out the number of channels for this direction
+ // (input/output) on this device -
+ // we'll ignore anything with no channels.
+ err = AudioDeviceGetPropertyInfo(an_id, 0, input,
+ kAudioDevicePropertyStreams,
+ &propsize, NULL);
+ if (0 == err) {
+ unsigned num_channels = propsize / sizeof(AudioStreamID);
+ if (0 < num_channels) {
+ out_dev_ids->push_back(an_id);
+ }
+ } else {
+ LOG(LS_ERROR) << "No property info for stream property for device id "
+ << an_id << "(is_input == " << input
+ << "), so not including it in the list.";
+ }
+ }
+
+ return true;
+}
+
+static bool GetAudioDeviceName(AudioDeviceID id,
+ bool input,
+ std::string* out_name) {
+ UInt32 nameLength = kAudioDeviceNameLength;
+ char name[kAudioDeviceNameLength + 1];
+ OSErr err = AudioDeviceGetProperty(id, 0, input,
+ kAudioDevicePropertyDeviceName,
+ &nameLength, name);
+ if (0 != err) {
+ LOG(LS_ERROR) << "No name acquired for device id " << id;
+ return false;
+ }
+
+ *out_name = name;
+ return true;
+}
+
+MacDeviceWatcher::MacDeviceWatcher(DeviceManagerInterface* manager)
+ : DeviceWatcher(manager),
+ manager_(manager),
+ impl_(NULL) {
+}
+
+MacDeviceWatcher::~MacDeviceWatcher() {
+}
+
+bool MacDeviceWatcher::Start() {
+ if (!impl_) {
+ impl_ = CreateDeviceWatcherCallback(manager_);
+ }
+ return impl_ != NULL;
+}
+
+void MacDeviceWatcher::Stop() {
+ if (impl_) {
+ ReleaseDeviceWatcherCallback(impl_);
+ impl_ = NULL;
+ }
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/macdevicemanager.h b/talk/session/phone/macdevicemanager.h
new file mode 100644
index 0000000..ec4cfc0
--- /dev/null
+++ b/talk/session/phone/macdevicemanager.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class DeviceWatcher;
+
+class MacDeviceManager : public DeviceManager {
+ public:
+ MacDeviceManager();
+ virtual ~MacDeviceManager();
+
+ virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+ virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+ bool FilterDevice(const Device& d);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MACDEVICEMANAGER_H_
diff --git a/talk/session/phone/macdevicemanagermm.mm b/talk/session/phone/macdevicemanagermm.mm
new file mode 100644
index 0000000..db970c2
--- /dev/null
+++ b/talk/session/phone/macdevicemanagermm.mm
@@ -0,0 +1,116 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/devicemanager.h"
+
+#import <assert.h>
+#import <QTKit/QTKit.h>
+
+#include "talk/base/logging.h"
+
+@interface DeviceWatcherImpl : NSObject {
+ @private
+ cricket::DeviceManagerInterface* manager_;
+}
+- (id)init:(cricket::DeviceManagerInterface*)manager;
+- (void)onDevicesChanged:(NSNotification *)notification;
+@end
+
+@implementation DeviceWatcherImpl
+- (id)init:(cricket::DeviceManagerInterface*)manager {
+ if ((self = [super init])) {
+ assert(manager != NULL);
+ manager_ = manager;
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onDevicesChanged:)
+ name:QTCaptureDeviceWasConnectedNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onDevicesChanged:)
+ name:QTCaptureDeviceWasDisconnectedNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+- (void)onDevicesChanged:(NSNotification *)notification {
+ manager_->SignalDevicesChange();
+}
+@end
+
+namespace cricket {
+
+DeviceWatcherImpl* CreateDeviceWatcherCallback(
+ DeviceManagerInterface* manager) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ DeviceWatcherImpl* impl = [[DeviceWatcherImpl alloc] init:manager];
+ [pool drain];
+ return impl;
+}
+
+void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* watcher) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ [watcher release];
+ [pool drain];
+}
+
+bool GetQTKitVideoDevices(std::vector<Device>* devices) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ NSArray* qt_capture_devices =
+ [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
+ NSUInteger count = [qt_capture_devices count];
+ LOG(LS_INFO) << count << " capture device(s) found:";
+ for (QTCaptureDevice* qt_capture_device in qt_capture_devices) {
+ static NSString* const kFormat = @"localizedDisplayName: \"%@\", "
+ @"modelUniqueID: \"%@\", uniqueID \"%@\", isConnected: %d, isOpen: %d, "
+ @"isInUseByAnotherApplication: %d";
+ NSString* info = [NSString stringWithFormat:kFormat,
+ [qt_capture_device localizedDisplayName],
+ [qt_capture_device modelUniqueID],
+ [qt_capture_device uniqueID],
+ [qt_capture_device isConnected],
+ [qt_capture_device isOpen],
+ [qt_capture_device isInUseByAnotherApplication]];
+ LOG(LS_INFO) << [info UTF8String];
+
+ std::string name([[qt_capture_device localizedDisplayName]
+ UTF8String]);
+ devices->push_back(Device(name,
+ [[qt_capture_device uniqueID]
+ UTF8String]));
+ }
+
+ [pool drain];
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/mediachannel.h b/talk/session/phone/mediachannel.h
index aa32671..cf86ce6 100644
--- a/talk/session/phone/mediachannel.h
+++ b/talk/session/phone/mediachannel.h
@@ -34,6 +34,7 @@
#include "talk/base/basictypes.h"
#include "talk/base/sigslot.h"
#include "talk/base/socket.h"
+#include "talk/base/window.h"
#include "talk/session/phone/codec.h"
// TODO: re-evaluate this include
#include "talk/session/phone/audiomonitor.h"
@@ -44,6 +45,9 @@
namespace cricket {
+class ScreencastId;
+struct StreamParams;
+struct VideoFormat;
class VideoRenderer;
const int kMinRtpHeaderExtensionId = 1;
@@ -72,13 +76,18 @@
// TODO: SendRecv direction;
};
-enum VoiceMediaChannelOptions {
- OPT_CONFERENCE = 0x10000, // tune the audio stream for conference mode
+enum MediaChannelOptions {
+ // Tune the stream for conference mode.
+ OPT_CONFERENCE = 0x0001
+};
+enum VoiceMediaChannelOptions {
+ // Tune the audio stream for vcs with different target levels.
+ OPT_AGC_MINUS_10DB = 0x80000000
};
enum VideoMediaChannelOptions {
- // Increase the output framerate by 2x by interpolating frames
+ // Increase the output framerate by 2x by interpolating frames.
OPT_INTERPOLATE = 0x10000,
// Enable video adaptation due to cpu load.
OPT_CPU_ADAPTATION = 0x20000
@@ -109,10 +118,21 @@
virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
// Called when a RTCP packet is received.
virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
- // Sets the SSRC to be used for outgoing data.
- virtual void SetSendSsrc(uint32 id) = 0;
- // Set the CNAME of RTCP
- virtual bool SetRtcpCName(const std::string& cname) = 0;
+ // Creates a new outgoing media stream with SSRCs and CNAME as described
+ // by sp.
+ virtual bool AddSendStream(const StreamParams& sp) = 0;
+ // Removes an outgoing media stream.
+ // ssrc must be the first SSRC of the media stream if the stream uses
+ // multiple SSRCs.
+ virtual bool RemoveSendStream(uint32 ssrc) = 0;
+ // Creates a new incoming media stream with SSRCs and CNAME as described
+ // by sp.
+ virtual bool AddRecvStream(const StreamParams& sp) = 0;
+ // Removes an incoming media stream.
+ // ssrc must be the first SSRC of the media stream if the stream uses
+ // multiple SSRCs.
+ virtual bool RemoveRecvStream(uint32 ssrc) = 0;
+
// Mutes the channel.
virtual bool Mute(bool on) = 0;
@@ -138,7 +158,24 @@
};
struct VoiceSenderInfo {
+ VoiceSenderInfo()
+ : ssrc(0),
+ bytes_sent(0),
+ packets_sent(0),
+ packets_lost(0),
+ fraction_lost(0.0),
+ ext_seqnum(0),
+ rtt_ms(0),
+ jitter_ms(0),
+ audio_level(0),
+ echo_delay_median_ms(0),
+ echo_delay_std_ms(0),
+ echo_return_loss(0),
+ echo_return_loss_enhancement(0) {
+ }
+
uint32 ssrc;
+ std::string codec_name;
int bytes_sent;
int packets_sent;
int packets_lost;
@@ -147,9 +184,27 @@
int rtt_ms;
int jitter_ms;
int audio_level;
+ int echo_delay_median_ms;
+ int echo_delay_std_ms;
+ int echo_return_loss;
+ int echo_return_loss_enhancement;
};
struct VoiceReceiverInfo {
+ VoiceReceiverInfo()
+ : ssrc(0),
+ bytes_rcvd(0),
+ packets_rcvd(0),
+ packets_lost(0),
+ fraction_lost(0.0),
+ ext_seqnum(0),
+ jitter_ms(0),
+ jitter_buffer_ms(0),
+ jitter_buffer_preferred_ms(0),
+ delay_estimate_ms(0),
+ audio_level(0) {
+ }
+
uint32 ssrc;
int bytes_rcvd;
int packets_rcvd;
@@ -164,7 +219,26 @@
};
struct VideoSenderInfo {
+ VideoSenderInfo()
+ : ssrc(0),
+ bytes_sent(0),
+ packets_sent(0),
+ packets_cached(0),
+ packets_lost(0),
+ fraction_lost(0.0),
+ firs_rcvd(0),
+ nacks_rcvd(0),
+ rtt_ms(0),
+ frame_width(0),
+ frame_height(0),
+ framerate_input(0),
+ framerate_sent(0),
+ nominal_bitrate(0),
+ preferred_bitrate(0) {
+ }
+
uint32 ssrc;
+ std::string codec_name;
int bytes_sent;
int packets_sent;
int packets_cached;
@@ -182,6 +256,22 @@
};
struct VideoReceiverInfo {
+ VideoReceiverInfo()
+ : ssrc(0),
+ bytes_rcvd(0),
+ packets_rcvd(0),
+ packets_lost(0),
+ packets_concealed(0),
+ fraction_lost(0.0),
+ firs_sent(0),
+ nacks_sent(0),
+ frame_width(0),
+ frame_height(0),
+ framerate_rcvd(0),
+ framerate_decoded(0),
+ framerate_output(0) {
+ }
+
uint32 ssrc;
int bytes_rcvd;
// vector<int> layer_bytes_rcvd;
@@ -199,6 +289,16 @@
};
struct BandwidthEstimationInfo {
+ BandwidthEstimationInfo()
+ : available_send_bandwidth(0),
+ available_recv_bandwidth(0),
+ target_enc_bitrate(0),
+ actual_enc_bitrate(0),
+ retransmit_bitrate(0),
+ transmit_bitrate(0),
+ bucket_delay(0) {
+ }
+
int available_send_bandwidth;
int available_recv_bandwidth;
int target_enc_bitrate;
@@ -261,10 +361,6 @@
virtual bool SetPlayout(bool playout) = 0;
// Starts or stops sending (and potentially capture) of local audio.
virtual bool SetSend(SendFlags flag) = 0;
- // Adds a new receive-only stream with the specified SSRC.
- virtual bool AddStream(uint32 ssrc) = 0;
- // Removes a stream added with AddStream.
- virtual bool RemoveStream(uint32 ssrc) = 0;
// Gets current energy levels for all incoming streams.
virtual bool GetActiveStreams(AudioInfo::StreamList* actives) = 0;
// Get the current energy level of the stream sent to the speaker.
@@ -315,17 +411,17 @@
virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs) = 0;
// Sets the codecs/payload types to be used for outgoing media.
virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs) = 0;
+ // Sets the format of a specified outgoing stream.
+ virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format) = 0;
// Starts or stops playout of received video.
virtual bool SetRender(bool render) = 0;
// Starts or stops transmission (and potentially capture) of local video.
virtual bool SetSend(bool send) = 0;
- // Adds a new receive-only stream with the specified SSRC.
- virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) = 0;
- // Removes a stream added with AddStream.
- virtual bool RemoveStream(uint32 ssrc) = 0;
// Sets the renderer object to be used for the specified stream.
// If SSRC is 0, the renderer is used for the 'default' stream.
virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) = 0;
+ virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) = 0;
+ virtual bool RemoveScreencast(uint32 ssrc) = 0;
// Gets quality stats for the channel.
virtual bool GetStats(VideoMediaInfo* info) = 0;
@@ -334,6 +430,8 @@
// Reuqest each of the remote senders to send an intra frame.
virtual bool RequestIntraFrame() = 0;
+ // Signals events from the currently active window.
+ sigslot::signal2<uint32, talk_base::WindowEvent> SignalScreencastWindowEvent;
sigslot::signal2<uint32, Error> SignalMediaError;
protected:
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
index 992fc15..05c7274 100644
--- a/talk/session/phone/mediaengine.cc
+++ b/talk/session/phone/mediaengine.cc
@@ -29,6 +29,8 @@
#if defined(HAVE_LINPHONE)
#include "talk/session/phone/linphonemediaengine.h"
+#elif defined(ANDROID)
+#include "talk/session/phone/androidmediaengine.h"
#else
#if defined(HAVE_WEBRTC_VOICE)
#include "talk/session/phone/webrtcvoiceengine.h"
@@ -47,6 +49,11 @@
#endif
#if defined(HAVE_WEBRTC_VIDEO)
+template<>
+CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>::
+ CompositeMediaEngine() {
+ video_.SetVoiceEngine(&voice_);
+}
#define VIDEO_ENG_NAME WebRtcVideoEngine
#endif
@@ -58,6 +65,8 @@
MediaEngineInterface* MediaEngineFactory::Create() {
#if defined(HAVE_LINPHONE)
return new LinphoneMediaEngine("", "");
+#elif defined(ANDROID)
+ return AndroidMediaEngineFactory::Create();
#elif defined(AUDIO_ENG_NAME) && defined(VIDEO_ENG_NAME)
return new CompositeMediaEngine<AUDIO_ENG_NAME, VIDEO_ENG_NAME>();
#else
diff --git a/talk/session/phone/mediaengine.h b/talk/session/phone/mediaengine.h
index 9ea808d..cc77099 100644
--- a/talk/session/phone/mediaengine.h
+++ b/talk/session/phone/mediaengine.h
@@ -32,6 +32,7 @@
#include <CoreAudio/CoreAudio.h>
#endif
+#include <climits>
#include <string>
#include <vector>
@@ -40,10 +41,19 @@
#include "talk/session/phone/devicemanager.h"
#include "talk/session/phone/mediachannel.h"
#include "talk/session/phone/mediacommon.h"
+#include "talk/session/phone/videoprocessor.h"
#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/voiceprocessor.h"
namespace cricket {
+// TODO: For now, a hard-coded ssrc is used as the video ssrc.
+// This is because when the video frame is passed to the mediaprocessor for
+// processing, it doesn't have the correct ssrc. Since currently only Tx
+// Video processing is supported, this is ok. When we switch over to trigger
+// from capturer, this should be fixed and this const removed.
+const uint32 kDummyVideoSsrc = 0xFFFFFFFF;
+
class VideoCapturer;
// MediaEngineInterface is an abstraction of a media engine which can be
@@ -58,7 +68,6 @@
ECHO_CANCELLATION = 1 << 0,
AUTO_GAIN_CONTROL = 1 << 1,
NOISE_SUPPRESSION = 1 << 2,
- TYPING_DETECTION = 1 << 3,
DEFAULT_AUDIO_OPTIONS = ECHO_CANCELLATION | AUTO_GAIN_CONTROL
};
enum VideoOptions {
@@ -100,6 +109,9 @@
virtual bool SetSoundDevices(const Device* in_device,
const Device* out_device) = 0;
virtual bool SetVideoCaptureDevice(const Device* cam_device) = 0;
+ // Sets the externally provided video capturer. The ssrc is the ssrc of the
+ // (video) stream for which the video capturer should be set.
+ virtual bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) = 0;
// Device configuration
// Gets the current speaker volume, as a value between 0 and 255.
@@ -126,6 +138,16 @@
virtual void SetVoiceLogging(int min_sev, const char* filter) = 0;
virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
+ // media processors for effects
+ virtual bool RegisterVideoProcessor(VideoProcessor* video_processor) = 0;
+ virtual bool UnregisterVideoProcessor(VideoProcessor* video_processor) = 0;
+ virtual bool RegisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* video_processor,
+ MediaProcessorDirection direction) = 0;
+ virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* video_processor,
+ MediaProcessorDirection direction) = 0;
+
sigslot::repeater2<VideoCapturer*, CaptureResult>
SignalVideoCaptureResult;
};
@@ -136,7 +158,6 @@
static MediaEngineInterface* Create();
};
-
// CompositeMediaEngine constructs a MediaEngine from separate
// voice and video engine classes.
template<class VOICE, class VIDEO>
@@ -189,6 +210,9 @@
virtual bool SetVideoCaptureDevice(const Device* cam_device) {
return video_.SetCaptureDevice(cam_device);
}
+ virtual bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) {
+ return video_.SetVideoCapturer(capturer, ssrc);
+ }
virtual bool GetOutputVolume(int* level) {
return voice_.GetOutputVolume(level);
@@ -224,6 +248,23 @@
return video_.SetLogging(min_sev, filter);
}
+ virtual bool RegisterVideoProcessor(VideoProcessor* processor) {
+ return video_.RegisterProcessor(processor);
+ }
+ virtual bool UnregisterVideoProcessor(VideoProcessor* processor) {
+ return video_.UnregisterProcessor(processor);
+ }
+ virtual bool RegisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return voice_.RegisterProcessor(ssrc, processor, direction);
+ }
+ virtual bool UnregisterVoiceProcessor(uint32 ssrc,
+ VoiceProcessor* processor,
+ MediaProcessorDirection direction) {
+ return voice_.UnregisterProcessor(ssrc, processor, direction);
+ }
+
protected:
VOICE voice_;
VIDEO video_;
@@ -256,6 +297,12 @@
bool SetLocalMonitor(bool enable) { return true; }
const std::vector<AudioCodec>& codecs() { return codecs_; }
void SetLogging(int min_sev, const char* filter) {}
+ bool RegisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) { return true; }
+ bool UnregisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) { return true; }
private:
std::vector<AudioCodec> codecs_;
@@ -282,6 +329,10 @@
CaptureResult SetCapture(bool capture) { return CR_SUCCESS; }
const std::vector<VideoCodec>& codecs() { return codecs_; }
void SetLogging(int min_sev, const char* filter) {}
+ bool RegisterProcessor(VideoProcessor* video_processor) { return true; }
+ bool UnregisterProcessor(VideoProcessor* video_processor) { return true; }
+ bool SetVideoCapturer(VideoCapturer* capturer, uint32 ssrc) { return true; }
+
sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
private:
std::vector<VideoCodec> codecs_;
diff --git a/talk/session/phone/mediamessages.cc b/talk/session/phone/mediamessages.cc
index 9031351..3dd6982 100644
--- a/talk/session/phone/mediamessages.cc
+++ b/talk/session/phone/mediamessages.cc
@@ -31,55 +31,22 @@
#include "talk/session/phone/mediamessages.h"
+#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/streamparams.h"
#include "talk/xmllite/xmlelement.h"
namespace cricket {
namespace {
-bool GetFirstSourceByNick(const NamedSources& sources,
- const std::string& nick,
- NamedSource* source_out) {
- for (NamedSources::const_iterator source = sources.begin();
- source != sources.end(); ++source) {
- if (source->nick == nick) {
- *source_out = *source;
- return true;
- }
- }
- return false;
-}
-
-bool GetSourceBySsrc(const NamedSources& sources, uint32 ssrc,
- NamedSource* source_out) {
- for (NamedSources::const_iterator source = sources.begin();
- source != sources.end(); ++source) {
- if (source->ssrc == ssrc) {
- *source_out = *source;
- return true;
- }
- }
- return false;
-}
-
-// NOTE: There is no check here for duplicate sources, so check before
+// NOTE: There is no check here for duplicate streams, so check before
// adding.
-void AddSource(NamedSources* sources, const NamedSource& source) {
- sources->push_back(source);
-}
-
-void RemoveSourceBySsrc(uint32 ssrc, NamedSources* sources) {
- for (NamedSources::iterator source = sources->begin();
- source != sources->end(); ) {
- if (source->ssrc == ssrc) {
- source = sources->erase(source);
- } else {
- ++source;
- }
- }
+void AddStream(std::vector<StreamParams>* streams, const StreamParams& stream) {
+ streams->push_back(stream);
}
bool ParseSsrc(const std::string& string, uint32* ssrc) {
@@ -93,41 +60,14 @@
return ParseSsrc(element->BodyText(), ssrc);
}
-bool ParseNamedSource(const buzz::XmlElement* source_elem,
- NamedSource* named_source,
- ParseError* error) {
- named_source->nick = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_NICK);
- if (named_source->nick.empty()) {
- return BadParse("Missing or invalid nick.", error);
- }
-
- named_source->name = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_NAME);
- named_source->usage = source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_USAGE);
- named_source->removed =
- STR_JINGLE_DRAFT_SOURCE_STATE_REMOVED ==
- source_elem->Attr(QN_JINGLE_DRAFT_SOURCE_STATE);
-
- const buzz::XmlElement* ssrc_elem =
- source_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE_SSRC);
- if (ssrc_elem != NULL && !ssrc_elem->BodyText().empty()) {
- uint32 ssrc;
- if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
- return BadParse("Missing or invalid ssrc.", error);
- }
- named_source->SetSsrc(ssrc);
- }
-
- return true;
-}
-
// Builds a <view> element according to the following spec:
// goto/jinglemuc
buzz::XmlElement* CreateViewElem(const std::string& name,
const std::string& type) {
buzz::XmlElement* view_elem =
new buzz::XmlElement(QN_JINGLE_DRAFT_VIEW, true);
- view_elem->AddAttr(QN_JINGLE_DRAFT_CONTENT_NAME, name);
- view_elem->SetAttr(QN_JINGLE_DRAFT_VIEW_TYPE, type);
+ view_elem->AddAttr(QN_NAME, name);
+ view_elem->SetAttr(QN_TYPE, type);
return view_elem;
}
@@ -144,16 +84,13 @@
const StaticVideoView& view) {
buzz::XmlElement* view_elem =
CreateVideoViewElem(content_name, STR_JINGLE_DRAFT_VIEW_TYPE_STATIC);
- AddXmlAttr(view_elem, QN_JINGLE_DRAFT_VIEW_SSRC, view.ssrc);
+ AddXmlAttr(view_elem, QN_SSRC, view.ssrc);
- buzz::XmlElement* params_elem = new buzz::XmlElement(
- QN_JINGLE_DRAFT_VIEW_PARAMS);
- AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_WIDTH, view.width);
- AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_HEIGHT, view.height);
- AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_FRAMERATE,
- view.framerate);
- AddXmlAttr(params_elem, QN_JINGLE_DRAFT_VIEW_PARAMS_PREFERENCE,
- view.preference);
+ buzz::XmlElement* params_elem = new buzz::XmlElement(QN_JINGLE_DRAFT_PARAMS);
+ AddXmlAttr(params_elem, QN_WIDTH, view.width);
+ AddXmlAttr(params_elem, QN_HEIGHT, view.height);
+ AddXmlAttr(params_elem, QN_FRAMERATE, view.framerate);
+ AddXmlAttr(params_elem, QN_PREFERENCE, view.preference);
view_elem->AddElement(params_elem);
return view_elem;
@@ -161,49 +98,100 @@
} // namespace
-bool MediaSources::GetFirstAudioSourceByNick(
- const std::string& nick, NamedSource* source) {
- return GetFirstSourceByNick(audio_, nick, source);
+bool MediaStreams::GetAudioStreamByNickAndName(
+ const std::string& nick, const std::string& name, StreamParams* stream) {
+ return GetStreamByNickAndName(audio_, nick, name, stream);
}
-bool MediaSources::GetFirstVideoSourceByNick(
- const std::string& nick, NamedSource* source) {
- return GetFirstSourceByNick(video_, nick, source);
+bool MediaStreams::GetVideoStreamByNickAndName(
+ const std::string& nick, const std::string& name, StreamParams* stream) {
+ return GetStreamByNickAndName(video_, nick, name, stream);
}
-void MediaSources::CopyFrom(const MediaSources& sources) {
- audio_ = sources.audio_;
- video_ = sources.video_;
+void MediaStreams::CopyFrom(const MediaStreams& streams) {
+ audio_ = streams.audio_;
+ video_ = streams.video_;
}
-bool MediaSources::GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source) {
- return GetSourceBySsrc(audio_, ssrc, source);
+bool MediaStreams::GetAudioStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+ return GetStreamBySsrc(audio_, ssrc, stream);
}
-bool MediaSources::GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source) {
- return GetSourceBySsrc(video_, ssrc, source);
+bool MediaStreams::GetVideoStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+ return GetStreamBySsrc(video_, ssrc, stream);
}
-void MediaSources::AddAudioSource(const NamedSource& source) {
- AddSource(&audio_, source);
+void MediaStreams::AddAudioStream(const StreamParams& stream) {
+ AddStream(&audio_, stream);
}
-void MediaSources::AddVideoSource(const NamedSource& source) {
- AddSource(&video_, source);
+void MediaStreams::AddVideoStream(const StreamParams& stream) {
+ AddStream(&video_, stream);
}
-void MediaSources::RemoveAudioSourceBySsrc(uint32 ssrc) {
- RemoveSourceBySsrc(ssrc, &audio_);
+void MediaStreams::RemoveAudioStreamByNickAndName(
+ const std::string& nick, const std::string& name) {
+ RemoveStreamByNickAndName(&audio_, nick, name);
}
-void MediaSources::RemoveVideoSourceBySsrc(uint32 ssrc) {
- RemoveSourceBySsrc(ssrc, &video_);
+void MediaStreams::RemoveVideoStreamByNickAndName(
+ const std::string& nick, const std::string& name) {
+ RemoveStreamByNickAndName(&video_, nick, name);
}
-bool WriteViewRequest(const std::string& content_name,
- const ViewRequest& request,
- XmlElements* elems,
- WriteError* error) {
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem) {
+ return action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW) != NULL;
+}
+
+bool ParseStaticVideoView(const buzz::XmlElement* view_elem,
+ StaticVideoView* view,
+ ParseError* error) {
+ if (!ParseSsrc(view_elem->Attr(QN_SSRC), &(view->ssrc))) {
+ return BadParse("Invalid or missing view ssrc.", error);
+ }
+
+ const buzz::XmlElement* params_elem =
+ view_elem->FirstNamed(QN_JINGLE_DRAFT_PARAMS);
+ if (params_elem) {
+ view->width = GetXmlAttr(params_elem, QN_WIDTH, 0);
+ view->height = GetXmlAttr(params_elem, QN_HEIGHT, 0);
+ view->framerate = GetXmlAttr(params_elem, QN_FRAMERATE, 0);
+ view->preference = GetXmlAttr(params_elem, QN_PREFERENCE, 0);
+ } else {
+ return BadParse("Missing view params.", error);
+ }
+
+ return true;
+}
+
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+ ViewRequest* view_request,
+ ParseError* error) {
+ for (const buzz::XmlElement* view_elem =
+ action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW);
+ view_elem != NULL;
+ view_elem = view_elem->NextNamed(QN_JINGLE_DRAFT_VIEW)) {
+ std::string type = view_elem->Attr(QN_TYPE);
+ if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
+ view_request->static_video_views.clear();
+ return true;
+ } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
+ StaticVideoView static_video_view(0, 0, 0, 0);
+ if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
+ return false;
+ }
+ view_request->static_video_views.push_back(static_video_view);
+ } else {
+ LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
+ }
+ }
+ return true;
+}
+
+bool WriteJingleViewRequest(const std::string& content_name,
+ const ViewRequest& request,
+ XmlElements* elems,
+ WriteError* error) {
if (request.static_video_views.empty()) {
elems->push_back(CreateNoneVideoViewElem(content_name));
} else {
@@ -216,46 +204,150 @@
return true;
}
-bool IsSourcesNotify(const buzz::XmlElement* action_elem) {
- return action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY) != NULL;
+bool ParseSsrcAsLegacyStream(const buzz::XmlElement* desc_elem,
+ std::vector<StreamParams>* streams,
+ ParseError* error) {
+ const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+ if (!ssrc_str.empty()) {
+ uint32 ssrc;
+ if (!ParseSsrc(ssrc_str, &ssrc)) {
+ return BadParse("Missing or invalid ssrc.", error);
+ }
+
+ streams->push_back(StreamParams::CreateLegacy(ssrc));
+ }
+ return true;
}
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
- const SessionDescription* session_description,
- MediaSources* sources,
+bool ParseSsrcs(const buzz::XmlElement* parent_elem,
+ std::vector<uint32>* ssrcs,
+ ParseError* error) {
+ for (const buzz::XmlElement* ssrc_elem =
+ parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC);
+ ssrc_elem != NULL;
+ ssrc_elem = ssrc_elem->NextNamed(QN_JINGLE_DRAFT_SSRC)) {
+ uint32 ssrc;
+ if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
+ return BadParse("Missing or invalid ssrc.", error);
+ }
+
+ ssrcs->push_back(ssrc);
+ }
+ return true;
+}
+
+bool ParseSsrcGroups(const buzz::XmlElement* parent_elem,
+ std::vector<SsrcGroup>* ssrc_groups,
+ ParseError* error) {
+ for (const buzz::XmlElement* group_elem =
+ parent_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC_GROUP);
+ group_elem != NULL;
+ group_elem = group_elem->NextNamed(QN_JINGLE_DRAFT_SSRC_GROUP)) {
+ std::string semantics = group_elem->Attr(QN_SEMANTICS);
+ std::vector<uint32> ssrcs;
+ if (!ParseSsrcs(group_elem, &ssrcs, error)) {
+ return false;
+ }
+ ssrc_groups->push_back(SsrcGroup(semantics, ssrcs));
+ }
+ return true;
+}
+
+bool ParseJingleStream(const buzz::XmlElement* stream_elem,
+ std::vector<StreamParams>* streams,
+ ParseError* error) {
+ StreamParams stream;
+ stream.nick = stream_elem->Attr(QN_NICK);
+ stream.name = stream_elem->Attr(QN_NAME);
+ stream.type = stream_elem->Attr(QN_TYPE);
+ stream.display = stream_elem->Attr(QN_DISPLAY);
+ stream.cname = stream_elem->Attr(QN_CNAME);
+ if (!ParseSsrcs(stream_elem, &(stream.ssrcs), error)) {
+ return false;
+ }
+ std::vector<SsrcGroup> ssrc_groups;
+ if (!ParseSsrcGroups(stream_elem, &(stream.ssrc_groups), error)) {
+ return false;
+ }
+ streams->push_back(stream);
+ return true;
+}
+
+bool HasJingleStreams(const buzz::XmlElement* desc_elem) {
+ const buzz::XmlElement* streams_elem =
+ desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+ return (streams_elem != NULL);
+}
+
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+ std::vector<StreamParams>* streams,
ParseError* error) {
- for (const buzz::XmlElement* notify_elem =
- action_elem->FirstNamed(QN_JINGLE_DRAFT_NOTIFY);
- notify_elem != NULL;
- notify_elem = notify_elem->NextNamed(QN_JINGLE_DRAFT_NOTIFY)) {
- std::string content_name = notify_elem->Attr(QN_JINGLE_DRAFT_CONTENT_NAME);
- for (const buzz::XmlElement* source_elem =
- notify_elem->FirstNamed(QN_JINGLE_DRAFT_SOURCE);
- source_elem != NULL;
- source_elem = source_elem->NextNamed(QN_JINGLE_DRAFT_SOURCE)) {
- NamedSource named_source;
- if (!ParseNamedSource(source_elem, &named_source, error)) {
- return false;
- }
-
- if (session_description == NULL) {
- return BadParse("Unknown content name: " + content_name, error);
- }
- const ContentInfo* content =
- FindContentInfoByName(session_description->contents(), content_name);
- if (content == NULL) {
- return BadParse("Unknown content name: " + content_name, error);
- }
-
- if (IsAudioContent(content)) {
- sources->mutable_audio()->push_back(named_source);
- } else if (IsVideoContent(content)) {
- sources->mutable_video()->push_back(named_source);
- }
+ const buzz::XmlElement* streams_elem =
+ desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+ if (streams_elem == NULL) {
+ return BadParse("Missing streams element.", error);
+ }
+ for (const buzz::XmlElement* stream_elem =
+ streams_elem->FirstNamed(QN_JINGLE_DRAFT_STREAM);
+ stream_elem != NULL;
+ stream_elem = stream_elem->NextNamed(QN_JINGLE_DRAFT_STREAM)) {
+ if (!ParseJingleStream(stream_elem, streams, error)) {
+ return false;
}
}
-
return true;
}
+void WriteSsrcs(const std::vector<uint32>& ssrcs,
+ buzz::XmlElement* parent_elem) {
+ for (std::vector<uint32>::const_iterator ssrc = ssrcs.begin();
+ ssrc != ssrcs.end(); ++ssrc) {
+ buzz::XmlElement* ssrc_elem =
+ new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC, false);
+ SetXmlBody(ssrc_elem, *ssrc);
+
+ parent_elem->AddElement(ssrc_elem);
+ }
+}
+
+void WriteSsrcGroups(const std::vector<SsrcGroup>& groups,
+ buzz::XmlElement* parent_elem) {
+ for (std::vector<SsrcGroup>::const_iterator group = groups.begin();
+ group != groups.end(); ++group) {
+ buzz::XmlElement* group_elem =
+ new buzz::XmlElement(QN_JINGLE_DRAFT_SSRC_GROUP, false);
+ AddXmlAttrIfNonEmpty(group_elem, QN_SEMANTICS, group->semantics);
+ WriteSsrcs(group->ssrcs, group_elem);
+
+ parent_elem->AddElement(group_elem);
+ }
+}
+
+void WriteJingleStream(const StreamParams& stream,
+ buzz::XmlElement* parent_elem) {
+ buzz::XmlElement* stream_elem =
+ new buzz::XmlElement(QN_JINGLE_DRAFT_STREAM, false);
+ AddXmlAttrIfNonEmpty(stream_elem, QN_NICK, stream.nick);
+ AddXmlAttrIfNonEmpty(stream_elem, QN_NAME, stream.name);
+ AddXmlAttrIfNonEmpty(stream_elem, QN_TYPE, stream.type);
+ AddXmlAttrIfNonEmpty(stream_elem, QN_DISPLAY, stream.display);
+ AddXmlAttrIfNonEmpty(stream_elem, QN_CNAME, stream.cname);
+ WriteSsrcs(stream.ssrcs, stream_elem);
+ WriteSsrcGroups(stream.ssrc_groups, stream_elem);
+
+ parent_elem->AddElement(stream_elem);
+}
+
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+ buzz::XmlElement* parent_elem) {
+ buzz::XmlElement* streams_elem =
+ new buzz::XmlElement(QN_JINGLE_DRAFT_STREAMS, true);
+ for (std::vector<StreamParams>::const_iterator stream = streams.begin();
+ stream != streams.end(); ++stream) {
+ WriteJingleStream(*stream, streams_elem);
+ }
+
+ parent_elem->AddElement(streams_elem);
+}
+
} // namespace cricket
diff --git a/talk/session/phone/mediamessages.h b/talk/session/phone/mediamessages.h
index 541572f..6f38a64 100644
--- a/talk/session/phone/mediamessages.h
+++ b/talk/session/phone/mediamessages.h
@@ -45,62 +45,50 @@
namespace cricket {
-// In a <notify> message, there are number of sources with names.
-// This represents one such named source.
-struct NamedSource {
- NamedSource() : ssrc(0), ssrc_set(false), removed(false) {}
+struct StreamParams;
- void SetSsrc(uint32 ssrc) {
- this->ssrc = ssrc;
- this->ssrc_set = true;
+// A collection of audio and video streams. Most of the methods are
+// merely for convenience. Many of these methods are keyed by ssrc,
+// which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaStreams {
+ public:
+ MediaStreams() {}
+ void CopyFrom(const MediaStreams& sources);
+
+ bool empty() const {
+ return audio_.empty() && video_.empty();
}
- std::string nick;
- std::string name;
- std::string usage;
- uint32 ssrc;
- bool ssrc_set;
- bool removed;
-};
+ std::vector<StreamParams>* mutable_audio() { return &audio_; }
+ std::vector<StreamParams>* mutable_video() { return &video_; }
+ const std::vector<StreamParams>& audio() const { return audio_; }
+ const std::vector<StreamParams>& video() const { return video_; }
-// TODO: Remove this, according to c++ readability.
-typedef std::vector<NamedSource> NamedSources;
-
-// A collection of named audio sources and named video sources, as
-// would be found in a typical <notify> message. Most of the methods
-// are merely for convenience. Many of these methods are keyed by
-// ssrc, which is the source identifier in the RTP spec
-// (http://tools.ietf.org/html/rfc3550).
-struct MediaSources {
- public:
- MediaSources() {}
- void CopyFrom(const MediaSources& sources);
-
- NamedSources* mutable_audio() { return &audio_; }
- NamedSources* mutable_video() { return &video_; }
- const NamedSources& audio() const { return audio_; }
- const NamedSources& video() const { return video_; }
-
+ // Remove the streams with the given name. Names are only unique to
+ // nicks, so you need the nick as well.
+ bool GetAudioStreamByNickAndName(
+ const std::string& nick, const std::string& name, StreamParams* source);
+ bool GetVideoStreamByNickAndName(
+ const std::string& nick, const std::string& name, StreamParams* source);
// Get the source with the given ssrc. Returns true if found.
- bool GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source);
- bool GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source);
- // Get the first source with the given nick. Returns true if found.
- // TODO: Remove the following two methods once all
- // senders use explicit-remove by ssrc.
- bool GetFirstAudioSourceByNick(const std::string& nick, NamedSource* source);
- bool GetFirstVideoSourceByNick(const std::string& nick, NamedSource* source);
+ bool GetAudioStreamBySsrc(uint32 ssrc, StreamParams* source);
+ bool GetVideoStreamBySsrc(uint32 ssrc, StreamParams* source);
// Add a source.
- void AddAudioSource(const NamedSource& source);
- void AddVideoSource(const NamedSource& source);
- // Remove the source with the given ssrc.
- void RemoveAudioSourceBySsrc(uint32 ssrc);
- void RemoveVideoSourceBySsrc(uint32 ssrc);
+ void AddAudioStream(const StreamParams& source);
+ void AddVideoStream(const StreamParams& source);
+ // Remove the source with the given name. Names are only unique to
+ // nicks, so you need the nick as well.
+ void RemoveAudioStreamByNickAndName(const std::string& nick,
+ const std::string& name);
+ void RemoveVideoStreamByNickAndName(const std::string& nick,
+ const std::string& name);
private:
- NamedSources audio_;
- NamedSources video_;
+ std::vector<StreamParams> audio_;
+ std::vector<StreamParams> video_;
- DISALLOW_COPY_AND_ASSIGN(MediaSources);
+ DISALLOW_COPY_AND_ASSIGN(MediaStreams);
};
// In a <view> message, there are a number of views specified. This
@@ -123,28 +111,45 @@
typedef std::vector<StaticVideoView> StaticVideoViews;
-// Represents a whole <view> message, which contains many views.
+// Represents a whole view request message, which contains many views.
struct ViewRequest {
StaticVideoViews static_video_views;
};
+// If the parent element (usually <jingle>) is a jingle view.
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem);
+
+// Parses a view request from the parent element (usually
+// <jingle>). If it fails, it returns false and fills an error
+// message.
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
+ ViewRequest* view_request,
+ ParseError* error);
+
// Serializes a view request to XML. If it fails, returns false and
// fills in an error message.
-bool WriteViewRequest(const std::string& content_name,
- const ViewRequest& view,
- XmlElements* elems,
- WriteError* error);
+bool WriteJingleViewRequest(const std::string& content_name,
+ const ViewRequest& view,
+ XmlElements* elems,
+ WriteError* error);
+// TODO: Get rid of legacy source notify and replace with
+// description-info as soon as reflector is capable of sending it.
bool IsSourcesNotify(const buzz::XmlElement* action_elem);
-// Parses a notify message from XML. If it fails, returns false and
-// fills in an error message.
-// The session_description is needed to map content_name => media type.
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
- const SessionDescription* session_description,
- MediaSources* sources,
+// If the given elem has <streams>.
+bool HasJingleStreams(const buzz::XmlElement* desc_elem);
+
+// Parses streams from a jingle <description>. If it fails, returns
+// false and fills an error message.
+bool ParseJingleStreams(const buzz::XmlElement* desc_elem,
+ std::vector<StreamParams>* streams,
ParseError* error);
+// Write a <streams> element to the parent_elem.
+void WriteJingleStreams(const std::vector<StreamParams>& streams,
+ buzz::XmlElement* parent_elem);
+
} // namespace cricket
#endif // TALK_SESSION_PHONE_MEDIAMESSAGES_H_
diff --git a/talk/session/phone/mediamessages_unittest.cc b/talk/session/phone/mediamessages_unittest.cc
new file mode 100644
index 0000000..a76d296
--- /dev/null
+++ b/talk/session/phone/mediamessages_unittest.cc
@@ -0,0 +1,286 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/mediamessages.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmllite/xmlelement.h"
+
+// Unit tests for mediamessages.cc.
+
+namespace cricket {
+
+namespace {
+
+static const char kViewVideoNoneXml[] =
+ "<view xmlns='google:jingle'"
+ " name='video1'"
+ " type='none'"
+ "/>";
+
+class MediaMessagesTest : public testing::Test {
+ public:
+ // CreateMediaSessionDescription uses a static variable cricket::NS_JINGLE_RTP
+ // defined in another file and cannot be used to initialize another static
+ // variable (http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14)
+ MediaMessagesTest()
+ : remote_description_(CreateMediaSessionDescription("audio1", "video1")) {
+ }
+
+ protected:
+ static std::string ViewVideoStaticVgaXml(const std::string& ssrc) {
+ return "<view xmlns='google:jingle'"
+ " name='video1'"
+ " type='static'"
+ " ssrc='" + ssrc + "'"
+ ">"
+ "<params"
+ " width='640'"
+ " height='480'"
+ " framerate='30'"
+ " preference='0'"
+ " />"
+ "</view>";
+ }
+
+ static cricket::StreamParams CreateStream(const std::string& nick,
+ const std::string& name,
+ uint32 ssrc1,
+ uint32 ssrc2,
+ const std::string& semantics,
+ const std::string& type,
+ const std::string& display) {
+ StreamParams stream;
+ stream.nick = nick;
+ stream.name = name;
+ stream.ssrcs.push_back(ssrc1);
+ stream.ssrcs.push_back(ssrc2);
+ stream.ssrc_groups.push_back(
+ cricket::SsrcGroup(semantics, stream.ssrcs));
+ stream.type = type;
+ stream.display = display;
+ return stream;
+ }
+
+ static std::string StreamsXml(const std::string& stream1,
+ const std::string& stream2) {
+ return "<streams xmlns='google:jingle'>"
+ + stream1
+ + stream2 +
+ "</streams>";
+ }
+
+
+ static std::string StreamXml(const std::string& nick,
+ const std::string& name,
+ const std::string& ssrc1,
+ const std::string& ssrc2,
+ const std::string& semantics,
+ const std::string& type,
+ const std::string& display) {
+ return "<stream"
+ " nick='" + nick + "'"
+ " name='" + name + "'"
+ " type='" + type + "'"
+ " display='" + display + "'"
+ ">"
+ "<ssrc>" + ssrc1 + "</ssrc>"
+ "<ssrc>" + ssrc2 + "</ssrc>"
+ "<ssrc-group"
+ " semantics='" + semantics + "'"
+ ">"
+ "<ssrc>" + ssrc1 + "</ssrc>"
+ "<ssrc>" + ssrc2 + "</ssrc>"
+ "</ssrc-group>"
+ "</stream>";
+ }
+
+ static cricket::SessionDescription* CreateMediaSessionDescription(
+ const std::string& audio_content_name,
+ const std::string& video_content_name) {
+ cricket::SessionDescription* desc = new cricket::SessionDescription();
+ desc->AddContent(audio_content_name, cricket::NS_JINGLE_RTP,
+ new cricket::AudioContentDescription());
+ desc->AddContent(video_content_name, cricket::NS_JINGLE_RTP,
+ new cricket::VideoContentDescription());
+ return desc;
+ }
+
+ talk_base::scoped_ptr<cricket::SessionDescription> remote_description_;
+};
+
+} // anonymous namespace
+
+// Test serializing/deserializing an empty <view> message.
+TEST_F(MediaMessagesTest, ViewNoneToFromXml) {
+ buzz::XmlElement* expected_view_elem =
+ buzz::XmlElement::ForStr(kViewVideoNoneXml);
+ talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+ new buzz::XmlElement(QN_JINGLE));
+
+ EXPECT_FALSE(cricket::IsJingleViewRequest(action_elem.get()));
+ action_elem->AddElement(expected_view_elem);
+ EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+
+ cricket::ViewRequest view_request;
+ cricket::XmlElements actual_view_elems;
+ cricket::WriteError error;
+
+ ASSERT_TRUE(cricket::WriteJingleViewRequest(
+ "video1", view_request, &actual_view_elems, &error));
+
+ ASSERT_EQ(1U, actual_view_elems.size());
+ EXPECT_EQ(expected_view_elem->Str(), actual_view_elems[0]->Str());
+
+ cricket::ParseError parse_error;
+ EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+ ASSERT_TRUE(cricket::ParseJingleViewRequest(
+ action_elem.get(), &view_request, &parse_error));
+ EXPECT_EQ(0U, view_request.static_video_views.size());
+}
+
+// Test serializing/deserializing an a simple vga <view> message.
+TEST_F(MediaMessagesTest, ViewVgaToFromXml) {
+ talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+ new buzz::XmlElement(QN_JINGLE));
+ buzz::XmlElement* expected_view_elem1 =
+ buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("1234"));
+ buzz::XmlElement* expected_view_elem2 =
+ buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("2468"));
+ action_elem->AddElement(expected_view_elem1);
+ action_elem->AddElement(expected_view_elem2);
+
+ cricket::ViewRequest view_request;
+ cricket::XmlElements actual_view_elems;
+ cricket::WriteError error;
+
+ view_request.static_video_views.push_back(
+ cricket::StaticVideoView(1234, 640, 480, 30));
+ view_request.static_video_views.push_back(
+ cricket::StaticVideoView(2468, 640, 480, 30));
+
+ ASSERT_TRUE(cricket::WriteJingleViewRequest(
+ "video1", view_request, &actual_view_elems, &error));
+
+ ASSERT_EQ(2U, actual_view_elems.size());
+ EXPECT_EQ(expected_view_elem1->Str(), actual_view_elems[0]->Str());
+ EXPECT_EQ(expected_view_elem2->Str(), actual_view_elems[1]->Str());
+
+ view_request.static_video_views.clear();
+ cricket::ParseError parse_error;
+ EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
+ ASSERT_TRUE(cricket::ParseJingleViewRequest(
+ action_elem.get(), &view_request, &parse_error));
+ EXPECT_EQ(2U, view_request.static_video_views.size());
+ EXPECT_EQ(1234U, view_request.static_video_views[0].ssrc);
+ EXPECT_EQ(640, view_request.static_video_views[0].width);
+ EXPECT_EQ(480, view_request.static_video_views[0].height);
+ EXPECT_EQ(30, view_request.static_video_views[0].framerate);
+ EXPECT_EQ(2468U, view_request.static_video_views[1].ssrc);
+}
+
+// Test deserializing bad view XML.
+TEST_F(MediaMessagesTest, ParseBadViewXml) {
+ talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+ new buzz::XmlElement(QN_JINGLE));
+ buzz::XmlElement* view_elem =
+ buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("not-an-ssrc"));
+ action_elem->AddElement(view_elem);
+
+ cricket::ViewRequest view_request;
+ cricket::ParseError parse_error;
+ ASSERT_FALSE(cricket::ParseJingleViewRequest(
+ action_elem.get(), &view_request, &parse_error));
+}
+
+
+// Test serializing/deserializing typical streams xml.
+TEST_F(MediaMessagesTest, StreamsToFromXml) {
+ talk_base::scoped_ptr<buzz::XmlElement> expected_streams_elem(
+ buzz::XmlElement::ForStr(
+ StreamsXml(
+ StreamXml("nick1", "name1", "101", "102",
+ "semantics1", "type1", "display1"),
+ StreamXml("nick2", "name2", "201", "202",
+ "semantics2", "type2", "display2"))));
+
+ std::vector<cricket::StreamParams> expected_streams;
+ expected_streams.push_back(CreateStream("nick1", "name1", 101U, 102U,
+ "semantics1", "type1", "display1"));
+ expected_streams.push_back(CreateStream("nick2", "name2", 201U, 202U,
+ "semantics2", "type2", "display2"));
+
+ talk_base::scoped_ptr<buzz::XmlElement> actual_desc_elem(
+ new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+ cricket::WriteJingleStreams(expected_streams, actual_desc_elem.get());
+
+ const buzz::XmlElement* actual_streams_elem =
+ actual_desc_elem->FirstNamed(QN_JINGLE_DRAFT_STREAMS);
+ ASSERT_TRUE(actual_streams_elem != NULL);
+ EXPECT_EQ(expected_streams_elem->Str(), actual_streams_elem->Str());
+
+ talk_base::scoped_ptr<buzz::XmlElement> expected_desc_elem(
+ new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+ expected_desc_elem->AddElement(new buzz::XmlElement(
+ *expected_streams_elem));
+ std::vector<cricket::StreamParams> actual_streams;
+ cricket::ParseError parse_error;
+
+ EXPECT_TRUE(cricket::HasJingleStreams(expected_desc_elem.get()));
+ ASSERT_TRUE(cricket::ParseJingleStreams(
+ expected_desc_elem.get(), &actual_streams, &parse_error));
+ EXPECT_EQ(2U, actual_streams.size());
+ EXPECT_EQ(expected_streams[0], actual_streams[0]);
+ EXPECT_EQ(expected_streams[1], actual_streams[1]);
+}
+
+// Test deserializing bad streams xml.
+TEST_F(MediaMessagesTest, StreamsFromBadXml) {
+ talk_base::scoped_ptr<buzz::XmlElement> streams_elem(
+ buzz::XmlElement::ForStr(
+ StreamsXml(
+ StreamXml("nick1", "name1", "101", "not-an-ssrc",
+ "semantics1", "type1", "display1"),
+ StreamXml("nick2", "name2", "202", "not-an-ssrc",
+ "semantics2", "type2", "display2"))));
+ talk_base::scoped_ptr<buzz::XmlElement> desc_elem(
+ new buzz::XmlElement(QN_JINGLE_RTP_CONTENT));
+ desc_elem->AddElement(new buzz::XmlElement(*streams_elem));
+
+ std::vector<cricket::StreamParams> actual_streams;
+ cricket::ParseError parse_error;
+ ASSERT_FALSE(cricket::ParseJingleStreams(
+ desc_elem.get(), &actual_streams, &parse_error));
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/mediaprocessorinterface.h b/talk/session/phone/mediaprocessorinterface.h
new file mode 100644
index 0000000..2f8d02a
--- /dev/null
+++ b/talk/session/phone/mediaprocessorinterface.h
@@ -0,0 +1,84 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
+#define TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+enum MediaProcessorDirection {
+ MPD_RX = 1 << 0,
+ MPD_TX = 1 << 1,
+ MPD_RX_AND_TX = MPD_RX | MPD_TX,
+};
+
+struct AudioFrame {
+ AudioFrame()
+ : audio10ms(NULL),
+ length(0),
+ sampling_freq(8000),
+ is_stereo(false) {
+ }
+
+ AudioFrame(int16* audio, size_t audio_length, int sample_freq, bool stereo)
+ : audio10ms(audio),
+ length(audio_length),
+ sampling_freq(sample_freq),
+ is_stereo(stereo) {
+ }
+
+ int16* audio10ms;
+ size_t length;
+ int sampling_freq;
+ bool is_stereo;
+};
+
+class VoiceProcessor : public sigslot::has_slots<> {
+ public:
+ virtual ~VoiceProcessor() {}
+ // Contents of frame may be manipulated by the processor.
+ // The processed data is expected to be the same size as the
+ // original data
+ virtual void OnFrame(uint32 ssrc, AudioFrame* frame) = 0;
+};
+
+class VideoProcessor : public sigslot::has_slots<> {
+ public:
+ virtual ~VideoProcessor() {}
+ // Contents of frame may be manipulated by the processor.
+ // The processed data is expected to be the same size as the
+ // original data
+ virtual void OnFrame(uint32 ssrc, VideoFrame* frame) = 0;
+};
+
+} // namespace cricket
+#endif // TALK_SESSION_PHONE_MEDIAPROCESSORINTERFACE_H_
diff --git a/talk/session/phone/mediarecorder.cc b/talk/session/phone/mediarecorder.cc
new file mode 100644
index 0000000..7b06e48
--- /dev/null
+++ b/talk/session/phone/mediarecorder.cc
@@ -0,0 +1,225 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/mediarecorder.h"
+
+#include <limits.h>
+
+#include <string>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/rtpdump.h"
+
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpSink.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpSink::RtpDumpSink(const std::string& filename)
+ : max_size_(INT_MAX),
+ recording_(false),
+ packet_filter_(PF_NONE),
+ filename_(filename) {
+}
+
+RtpDumpSink::~RtpDumpSink() {}
+
+void RtpDumpSink::SetMaxSize(size_t size) {
+ talk_base::CritScope cs(&critical_section_);
+ max_size_ = size;
+}
+
+bool RtpDumpSink::Enable(bool enable) {
+ talk_base::CritScope cs(&critical_section_);
+
+ recording_ = enable;
+
+ // Create a file and the RTP writer if we have not done yet.
+ if (recording_ && !writer_.get()) {
+ stream_.reset(talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(filename_), "wb"));
+ if (!stream_.get()) {
+ return false;
+ }
+ writer_.reset(new RtpDumpWriter(stream_.get()));
+ writer_->set_packet_filter(packet_filter_);
+ } else if (!recording_ && stream_.get()) {
+ stream_->Flush();
+ }
+ return true;
+}
+
+void RtpDumpSink::OnPacket(const void* data, size_t size, bool rtcp) {
+ talk_base::CritScope cs(&critical_section_);
+
+ if (recording_ && writer_.get()) {
+ size_t current_size;
+ if (writer_->GetDumpSize(¤t_size) &&
+ current_size + RtpDumpPacket::kHeaderLength + size <= max_size_) {
+ if (!rtcp) {
+ writer_->WriteRtpPacket(data, size);
+ } else {
+ // TODO: Enable recording RTCP.
+ }
+ }
+ }
+}
+
+void RtpDumpSink::set_packet_filter(int filter) {
+ talk_base::CritScope cs(&critical_section_);
+ packet_filter_ = filter;
+ if (writer_.get()) {
+ writer_->set_packet_filter(packet_filter_);
+ }
+}
+
+void RtpDumpSink::Flush() {
+ talk_base::CritScope cs(&critical_section_);
+ if (stream_.get()) {
+ stream_->Flush();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of MediaRecorder.
+///////////////////////////////////////////////////////////////////////////
+MediaRecorder::MediaRecorder() {}
+
+MediaRecorder::~MediaRecorder() {
+ talk_base::CritScope cs(&critical_section_);
+ std::map<BaseChannel*, SinkPair*>::iterator itr;
+ for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
+ delete itr->second;
+ }
+}
+
+bool MediaRecorder::AddChannel(VoiceChannel* channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter) {
+ return InternalAddChannel(channel, false, send_filename, recv_filename,
+ filter);
+}
+bool MediaRecorder::AddChannel(VideoChannel* channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter) {
+ return InternalAddChannel(channel, true, send_filename, recv_filename,
+ filter);
+}
+
+bool MediaRecorder::InternalAddChannel(BaseChannel* channel,
+ bool video_channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter) {
+ if (!channel) {
+ return false;
+ }
+
+ talk_base::CritScope cs(&critical_section_);
+ if (sinks_.end() != sinks_.find(channel)) {
+ return false; // The channel was added already.
+ }
+
+ SinkPair* sink_pair = new SinkPair;
+ sink_pair->video_channel = video_channel;
+ sink_pair->filter = filter;
+ sink_pair->send_sink.reset(new RtpDumpSink(send_filename));
+ sink_pair->send_sink->set_packet_filter(filter);
+ sink_pair->recv_sink.reset(new RtpDumpSink(recv_filename));
+ sink_pair->recv_sink->set_packet_filter(filter);
+ sinks_[channel] = sink_pair;
+
+ return true;
+}
+
+void MediaRecorder::RemoveChannel(BaseChannel* channel) {
+ talk_base::CritScope cs(&critical_section_);
+ std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
+ if (sinks_.end() != itr) {
+ channel->UnregisterSendSink(itr->second->send_sink.get());
+ channel->UnregisterRecvSink(itr->second->recv_sink.get());
+ delete itr->second;
+ sinks_.erase(itr);
+ }
+}
+
+bool MediaRecorder::EnableChannel(
+ BaseChannel* channel, bool enable_send, bool enable_recv) {
+ talk_base::CritScope cs(&critical_section_);
+ std::map<BaseChannel*, SinkPair*>::iterator itr = sinks_.find(channel);
+ if (sinks_.end() == itr) {
+ return false;
+ }
+
+ SinkPair* sink_pair = itr->second;
+ RtpDumpSink* sink = sink_pair->send_sink.get();
+ sink->Enable(enable_send);
+ if (enable_send) {
+ channel->RegisterSendSink(sink, &RtpDumpSink::OnPacket);
+ } else {
+ channel->UnregisterSendSink(sink);
+ }
+
+ sink = sink_pair->recv_sink.get();
+ sink->Enable(enable_recv);
+ if (enable_recv) {
+ channel->RegisterRecvSink(sink, &RtpDumpSink::OnPacket);
+ } else {
+ channel->UnregisterRecvSink(sink);
+ }
+
+ if (sink_pair->video_channel &&
+ (sink_pair->filter & PF_RTPPACKET) == PF_RTPPACKET) {
+ // Request a full intra frame.
+ VideoChannel* video_channel = static_cast<VideoChannel*>(channel);
+ if (enable_send) {
+ video_channel->SendIntraFrame();
+ }
+ if (enable_recv) {
+ video_channel->RequestIntraFrame();
+ }
+ }
+
+ return true;
+}
+
+void MediaRecorder::FlushSinks() {
+ talk_base::CritScope cs(&critical_section_);
+ std::map<BaseChannel*, SinkPair*>::iterator itr;
+ for (itr = sinks_.begin(); itr != sinks_.end(); ++itr) {
+ itr->second->send_sink->Flush();
+ itr->second->recv_sink->Flush();
+ }
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/mediarecorder.h b/talk/session/phone/mediarecorder.h
new file mode 100644
index 0000000..fa77e6d
--- /dev/null
+++ b/talk/session/phone/mediarecorder.h
@@ -0,0 +1,117 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_MEDIARECORDER_H_
+#define TALK_SESSION_PHONE_MEDIARECORDER_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/mediasink.h"
+
+namespace talk_base {
+class Pathname;
+class FileStream;
+}
+
+namespace cricket {
+
+class BaseChannel;
+class VideoChannel;
+class VoiceChannel;
+class RtpDumpWriter;
+
+// RtpDumpSink implements MediaSinkInterface by dumping the RTP/RTCP packets to
+// a file.
+class RtpDumpSink : public MediaSinkInterface, public sigslot::has_slots<> {
+ public:
+ explicit RtpDumpSink(const std::string& filename);
+ virtual ~RtpDumpSink();
+
+ virtual void SetMaxSize(size_t size);
+ virtual bool Enable(bool enable);
+ virtual bool IsEnabled() const { return recording_; }
+ virtual void OnPacket(const void* data, size_t size, bool rtcp);
+ virtual void set_packet_filter(int filter);
+ int packet_filter() const { return packet_filter_; }
+ void Flush();
+
+ private:
+ size_t max_size_;
+ bool recording_;
+ int packet_filter_;
+ std::string filename_;
+ talk_base::scoped_ptr<talk_base::FileStream> stream_;
+ talk_base::scoped_ptr<RtpDumpWriter> writer_;
+ talk_base::CriticalSection critical_section_;
+
+ DISALLOW_COPY_AND_ASSIGN(RtpDumpSink);
+};
+
+class MediaRecorder {
+ public:
+ MediaRecorder();
+ virtual ~MediaRecorder();
+
+ bool AddChannel(VoiceChannel* channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter);
+ bool AddChannel(VideoChannel* channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter);
+ void RemoveChannel(BaseChannel* channel);
+ bool EnableChannel(BaseChannel* channel, bool enable_send, bool enable_recv);
+ void FlushSinks();
+
+ private:
+ struct SinkPair {
+ bool video_channel;
+ int filter;
+ talk_base::scoped_ptr<RtpDumpSink> send_sink;
+ talk_base::scoped_ptr<RtpDumpSink> recv_sink;
+ };
+
+ bool InternalAddChannel(BaseChannel* channel,
+ bool video_channel,
+ const std::string& send_filename,
+ const std::string& recv_filename,
+ int filter);
+
+ std::map<BaseChannel*, SinkPair*> sinks_;
+ talk_base::CriticalSection critical_section_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaRecorder);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIARECORDER_H_
diff --git a/talk/session/phone/mediarecorder_unittest.cc b/talk/session/phone/mediarecorder_unittest.cc
new file mode 100644
index 0000000..59d1afe
--- /dev/null
+++ b/talk/session/phone/mediarecorder_unittest.cc
@@ -0,0 +1,344 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/fakesession.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediarecorder.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////////
+// Test RtpDumpSink
+/////////////////////////////////////////////////////////////////////////
+class RtpDumpSinkTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path_, true, NULL));
+ path_.SetFilename("sink-test.rtpdump");
+ sink_.reset(new RtpDumpSink(path_.pathname()));
+
+ for (int i = 0; i < ARRAY_SIZE(rtp_buf_); ++i) {
+ RtpTestUtility::kTestRawRtpPackets[i].WriteToByteBuffer(
+ RtpTestUtility::kDefaultSsrc, &rtp_buf_[i]);
+ }
+ }
+
+ virtual void TearDown() {
+ stream_.reset();
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(path_));
+ }
+
+ protected:
+ void OnRtpPacket(const RawRtpPacket& raw) {
+ talk_base::ByteBuffer buf;
+ raw.WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf);
+ sink_->OnPacket(buf.Data(), buf.Length(), false);
+ }
+
+ talk_base::StreamResult ReadPacket(RtpDumpPacket* packet) {
+ if (!stream_.get()) {
+ sink_.reset(); // This will close the file. So we can read it.
+ stream_.reset(talk_base::Filesystem::OpenFile(path_, "rb"));
+ reader_.reset(new RtpDumpReader(stream_.get()));
+ }
+ return reader_->ReadPacket(packet);
+ }
+
+ talk_base::Pathname path_;
+ talk_base::scoped_ptr<RtpDumpSink> sink_;
+ talk_base::ByteBuffer rtp_buf_[3];
+ talk_base::scoped_ptr<talk_base::StreamInterface> stream_;
+ talk_base::scoped_ptr<RtpDumpReader> reader_;
+};
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSink) {
+ // By default, the sink is disabled. The 1st packet is not written.
+ EXPECT_FALSE(sink_->IsEnabled());
+ sink_->set_packet_filter(PF_ALL);
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+ // Enable the sink. The 2nd packet is written.
+ EXPECT_TRUE(sink_->Enable(true));
+ EXPECT_TRUE(sink_->IsEnabled());
+ EXPECT_TRUE(talk_base::Filesystem::IsFile(path_.pathname()));
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+
+ // Disable the sink. The 3rd packet is not written.
+ EXPECT_TRUE(sink_->Enable(false));
+ EXPECT_FALSE(sink_->IsEnabled());
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+ // Read the recorded file and verify it contains only the 2nd packet.
+ RtpDumpPacket packet;
+ EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+ EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+ &packet, &RtpTestUtility::kTestRawRtpPackets[1], false));
+ EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSinkMaxSize) {
+ EXPECT_TRUE(sink_->Enable(true));
+ sink_->set_packet_filter(PF_ALL);
+ sink_->SetMaxSize(strlen(RtpDumpFileHeader::kFirstLine) +
+ RtpDumpFileHeader::kHeaderLength +
+ RtpDumpPacket::kHeaderLength +
+ RtpTestUtility::kTestRawRtpPackets[0].size());
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+ // Exceed the limit size: the 2nd and 3rd packets are not written.
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+ // Read the recorded file and verify that it contains only the first packet.
+ RtpDumpPacket packet;
+ EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+ EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+ &packet, &RtpTestUtility::kTestRawRtpPackets[0], false));
+ EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+TEST_F(RtpDumpSinkTest, TestRtpDumpSinkFilter) {
+ // The default filter is PF_NONE.
+ EXPECT_EQ(PF_NONE, sink_->packet_filter());
+
+ // Set to PF_RTPHEADER before enable.
+ sink_->set_packet_filter(PF_RTPHEADER);
+ EXPECT_EQ(PF_RTPHEADER, sink_->packet_filter());
+ EXPECT_TRUE(sink_->Enable(true));
+ // We dump only the header of the first packet.
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[0]);
+
+ // Set the filter to PF_RTPPACKET. We dump all the second packet.
+ sink_->set_packet_filter(PF_RTPPACKET);
+ EXPECT_EQ(PF_RTPPACKET, sink_->packet_filter());
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[1]);
+
+ // Set the filter to PF_NONE. We do not dump the third packet.
+ sink_->set_packet_filter(PF_NONE);
+ EXPECT_EQ(PF_NONE, sink_->packet_filter());
+ OnRtpPacket(RtpTestUtility::kTestRawRtpPackets[2]);
+
+ // Read the recorded file and verify the header of the first packet and
+ // the whole packet for the second packet.
+ RtpDumpPacket packet;
+ EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+ EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+ &packet, &RtpTestUtility::kTestRawRtpPackets[0], true));
+ EXPECT_EQ(talk_base::SR_SUCCESS, ReadPacket(&packet));
+ EXPECT_TRUE(RtpTestUtility::VerifyPacket(
+ &packet, &RtpTestUtility::kTestRawRtpPackets[1], false));
+ EXPECT_EQ(talk_base::SR_EOS, ReadPacket(&packet));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Test MediaRecorder
+/////////////////////////////////////////////////////////////////////////
+void TestMediaRecorder(BaseChannel* channel,
+ FakeVideoMediaChannel* video_media_channel,
+ int filter) {
+ // Create media recorder.
+ talk_base::scoped_ptr<MediaRecorder> recorder(new MediaRecorder);
+ // Fail to EnableChannel before AddChannel.
+ EXPECT_FALSE(recorder->EnableChannel(channel, true, true));
+ EXPECT_FALSE(channel->HasSendSinks());
+ EXPECT_FALSE(channel->HasRecvSinks());
+
+ // Add the channel to the recorder.
+ talk_base::Pathname path;
+ EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetFilename("send.rtpdump");
+ std::string send_file = path.pathname();
+ path.SetFilename("recv.rtpdump");
+ std::string recv_file = path.pathname();
+ if (video_media_channel) {
+ EXPECT_TRUE(recorder->AddChannel(static_cast<VideoChannel*>(channel),
+ send_file, recv_file, filter));
+ } else {
+ EXPECT_TRUE(recorder->AddChannel(static_cast<VoiceChannel*>(channel),
+ send_file, recv_file, filter));
+ }
+
+ // Enable recording only the sent media.
+ EXPECT_TRUE(recorder->EnableChannel(channel, true, false));
+ EXPECT_TRUE(channel->HasSendSinks());
+ EXPECT_FALSE(channel->HasRecvSinks());
+ if (video_media_channel) {
+ EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100);
+ }
+
+ // Enable recording only the received meida.
+ EXPECT_TRUE(recorder->EnableChannel(channel, false, true));
+ EXPECT_FALSE(channel->HasSendSinks());
+ EXPECT_TRUE(channel->HasRecvSinks());
+ if (video_media_channel) {
+ EXPECT_TRUE(video_media_channel->requested_intra_frame());
+ }
+
+ // Enable recording both the sent and the received video.
+ EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+ EXPECT_TRUE(channel->HasSendSinks());
+ EXPECT_TRUE(channel->HasRecvSinks());
+
+ // Enable recording only headers.
+ if (video_media_channel) {
+ video_media_channel->set_sent_intra_frame(false);
+ video_media_channel->set_requested_intra_frame(false);
+ }
+ EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+ EXPECT_TRUE(channel->HasSendSinks());
+ EXPECT_TRUE(channel->HasRecvSinks());
+ if (video_media_channel) {
+ if ((filter & PF_RTPPACKET) == PF_RTPPACKET) {
+ // If record the whole RTP packet, trigers FIR.
+ EXPECT_TRUE(video_media_channel->requested_intra_frame());
+ EXPECT_TRUE(video_media_channel->sent_intra_frame());
+ } else {
+ // If record only the RTP header, does not triger FIR.
+ EXPECT_FALSE(video_media_channel->requested_intra_frame());
+ EXPECT_FALSE(video_media_channel->sent_intra_frame());
+ }
+ }
+
+ // Remove the voice channel from the recorder.
+ recorder->RemoveChannel(channel);
+ EXPECT_FALSE(channel->HasSendSinks());
+ EXPECT_FALSE(channel->HasRecvSinks());
+
+ // Delete all files.
+ recorder.reset();
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_file));
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_file));
+}
+
+// Fisrt start recording header and then start recording media. Verify that
+// differnt files are created for header and media.
+void TestRecordHeaderAndMedia(BaseChannel* channel,
+ FakeVideoMediaChannel* video_media_channel) {
+ // Create RTP header recorder.
+ talk_base::scoped_ptr<MediaRecorder> header_recorder(new MediaRecorder);
+
+ talk_base::Pathname path;
+ EXPECT_TRUE(talk_base::Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetFilename("send-header.rtpdump");
+ std::string send_header_file = path.pathname();
+ path.SetFilename("recv-header.rtpdump");
+ std::string recv_header_file = path.pathname();
+ if (video_media_channel) {
+ EXPECT_TRUE(header_recorder->AddChannel(
+ static_cast<VideoChannel*>(channel),
+ send_header_file, recv_header_file, PF_RTPHEADER));
+ } else {
+ EXPECT_TRUE(header_recorder->AddChannel(
+ static_cast<VoiceChannel*>(channel),
+ send_header_file, recv_header_file, PF_RTPHEADER));
+ }
+
+ // Enable recording both sent and received.
+ EXPECT_TRUE(header_recorder->EnableChannel(channel, true, true));
+ EXPECT_TRUE(channel->HasSendSinks());
+ EXPECT_TRUE(channel->HasRecvSinks());
+ if (video_media_channel) {
+ EXPECT_FALSE(video_media_channel->sent_intra_frame());
+ EXPECT_FALSE(video_media_channel->requested_intra_frame());
+ }
+
+ // Verify that header files are created.
+ EXPECT_TRUE(talk_base::Filesystem::IsFile(send_header_file));
+ EXPECT_TRUE(talk_base::Filesystem::IsFile(recv_header_file));
+
+ // Create RTP header recorder.
+ talk_base::scoped_ptr<MediaRecorder> recorder(new MediaRecorder);
+ path.SetFilename("send.rtpdump");
+ std::string send_file = path.pathname();
+ path.SetFilename("recv.rtpdump");
+ std::string recv_file = path.pathname();
+ if (video_media_channel) {
+ EXPECT_TRUE(recorder->AddChannel(
+ static_cast<VideoChannel*>(channel),
+ send_file, recv_file, PF_RTPPACKET));
+ } else {
+ EXPECT_TRUE(recorder->AddChannel(
+ static_cast<VoiceChannel*>(channel),
+ send_file, recv_file, PF_RTPPACKET));
+ }
+
+ // Enable recording both sent and received.
+ EXPECT_TRUE(recorder->EnableChannel(channel, true, true));
+ EXPECT_TRUE(channel->HasSendSinks());
+ EXPECT_TRUE(channel->HasRecvSinks());
+ if (video_media_channel) {
+ EXPECT_TRUE_WAIT(video_media_channel->sent_intra_frame(), 100);
+ EXPECT_TRUE(video_media_channel->requested_intra_frame());
+ }
+
+ // Verify that media files are created.
+ EXPECT_TRUE(talk_base::Filesystem::IsFile(send_file));
+ EXPECT_TRUE(talk_base::Filesystem::IsFile(recv_file));
+
+ // Delete all files.
+ header_recorder.reset();
+ recorder.reset();
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_header_file));
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_header_file));
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(send_file));
+ EXPECT_TRUE(talk_base::Filesystem::DeleteFile(recv_file));
+}
+
+TEST(MediaRecorderTest, TestMediaRecorderVoiceChannel) {
+ // Create the voice channel.
+ cricket::FakeSession session;
+ cricket::FakeMediaEngine media_engine;
+ VoiceChannel channel(talk_base::Thread::Current(), &media_engine,
+ new FakeVoiceMediaChannel(NULL), &session, "", false);
+ EXPECT_TRUE(channel.Init());
+ TestMediaRecorder(&channel, NULL, PF_RTPPACKET);
+ TestMediaRecorder(&channel, NULL, PF_RTPHEADER);
+ TestRecordHeaderAndMedia(&channel, NULL);
+}
+
+TEST(MediaRecorderTest, TestMediaRecorderVideoChannel) {
+ // Create the video channel.
+ cricket::FakeSession session;
+ cricket::FakeMediaEngine media_engine;
+ FakeVideoMediaChannel* media_channel = new FakeVideoMediaChannel(NULL);
+ VideoChannel channel(talk_base::Thread::Current(), &media_engine,
+ media_channel, &session, "", false, NULL);
+ EXPECT_TRUE(channel.Init());
+ TestMediaRecorder(&channel, media_channel, PF_RTPPACKET);
+ TestMediaRecorder(&channel, media_channel, PF_RTPHEADER);
+ TestRecordHeaderAndMedia(&channel, media_channel);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index b423708..ed4eaa7 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -29,6 +29,7 @@
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
#include "talk/p2p/base/constants.h"
#include "talk/session/phone/channelmanager.h"
#include "talk/session/phone/cryptoparams.h"
@@ -41,6 +42,8 @@
namespace cricket {
+using talk_base::scoped_ptr;
+
static bool CreateCryptoParams(int tag, const std::string& cipher,
CryptoParams *out) {
std::string key;
@@ -102,40 +105,215 @@
return false;
}
+static const StreamParams* FindFirstStreamParamsByCname(
+ const StreamParamsVec& params_vec,
+ const std::string& cname) {
+ for (StreamParamsVec::const_iterator it = params_vec.begin();
+ it != params_vec.end(); ++it) {
+ if (cname == it->cname)
+ return &*it;
+ }
+ return NULL;
+}
+
+// Generates a new CNAME or the CNAME of an already existing StreamParams
+// if a StreamParams exist for another Stream in streams with sync_label
+// sync_label.
+static bool GenerateCname(const StreamParamsVec& params_vec,
+ const MediaSessionOptions::Streams& streams,
+ const std::string& synch_label,
+ std::string* cname) {
+ ASSERT(cname != NULL);
+ if (!cname)
+ return false;
+
+ // Check if a CNAME exist for any of the other synched streams.
+ for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin();
+ stream_it != streams.end() ; ++stream_it) {
+ if (synch_label != stream_it->sync_label)
+ continue;
+
+ StreamParams param;
+ // nick is empty for StreamParams generated using
+ // MediaSessionDescriptionFactory.
+ if (GetStreamByNickAndName(params_vec, "", stream_it->name,
+ ¶m)) {
+ *cname = param.cname;
+ return true;
+ }
+ }
+ // No other stream seems to exist that we should sync with.
+ // Generate a random string for the RTCP CNAME, as stated in RFC 6222.
+ // This string is only used for synchronization, and therefore is opaque.
+ do {
+ if (!talk_base::CreateRandomString(16, cname)) {
+ ASSERT(false);
+ return false;
+ }
+ } while (FindFirstStreamParamsByCname(params_vec, *cname));
+
+ return true;
+}
+
+// Generate a new SSRC and make sure it does not exist in params_vec.
+static uint32 GenerateSsrc(const StreamParamsVec& params_vec) {
+ uint32 ssrc = 0;
+ do {
+ ssrc = talk_base::CreateRandomNonZeroId();
+ } while (GetStreamBySsrc(params_vec, ssrc, NULL));
+ return ssrc;
+}
+
+// Finds all StreamParams of all media types and attach them to stream_params.
+static void GetCurrentStreamParams(const SessionDescription* sdesc,
+ StreamParamsVec* stream_params) {
+ if (!sdesc)
+ return;
+
+ const ContentInfos& contents = sdesc->contents();
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); content++) {
+ if (!IsAudioContent(&*content) && !IsVideoContent(&*content))
+ continue;
+ const MediaContentDescription* media =
+ static_cast<const MediaContentDescription*>(
+ content->description);
+ const StreamParamsVec& streams = media->streams();
+ for (StreamParamsVec::const_iterator it = streams.begin();
+ it != streams.end(); ++it) {
+ stream_params->push_back(*it);
+ }
+ }
+}
+
+// Adds a StreamParams for each Stream in Streams with media type
+// media_type to content_description.
+// current_parms - All currently known StreamParams of any media type.
+static bool AddStreamParams(
+ MediaType media_type,
+ const MediaSessionOptions::Streams& streams,
+ StreamParamsVec* current_params,
+ MediaContentDescription* content_description) {
+ for (MediaSessionOptions::Streams::const_iterator stream_it = streams.begin();
+ stream_it != streams.end(); ++stream_it) {
+ if (stream_it->type != media_type)
+ continue; // Wrong media type.
+
+ StreamParams param;
+ // nick is empty for StreamParams generated using
+ // MediaSessionDescriptionFactory.
+ if (!GetStreamByNickAndName(*current_params, "", stream_it->name, ¶m)) {
+ // This is a new stream.
+ // Get a CNAME. Either new or same as one of the other synched streams.
+ std::string cname;
+ if (!GenerateCname(*current_params, streams, stream_it->sync_label,
+ &cname)) {
+ return false;
+ }
+ uint32 ssrc = GenerateSsrc(*current_params);
+ // TODO: Generate the more complex types of stream_params.
+
+ StreamParams stream_param;
+ stream_param.name = stream_it->name;
+ stream_param.ssrcs.push_back(ssrc);
+ stream_param.cname = cname;
+ stream_param.sync_label = stream_it->sync_label;
+ content_description->AddStream(stream_param);
+
+ // Store the new StreamParams in current_params.
+ // This is necessary so that we can use the CNAME for other media types.
+ current_params->push_back(stream_param);
+ } else {
+ content_description->AddStream(param);
+ }
+ }
+ return true;
+}
+
+void MediaSessionOptions::AddStream(MediaType type,
+ const std::string& name,
+ const std::string& sync_label) {
+ streams.push_back(Stream(type, name, sync_label));
+
+ if (type == MEDIA_TYPE_VIDEO)
+ has_video = true;
+ else if (type == MEDIA_TYPE_AUDIO)
+ has_audio = true;
+}
+
+void MediaSessionOptions::RemoveStream(MediaType type,
+ const std::string& name) {
+ Streams::iterator stream_it = streams.begin();
+ for (; stream_it != streams.end(); ++stream_it) {
+ if (stream_it->type == type && stream_it->name == name) {
+ streams.erase(stream_it);
+ return;
+ }
+ }
+ ASSERT(false);
+}
+
MediaSessionDescriptionFactory::MediaSessionDescriptionFactory()
- : secure_(SEC_DISABLED) {
+ : secure_(SEC_DISABLED),
+ add_legacy_(true) {
}
MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
ChannelManager* channel_manager)
- : secure_(SEC_DISABLED) {
+ : secure_(SEC_DISABLED),
+ add_legacy_(true) {
channel_manager->GetSupportedAudioCodecs(&audio_codecs_);
channel_manager->GetSupportedVideoCodecs(&video_codecs_);
}
SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
- const MediaSessionOptions& options) {
- SessionDescription* offer = new SessionDescription();
+ const MediaSessionOptions& options,
+ const SessionDescription* current_description) {
+ scoped_ptr<SessionDescription> offer(new SessionDescription());
+
+ StreamParamsVec current_params;
+ GetCurrentStreamParams(current_description, ¤t_params);
if (options.has_audio) {
- AudioContentDescription* audio = new AudioContentDescription();
+ scoped_ptr<AudioContentDescription> audio(new AudioContentDescription());
for (AudioCodecs::const_iterator codec = audio_codecs_.begin();
codec != audio_codecs_.end(); ++codec) {
audio->AddCodec(*codec);
}
audio->SortCodecs();
- audio->set_ssrc(talk_base::CreateRandomNonZeroId());
- audio->set_rtcp_mux(true);
+ if (!AddStreamParams(MEDIA_TYPE_AUDIO, options.streams, ¤t_params,
+ audio.get())) {
+ return NULL; // Abort, something went seriously wrong.
+ }
+
+ if (options.streams.empty() && add_legacy_) {
+ // TODO: Remove this legacy stream when all apps use StreamParams.
+ audio->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+ }
+ audio->set_multistream(options.is_muc);
+ audio->set_rtcp_mux(options.rtcp_mux_enabled);
audio->set_lang(lang_);
if (secure() != SEC_DISABLED) {
CryptoParamsVec audio_cryptos;
- if (GetSupportedAudioCryptos(&audio_cryptos)) {
- for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
- crypto != audio_cryptos.end(); ++crypto) {
- audio->AddCrypto(*crypto);
+ if (current_description) {
+ // Copy crypto parameters from the previous offer.
+ const ContentInfo* info =
+ GetFirstAudioContent(current_description);
+ if (info) {
+ const AudioContentDescription* desc =
+ static_cast<const AudioContentDescription*>(info->description);
+ audio_cryptos = desc->cryptos();
}
}
+ if (audio_cryptos.empty())
+ GetSupportedAudioCryptos(&audio_cryptos); // Generate new cryptos.
+
+ for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
+ crypto != audio_cryptos.end(); ++crypto) {
+ audio->AddCrypto(*crypto);
+ }
+
if (secure() == SEC_REQUIRED) {
if (audio->cryptos().empty()) {
return NULL; // Abort, crypto required but none found.
@@ -144,30 +322,47 @@
}
}
- offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio);
+ offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio.release());
}
// add video codecs, if this is a video call
if (options.has_video) {
- VideoContentDescription* video = new VideoContentDescription();
+ scoped_ptr<VideoContentDescription> video(new VideoContentDescription());
for (VideoCodecs::const_iterator codec = video_codecs_.begin();
codec != video_codecs_.end(); ++codec) {
video->AddCodec(*codec);
}
video->SortCodecs();
- video->set_ssrc(talk_base::CreateRandomNonZeroId());
+ if (!AddStreamParams(MEDIA_TYPE_VIDEO, options.streams, ¤t_params,
+ video.get())) {
+ return NULL; // Abort, something went seriously wrong.
+ }
+
+ if (options.streams.empty() && add_legacy_) {
+ // TODO: Remove this legacy stream when all apps use StreamParams.
+ video->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+ }
+ video->set_multistream(options.is_muc);
video->set_bandwidth(options.video_bandwidth);
- video->set_rtcp_mux(true);
+ video->set_rtcp_mux(options.rtcp_mux_enabled);
if (secure() != SEC_DISABLED) {
CryptoParamsVec video_cryptos;
- if (GetSupportedVideoCryptos(&video_cryptos)) {
- for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
- crypto != video_cryptos.end(); ++crypto) {
- video->AddCrypto(*crypto);
+ if (current_description) {
+ // Copy crypto parameters from the previous offer.
+ const VideoContentDescription* desc =
+ GetFirstVideoContentDescription(current_description);
+ if (desc) {
+ video_cryptos = desc->cryptos();
}
}
+ if (video_cryptos.empty())
+ GetSupportedVideoCryptos(&video_cryptos); // Generate new crypto.
+ for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
+ crypto != video_cryptos.end(); ++crypto) {
+ video->AddCrypto(*crypto);
+ }
if (secure() == SEC_REQUIRED) {
if (video->cryptos().empty()) {
return NULL; // Abort, crypto required but none found.
@@ -176,24 +371,29 @@
}
}
- offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video);
+ offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video.release());
}
- return offer;
+ return offer.release();
}
SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
- const SessionDescription* offer, const MediaSessionOptions& options) {
+ const SessionDescription* offer, const MediaSessionOptions& options,
+ const SessionDescription* current_description) {
// The answer contains the intersection of the codecs in the offer with the
// codecs we support, ordered by our local preference. As indicated by
// XEP-0167, we retain the same payload ids from the offer in the answer.
- SessionDescription* accept = new SessionDescription();
+ scoped_ptr<SessionDescription> accept(new SessionDescription());
+
+ StreamParamsVec current_params;
+ GetCurrentStreamParams(current_description, ¤t_params);
const ContentInfo* audio_content = GetFirstAudioContent(offer);
if (audio_content && options.has_audio) {
const AudioContentDescription* audio_offer =
static_cast<const AudioContentDescription*>(audio_content->description);
- AudioContentDescription* audio_accept = new AudioContentDescription();
+ scoped_ptr<AudioContentDescription> audio_accept(
+ new AudioContentDescription());
for (AudioCodecs::const_iterator ours = audio_codecs_.begin();
ours != audio_codecs_.end(); ++ours) {
for (AudioCodecs::const_iterator theirs = audio_offer->codecs().begin();
@@ -207,13 +407,40 @@
}
audio_accept->SortCodecs();
- audio_accept->set_ssrc(talk_base::CreateRandomNonZeroId());
- audio_accept->set_rtcp_mux(audio_offer->rtcp_mux());
+ if (!AddStreamParams(MEDIA_TYPE_AUDIO, options.streams, ¤t_params,
+ audio_accept.get())) {
+ return NULL; // Abort, something went seriously wrong.
+ }
+
+ if (options.streams.empty() && add_legacy_) {
+ // TODO: Remove this legacy stream when all apps use StreamParams.
+ audio_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+ }
+ audio_accept->set_rtcp_mux(
+ options.rtcp_mux_enabled && audio_offer->rtcp_mux());
if (secure() != SEC_DISABLED) {
CryptoParams crypto;
if (SelectCrypto(audio_offer, &crypto)) {
+ if (current_description) {
+ // Check if this crypto already exist in the previous
+ // session description. Use it in that case.
+ const ContentInfo* info =
+ GetFirstAudioContent(current_description);
+ if (info) {
+ const AudioContentDescription* desc =
+ static_cast<const AudioContentDescription*>(info->description);
+ const CryptoParamsVec& cryptos = desc->cryptos();
+ for (CryptoParamsVec::const_iterator it = cryptos.begin();
+ it != cryptos.end(); ++it) {
+ if (crypto.Matches(*it)) {
+ crypto = *it;
+ break;
+ }
+ }
+ }
+ }
audio_accept->AddCrypto(crypto);
}
}
@@ -222,7 +449,8 @@
(audio_offer->crypto_required() || secure() == SEC_REQUIRED)) {
return NULL; // Fails the session setup.
}
- accept->AddContent(audio_content->name, audio_content->type, audio_accept);
+ accept->AddContent(audio_content->name, audio_content->type,
+ audio_accept.release());
} else {
LOG(LS_INFO) << "Audio is not supported in answer";
}
@@ -231,7 +459,8 @@
if (video_content && options.has_video) {
const VideoContentDescription* video_offer =
static_cast<const VideoContentDescription*>(video_content->description);
- VideoContentDescription* video_accept = new VideoContentDescription();
+ scoped_ptr<VideoContentDescription> video_accept(
+ new VideoContentDescription());
for (VideoCodecs::const_iterator ours = video_codecs_.begin();
ours != video_codecs_.end(); ++ours) {
for (VideoCodecs::const_iterator theirs = video_offer->codecs().begin();
@@ -243,16 +472,40 @@
}
}
}
+ if (!AddStreamParams(MEDIA_TYPE_VIDEO, options.streams, ¤t_params,
+ video_accept.get())) {
+ return NULL; // Abort, something went seriously wrong.
+ }
- video_accept->set_ssrc(talk_base::CreateRandomNonZeroId());
+ if (options.streams.empty() && add_legacy_) {
+ // TODO: Remove this legacy stream when all apps use StreamParams.
+ video_accept->AddLegacyStream(talk_base::CreateRandomNonZeroId());
+ }
video_accept->set_bandwidth(options.video_bandwidth);
- video_accept->set_rtcp_mux(video_offer->rtcp_mux());
+ video_accept->set_rtcp_mux(
+ options.rtcp_mux_enabled && video_offer->rtcp_mux());
video_accept->SortCodecs();
if (secure() != SEC_DISABLED) {
CryptoParams crypto;
if (SelectCrypto(video_offer, &crypto)) {
+ if (current_description) {
+ // Check if this crypto already exist in the previous
+ // session description. Use it in that case.
+ const VideoContentDescription* desc =
+ GetFirstVideoContentDescription(current_description);
+ if (desc) {
+ const CryptoParamsVec& cryptos = desc->cryptos();
+ for (CryptoParamsVec::const_iterator it = cryptos.begin();
+ it != cryptos.end(); ++it) {
+ if (crypto.Matches(*it)) {
+ crypto = *it;
+ break;
+ }
+ }
+ }
+ }
video_accept->AddCrypto(crypto);
}
}
@@ -261,12 +514,12 @@
(video_offer->crypto_required() || secure() == SEC_REQUIRED)) {
return NULL; // Fails the session setup.
}
- accept->AddContent(video_content->name, video_content->type, video_accept);
+ accept->AddContent(video_content->name, video_content->type,
+ video_accept.release());
} else {
LOG(LS_INFO) << "Video is not supported in answer";
}
-
- return accept;
+ return accept.release();
}
static bool IsMediaContent(const ContentInfo* content, MediaType media_type) {
@@ -287,12 +540,8 @@
return IsMediaContent(content, MEDIA_TYPE_VIDEO);
}
-static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
MediaType media_type) {
- if (sdesc == NULL)
- return NULL;
-
- const ContentInfos& contents = sdesc->contents();
for (ContentInfos::const_iterator content = contents.begin();
content != contents.end(); content++) {
if (IsMediaContent(&*content, media_type)) {
@@ -302,6 +551,22 @@
return NULL;
}
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) {
+ return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO);
+}
+
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) {
+ return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO);
+}
+
+static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+ MediaType media_type) {
+ if (sdesc == NULL)
+ return NULL;
+
+ return GetFirstMediaContent(sdesc->contents(), media_type);
+}
+
const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
}
@@ -310,4 +575,18 @@
return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
}
+const AudioContentDescription* GetFirstAudioContentDescription(
+ const SessionDescription* sdesc) {
+ const ContentInfo* content = GetFirstAudioContent(sdesc);
+ const ContentDescription* description = content ? content->description : NULL;
+ return static_cast<const AudioContentDescription*>(description);
+}
+
+const VideoContentDescription* GetFirstVideoContentDescription(
+ const SessionDescription* sdesc) {
+ const ContentInfo* content = GetFirstVideoContent(sdesc);
+ const ContentDescription* description = content ? content->description : NULL;
+ return static_cast<const VideoContentDescription*>(description);
+}
+
} // namespace cricket
diff --git a/talk/session/phone/mediasession.h b/talk/session/phone/mediasession.h
index 4e2a8fb..718f013 100644
--- a/talk/session/phone/mediasession.h
+++ b/talk/session/phone/mediasession.h
@@ -37,6 +37,7 @@
#include "talk/session/phone/codec.h"
#include "talk/session/phone/cryptoparams.h"
#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/streamparams.h"
#include "talk/p2p/base/sessiondescription.h"
namespace cricket {
@@ -64,6 +65,11 @@
SEC_REQUIRED
};
+enum MediaType {
+ MEDIA_TYPE_AUDIO,
+ MEDIA_TYPE_VIDEO
+};
+
// Options to control how session descriptions are generated.
const int kAutoBandwidth = -1;
struct MediaSessionOptions {
@@ -71,41 +77,55 @@
has_audio(true), // Audio enabled by default.
has_video(false),
is_muc(false),
+ rtcp_mux_enabled(true),
video_bandwidth(kAutoBandwidth) {
}
+ // Add a stream with MediaType type and id name.
+ // All streams with the same sync_label will get the same CNAME.
+ // All names must be unique.
+ void AddStream(MediaType type,
+ const std::string& name,
+ const std::string& sync_label);
+ void RemoveStream(MediaType type, const std::string& name);
+
bool has_audio;
bool has_video;
bool is_muc;
+ bool rtcp_mux_enabled;
// bps. -1 == auto.
int video_bandwidth;
-};
-enum MediaType {
- MEDIA_TYPE_AUDIO,
- MEDIA_TYPE_VIDEO
+ struct Stream {
+ Stream(MediaType type,
+ const std::string& name,
+ const std::string& sync_label)
+ : type(type), name(name), sync_label(sync_label) {
+ }
+ MediaType type;
+ std::string name;
+ std::string sync_label;
+ };
+
+ typedef std::vector<Stream> Streams;
+ Streams streams;
};
// "content" (as used in XEP-0166) descriptions for voice and video.
class MediaContentDescription : public ContentDescription {
public:
MediaContentDescription()
- : ssrc_(0),
- ssrc_set_(false),
- rtcp_mux_(false),
+ : rtcp_mux_(false),
bandwidth_(kAutoBandwidth),
crypto_required_(false),
- rtp_header_extensions_set_(false) {
+ rtp_header_extensions_set_(false),
+ multistream_(false),
+ conference_mode_(false),
+ partial_(false) {
}
virtual MediaType type() const = 0;
-
- uint32 ssrc() const { return ssrc_; }
- bool ssrc_set() const { return ssrc_set_; }
- void set_ssrc(uint32 ssrc) {
- ssrc_ = ssrc;
- ssrc_set_ = true;
- }
+ virtual bool has_codecs() const = 0;
bool rtcp_mux() const { return rtcp_mux_; }
void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
@@ -141,16 +161,63 @@
bool rtp_header_extensions_set() const {
return rtp_header_extensions_set_;
}
+ // True iff the client supports multiple streams.
+ void set_multistream(bool multistream) { multistream_ = multistream; }
+ bool multistream() const { return multistream_; }
+ const StreamParamsVec& streams() const {
+ return streams_;
+ }
+ // TODO: Remove this by giving mediamessage.cc access
+ // to MediaContentDescription
+ StreamParamsVec& mutable_streams() {
+ return streams_;
+ }
+ void AddStream(const StreamParams& stream) {
+ streams_.push_back(stream);
+ }
+ // Legacy streams have an ssrc, but nothing else.
+ void AddLegacyStream(uint32 ssrc) {
+ streams_.push_back(StreamParams::CreateLegacy(ssrc));
+ }
+ // Sets the CNAME of all StreamParams if it have not been set.
+ // This can be used to set the CNAME of legacy streams.
+ void SetCnameIfEmpty(const std::string& cname) {
+ for (cricket::StreamParamsVec::iterator it = streams_.begin();
+ it != streams_.end(); ++it) {
+ if (it->cname.empty())
+ it->cname = cname;
+ }
+ }
+ uint32 first_ssrc() const {
+ if (streams_.empty()) {
+ return 0;
+ }
+ return streams_[0].first_ssrc();
+ }
+ bool has_ssrcs() const {
+ if (streams_.empty()) {
+ return false;
+ }
+ return streams_[0].has_ssrcs();
+ }
+
+ void set_conference_mode(bool enable) { conference_mode_ = enable; }
+ bool conference_mode() const { return conference_mode_; }
+
+ void set_partial(bool partial) { partial_ = partial; }
+ bool partial() const { return partial_; }
protected:
- uint32 ssrc_;
- bool ssrc_set_;
bool rtcp_mux_;
int bandwidth_;
std::vector<CryptoParams> cryptos_;
bool crypto_required_;
std::vector<RtpHeaderExtension> rtp_header_extensions_;
bool rtp_header_extensions_set_;
+ bool multistream_;
+ StreamParamsVec streams_;
+ bool conference_mode_;
+ bool partial_;
};
template <class C>
@@ -161,6 +228,8 @@
};
const std::vector<C>& codecs() const { return codecs_; }
+ void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
+ virtual bool has_codecs() const { return !codecs_.empty(); }
void AddCodec(const C& codec) {
codecs_.push_back(codec);
}
@@ -175,21 +244,22 @@
class AudioContentDescription : public MediaContentDescriptionImpl<AudioCodec> {
public:
AudioContentDescription() :
- conference_mode_(false) {}
+ agc_minus_10db_(false) {}
virtual MediaType type() const { return MEDIA_TYPE_AUDIO; }
- bool conference_mode() const { return conference_mode_; }
- void set_conference_mode(bool enable) {
- conference_mode_ = enable;
- }
-
const std::string &lang() const { return lang_; }
void set_lang(const std::string &lang) { lang_ = lang; }
+ bool agc_minus_10db() const { return agc_minus_10db_; }
+ void set_agc_minus_10db(bool enable) {
+ agc_minus_10db_ = enable;
+ }
private:
- bool conference_mode_;
+ bool agc_minus_10db_;
+
+ private:
std::string lang_;
};
@@ -215,23 +285,40 @@
void set_video_codecs(const VideoCodecs& codecs) { video_codecs_ = codecs; }
SecureMediaPolicy secure() const { return secure_; }
void set_secure(SecureMediaPolicy s) { secure_ = s; }
+ // Decides if a StreamParams shall be added to the audio and video media
+ // content in SessionDescription when CreateOffer and CreateAnswer is called
+ // even if |options| don't include a Stream. This is needed to support legacy
+ // applications. |add_legacy_| is true per default.
+ void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; }
- SessionDescription* CreateOffer(const MediaSessionOptions& options);
- SessionDescription* CreateAnswer(const SessionDescription* offer,
- const MediaSessionOptions& options);
+ SessionDescription* CreateOffer(
+ const MediaSessionOptions& options,
+ const SessionDescription* current_description);
+
+ SessionDescription* CreateAnswer(
+ const SessionDescription* offer,
+ const MediaSessionOptions& options,
+ const SessionDescription* current_description);
private:
AudioCodecs audio_codecs_;
VideoCodecs video_codecs_;
SecureMediaPolicy secure_;
+ bool add_legacy_;
std::string lang_;
};
// Convenience functions.
bool IsAudioContent(const ContentInfo* content);
bool IsVideoContent(const ContentInfo* content);
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents);
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents);
const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+const AudioContentDescription* GetFirstAudioContentDescription(
+ const SessionDescription* sdesc);
+const VideoContentDescription* GetFirstVideoContentDescription(
+ const SessionDescription* sdesc);
} // namespace cricket
diff --git a/talk/session/phone/mediasession_unittest.cc b/talk/session/phone/mediasession_unittest.cc
new file mode 100644
index 0000000..974531e
--- /dev/null
+++ b/talk/session/phone/mediasession_unittest.cc
@@ -0,0 +1,624 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/srtpfilter.h"
+#include "talk/session/phone/testutils.h"
+
+#ifdef HAVE_SRTP
+#define ASSERT_CRYPTO(cd, r, s, cs) \
+ ASSERT_EQ(r, cd->crypto_required()); \
+ ASSERT_EQ(s, cd->cryptos().size()); \
+ ASSERT_EQ(std::string(cs), cd->cryptos()[0].cipher_suite)
+#else
+#define ASSERT_CRYPTO(c, r, s, cs) \
+ ASSERT_EQ(false, cd->crypto_required()); \
+ ASSERT_EQ(0U, cd->cryptos().size());
+#endif
+
+using cricket::MediaSessionDescriptionFactory;
+using cricket::MediaSessionOptions;
+using cricket::MediaType;
+using cricket::SessionDescription;
+using cricket::SsrcGroup;
+using cricket::StreamParams;
+using cricket::StreamParamsVec;
+using cricket::ContentInfo;
+using cricket::CryptoParamsVec;
+using cricket::AudioContentDescription;
+using cricket::VideoContentDescription;
+using cricket::GetFirstAudioContentDescription;
+using cricket::GetFirstVideoContentDescription;
+using cricket::kAutoBandwidth;
+using cricket::AudioCodec;
+using cricket::VideoCodec;
+using cricket::NS_JINGLE_RTP;
+using cricket::MEDIA_TYPE_AUDIO;
+using cricket::MEDIA_TYPE_VIDEO;
+using cricket::SEC_ENABLED;
+using cricket::CS_AES_CM_128_HMAC_SHA1_32;
+using cricket::CS_AES_CM_128_HMAC_SHA1_80;
+
+static const AudioCodec kAudioCodecs1[] = {
+ AudioCodec(103, "ISAC", 16000, -1, 1, 5),
+ AudioCodec(102, "iLBC", 8000, 13300, 1, 4),
+ AudioCodec(0, "PCMU", 8000, 64000, 1, 3),
+ AudioCodec(8, "PCMA", 8000, 64000, 1, 2),
+ AudioCodec(117, "red", 8000, 0, 1, 1),
+};
+
+static const AudioCodec kAudioCodecs2[] = {
+ AudioCodec(126, "speex", 16000, 22000, 1, 3),
+ AudioCodec(127, "iLBC", 8000, 13300, 1, 2),
+ AudioCodec(0, "PCMU", 8000, 64000, 1, 1),
+};
+
+static const AudioCodec kAudioCodecsAnswer[] = {
+ AudioCodec(102, "iLBC", 8000, 13300, 1, 2),
+ AudioCodec(0, "PCMU", 8000, 64000, 1, 1),
+};
+
+static const VideoCodec kVideoCodecs1[] = {
+ VideoCodec(96, "H264-SVC", 320, 200, 30, 2),
+ VideoCodec(97, "H264", 320, 200, 30, 1)
+};
+
+static const VideoCodec kVideoCodecs2[] = {
+ VideoCodec(126, "H264", 320, 200, 30, 2),
+ VideoCodec(127, "H263", 320, 200, 30, 1)
+};
+
+static const VideoCodec kVideoCodecsAnswer[] = {
+ VideoCodec(97, "H264", 320, 200, 30, 2)
+};
+
+static const uint32 kSimulcastParamsSsrc[] = {10, 11, 20, 21, 30, 31};
+static const uint32 kSimSsrc[] = {10, 20, 30};
+static const uint32 kFec1Ssrc[] = {10, 11};
+static const uint32 kFec2Ssrc[] = {20, 21};
+static const uint32 kFec3Ssrc[] = {30, 31};
+
+static const char kMediaStream1[] = "stream_1";
+static const char kMediaStream2[] = "stream_2";
+static const char kVideoTrack1[] = "video_1";
+static const char kVideoTrack2[] = "video_2";
+static const char kAudioTrack1[] = "audio_1";
+static const char kAudioTrack2[] = "audio_2";
+static const char kAudioTrack3[] = "audio_3";
+
+class MediaSessionDescriptionFactoryTest : public testing::Test {
+ public:
+ MediaSessionDescriptionFactoryTest() {
+ f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1));
+ f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
+ f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2));
+ f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2));
+ }
+
+ // Create a video StreamParamsVec object with:
+ // - one video stream with 3 simulcast streams and FEC,
+ StreamParamsVec CreateComplexVideoStreamParamsVec() {
+ SsrcGroup sim_group("SIM", MAKE_VECTOR(kSimSsrc));
+ SsrcGroup fec_group1("FEC", MAKE_VECTOR(kFec1Ssrc));
+ SsrcGroup fec_group2("FEC", MAKE_VECTOR(kFec2Ssrc));
+ SsrcGroup fec_group3("FEC", MAKE_VECTOR(kFec3Ssrc));
+
+ std::vector<SsrcGroup> ssrc_groups;
+ ssrc_groups.push_back(sim_group);
+ ssrc_groups.push_back(fec_group1);
+ ssrc_groups.push_back(fec_group2);
+ ssrc_groups.push_back(fec_group3);
+
+ StreamParams simulcast_params;
+ simulcast_params.name = kVideoTrack1;
+ simulcast_params.ssrcs = MAKE_VECTOR(kSimulcastParamsSsrc);
+ simulcast_params.ssrc_groups = ssrc_groups;
+ simulcast_params.cname = "Video_SIM_FEC";
+ simulcast_params.sync_label = kMediaStream1;
+
+ StreamParamsVec video_streams;
+ video_streams.push_back(simulcast_params);
+
+ return video_streams;
+ }
+ bool CompareCryptoParams(const CryptoParamsVec& c1,
+ const CryptoParamsVec& c2) {
+ if (c1.size() != c2.size())
+ return false;
+ for (size_t i = 0; i < c1.size(); ++i)
+ if (c1[i].tag != c2[i].tag || c1[i].cipher_suite != c2[i].cipher_suite ||
+ c1[i].key_params != c2[i].key_params ||
+ c1[i].session_params != c2[i].session_params)
+ return false;
+ return true;
+ }
+
+ protected:
+ MediaSessionDescriptionFactory f1_;
+ MediaSessionDescriptionFactory f2_;
+};
+
+// Create a typical audio offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
+ f1_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription> offer(
+ f1_.CreateOffer(MediaSessionOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc == NULL);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+ EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on
+ ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Create a typical video offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ f1_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription>
+ offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+ EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on
+ ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+ EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+ EXPECT_EQ(f1_.video_codecs(), vcd->codecs());
+ EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_EQ(kAutoBandwidth, vcd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(vcd->rtcp_mux()); // rtcp-mux defaults on
+ ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Create an audio, video offer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+ TestCreateOfferWithoutLegacyStreams) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ f1_.set_add_legacy_streams(false);
+ talk_base::scoped_ptr<SessionDescription>
+ offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+
+ EXPECT_FALSE(vcd->has_ssrcs()); // No StreamParams.
+ EXPECT_FALSE(acd->has_ssrcs()); // No StreamParams.
+}
+
+// Create a typical audio answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
+ f1_.set_secure(SEC_ENABLED);
+ f2_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription> offer(
+ f1_.CreateOffer(MediaSessionOptions(), NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ talk_base::scoped_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+ const ContentInfo* ac = answer->GetContentByName("audio");
+ const ContentInfo* vc = answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc == NULL);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+ EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw
+ EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux
+ ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Create a typical video answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ f1_.set_secure(SEC_ENABLED);
+ f2_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ talk_base::scoped_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ const ContentInfo* ac = answer->GetContentByName("audio");
+ const ContentInfo* vc = answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw
+ EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux
+ ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+ EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+ EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
+ EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux
+ ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Create an audio, video answer without legacy StreamParams.
+TEST_F(MediaSessionDescriptionFactoryTest,
+ TestCreateAnswerWithoutLegacyStreams) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ f1_.set_add_legacy_streams(false);
+ f2_.set_add_legacy_streams(false);
+ talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ talk_base::scoped_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ const ContentInfo* ac = answer->GetContentByName("audio");
+ const ContentInfo* vc = answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+
+ EXPECT_FALSE(acd->has_ssrcs()); // No StreamParams.
+ EXPECT_FALSE(vcd->has_ssrcs()); // No StreamParams.
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ f1_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription>
+ offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ AudioContentDescription* acd = const_cast<AudioContentDescription*>(
+ static_cast<const AudioContentDescription*>(ac->description));
+ VideoContentDescription* vcd = const_cast<VideoContentDescription*>(
+ static_cast<const VideoContentDescription*>(vc->description));
+
+ EXPECT_FALSE(acd->partial()); // default is false.
+ acd->set_partial(true);
+ EXPECT_TRUE(acd->partial());
+ acd->set_partial(false);
+ EXPECT_FALSE(acd->partial());
+
+ EXPECT_FALSE(vcd->partial()); // default is false.
+ vcd->set_partial(true);
+ EXPECT_TRUE(vcd->partial());
+ vcd->set_partial(false);
+ EXPECT_FALSE(vcd->partial());
+}
+
+// Create a typical video answer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) {
+ MediaSessionOptions offer_opts;
+ MediaSessionOptions answer_opts;
+ answer_opts.has_video = true;
+ offer_opts.has_video = true;
+
+ talk_base::scoped_ptr<SessionDescription> offer(NULL);
+ talk_base::scoped_ptr<SessionDescription> answer(NULL);
+
+ offer_opts.rtcp_mux_enabled = true;
+ answer_opts.rtcp_mux_enabled = true;
+
+ offer.reset(f1_.CreateOffer(offer_opts, NULL));
+ answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+ EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+ EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+ EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+ EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+ offer_opts.rtcp_mux_enabled = true;
+ answer_opts.rtcp_mux_enabled = false;
+
+ offer.reset(f1_.CreateOffer(offer_opts, NULL));
+ answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+ EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+ EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+ offer_opts.rtcp_mux_enabled = false;
+ answer_opts.rtcp_mux_enabled = true;
+
+ offer.reset(f1_.CreateOffer(offer_opts, NULL));
+ answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+ EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+
+ offer_opts.rtcp_mux_enabled = false;
+ answer_opts.rtcp_mux_enabled = false;
+
+ offer.reset(f1_.CreateOffer(offer_opts, NULL));
+ answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+ ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+ ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+ EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+ EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
+}
+
+// Create an audio-only answer to a video offer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
+ MediaSessionOptions opts;
+ opts.has_video = true;
+ talk_base::scoped_ptr<SessionDescription>
+ offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ talk_base::scoped_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+ const ContentInfo* ac = answer->GetContentByName("audio");
+ const ContentInfo* vc = answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc == NULL);
+}
+
+// Create an audio and video offer with:
+// - one video track,
+// - two audio tracks.
+// and ensure it matches what we expect. Also updates the initial offer by
+// adding a new video track and replaces one of the audio tracks.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
+ MediaSessionOptions opts;
+ opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+ opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+ opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+
+ f1_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+
+ ASSERT_TRUE(offer.get() != NULL);
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(f1_.audio_codecs(), acd->codecs());
+
+ const StreamParamsVec& audio_streams = acd->streams();
+ ASSERT_EQ(2U, audio_streams.size());
+ EXPECT_EQ(audio_streams[0].cname , audio_streams[1].cname);
+ EXPECT_EQ(kAudioTrack1, audio_streams[0].name);
+ ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+ EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+ EXPECT_EQ(kAudioTrack2, audio_streams[1].name);
+ ASSERT_EQ(1U, audio_streams[1].ssrcs.size());
+ EXPECT_NE(0U, audio_streams[1].ssrcs[0]);
+
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on
+ ASSERT_CRYPTO(acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+
+ EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+ EXPECT_EQ(f1_.video_codecs(), vcd->codecs());
+ ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+ const StreamParamsVec& video_streams = vcd->streams();
+ ASSERT_EQ(1U, video_streams.size());
+ EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
+ EXPECT_EQ(kVideoTrack1, video_streams[0].name);
+ EXPECT_EQ(kAutoBandwidth, vcd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(vcd->rtcp_mux()); // rtcp-mux defaults on
+
+
+ // Update the offer. Add a new video track that is not synched to the
+ // other tracks and replace audio track 2 with audio track 3.
+ opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+ opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+ opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1);
+ talk_base::scoped_ptr<SessionDescription>
+ updated_offer(f1_.CreateOffer(opts, offer.get()));
+
+ ASSERT_TRUE(updated_offer.get() != NULL);
+ ac = updated_offer->GetContentByName("audio");
+ vc = updated_offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* updated_acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* updated_vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+
+ EXPECT_EQ(acd->type(), updated_acd->type());
+ EXPECT_EQ(acd->codecs(), updated_acd->codecs());
+ EXPECT_EQ(vcd->type(), updated_vcd->type());
+ EXPECT_EQ(vcd->codecs(), updated_vcd->codecs());
+ ASSERT_CRYPTO(updated_acd, false, 2U, CS_AES_CM_128_HMAC_SHA1_32);
+ EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+ ASSERT_CRYPTO(updated_vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+ EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+
+ const StreamParamsVec& updated_audio_streams = updated_acd->streams();
+ ASSERT_EQ(2U, updated_audio_streams.size());
+ EXPECT_EQ(audio_streams[0], updated_audio_streams[0]);
+ EXPECT_EQ(kAudioTrack3, updated_audio_streams[1].name); // New audio track.
+ ASSERT_EQ(1U, updated_audio_streams[1].ssrcs.size());
+ EXPECT_NE(0U, updated_audio_streams[1].ssrcs[0]);
+ EXPECT_EQ(updated_audio_streams[0].cname, updated_audio_streams[1].cname);
+
+ const StreamParamsVec& updated_video_streams = updated_vcd->streams();
+ ASSERT_EQ(2U, updated_video_streams.size());
+ EXPECT_EQ(video_streams[0], updated_video_streams[0]);
+ EXPECT_EQ(kVideoTrack2, updated_video_streams[1].name);
+ EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+}
+
+// Create an audio and video answer to a standard video offer with:
+// - one video track,
+// - two audio tracks.
+// and ensure it matches what we expect. Also updates the initial answer by
+// adding a new video track and removes one of the audio tracks.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
+ MediaSessionOptions offer_opts;
+ offer_opts.has_video = true;
+ f1_.set_secure(SEC_ENABLED);
+ f2_.set_secure(SEC_ENABLED);
+ talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(offer_opts,
+ NULL));
+
+ MediaSessionOptions opts;
+ opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+ opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+ opts.AddStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
+
+ talk_base::scoped_ptr<SessionDescription>
+ answer(f2_.CreateAnswer(offer.get(), opts, NULL));
+
+ ASSERT_TRUE(answer.get() != NULL);
+ const ContentInfo* ac = answer->GetContentByName("audio");
+ const ContentInfo* vc = answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
+ ASSERT_CRYPTO(acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+ ASSERT_CRYPTO(vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+
+ const StreamParamsVec& audio_streams = acd->streams();
+ ASSERT_EQ(2U, audio_streams.size());
+ EXPECT_TRUE(audio_streams[0].cname == audio_streams[1].cname);
+ EXPECT_EQ(kAudioTrack1, audio_streams[0].name);
+ ASSERT_EQ(1U, audio_streams[0].ssrcs.size());
+ EXPECT_NE(0U, audio_streams[0].ssrcs[0]);
+ EXPECT_EQ(kAudioTrack2, audio_streams[1].name);
+ ASSERT_EQ(1U, audio_streams[1].ssrcs.size());
+ EXPECT_NE(0U, audio_streams[1].ssrcs[0]);
+
+ EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(acd->rtcp_mux()); // rtcp-mux defaults on
+
+ EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+ EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
+
+ const StreamParamsVec& video_streams = vcd->streams();
+ ASSERT_EQ(1U, video_streams.size());
+ EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
+ EXPECT_EQ(kVideoTrack1, video_streams[0].name);
+ EXPECT_EQ(kAutoBandwidth, vcd->bandwidth()); // default bandwidth (auto)
+ EXPECT_TRUE(vcd->rtcp_mux()); // rtcp-mux defaults on
+
+ // Update the answer. Add a new video track that is not synched to the
+ // other traacks and remove 1 audio track.
+ opts.AddStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+ opts.RemoveStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+ talk_base::scoped_ptr<SessionDescription>
+ updated_answer(f2_.CreateAnswer(offer.get(), opts, answer.get()));
+
+ ASSERT_TRUE(updated_answer.get() != NULL);
+ ac = updated_answer->GetContentByName("audio");
+ vc = updated_answer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc != NULL);
+ const AudioContentDescription* updated_acd =
+ static_cast<const AudioContentDescription*>(ac->description);
+ const VideoContentDescription* updated_vcd =
+ static_cast<const VideoContentDescription*>(vc->description);
+
+ ASSERT_CRYPTO(updated_acd, false, 1U, CS_AES_CM_128_HMAC_SHA1_32);
+ EXPECT_TRUE(CompareCryptoParams(acd->cryptos(), updated_acd->cryptos()));
+ ASSERT_CRYPTO(updated_vcd, false, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+ EXPECT_TRUE(CompareCryptoParams(vcd->cryptos(), updated_vcd->cryptos()));
+
+ EXPECT_EQ(acd->type(), updated_acd->type());
+ EXPECT_EQ(acd->codecs(), updated_acd->codecs());
+ EXPECT_EQ(vcd->type(), updated_vcd->type());
+ EXPECT_EQ(vcd->codecs(), updated_vcd->codecs());
+
+ const StreamParamsVec& updated_audio_streams = updated_acd->streams();
+ ASSERT_EQ(1U, updated_audio_streams.size());
+ EXPECT_TRUE(audio_streams[0] == updated_audio_streams[0]);
+
+ const StreamParamsVec& updated_video_streams = updated_vcd->streams();
+ ASSERT_EQ(2U, updated_video_streams.size());
+ EXPECT_EQ(video_streams[0], updated_video_streams[0]);
+ EXPECT_EQ(kVideoTrack2, updated_video_streams[1].name);
+ EXPECT_NE(updated_video_streams[1].cname, updated_video_streams[0].cname);
+}
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
index 3491d26..07ccf13 100644
--- a/talk/session/phone/mediasessionclient.cc
+++ b/talk/session/phone/mediasessionclient.cc
@@ -36,6 +36,7 @@
#include "talk/p2p/base/constants.h"
#include "talk/p2p/base/parsing.h"
#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/mediamessages.h"
#include "talk/session/phone/srtpfilter.h"
#include "talk/xmpp/constants.h"
#include "talk/xmllite/qname.h"
@@ -198,6 +199,8 @@
return session;
}
+// TODO: Move all of the parsing and writing functions into
+// mediamessages.cc, with unit tests.
bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
int id = GetXmlAttr(element, QN_ID, -1);
if (id < 0)
@@ -225,26 +228,30 @@
return true;
}
-uint32 parse_ssrc(const std::string& ssrc) {
- // TODO: Return an error rather than defaulting to 0.
- uint32 default_ssrc = 0U;
- return talk_base::FromString(default_ssrc, ssrc);
+// Parses an ssrc string as a legacy stream. If it fails, returns
+// false and fills an error message.
+bool ParseSsrcAsLegacyStream(const std::string& ssrc_str,
+ std::vector<StreamParams>* streams,
+ ParseError* error) {
+ if (!ssrc_str.empty()) {
+ uint32 ssrc;
+ if (!talk_base::FromString(ssrc_str, &ssrc)) {
+ return BadParse("Missing or invalid ssrc.", error);
+ }
+
+ streams->push_back(StreamParams::CreateLegacy(ssrc));
+ }
+ return true;
}
void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
const buzz::QName& name,
- MediaContentDescription* content) {
+ MediaContentDescription* media) {
const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
if (ssrc_elem) {
- content->set_ssrc(parse_ssrc(ssrc_elem->BodyText()));
- }
-}
-
-void ParseJingleSsrc(const buzz::XmlElement* desc_elem,
- MediaContentDescription* content) {
- const std::string ssrc = desc_elem->Attr(QN_JINGLE_SSRC);
- if (!ssrc.empty()) {
- content->set_ssrc(parse_ssrc(ssrc));
+ ParseError error;
+ ParseSsrcAsLegacyStream(
+ ssrc_elem->BodyText(), &(media->mutable_streams()), &error);
}
}
@@ -304,7 +311,7 @@
void ParseBandwidth(const buzz::XmlElement* parent_elem,
MediaContentDescription* media) {
const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
- int bandwidth_kbps;
+ int bandwidth_kbps = -1;
if (bw_elem && talk_base::FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
if (bandwidth_kbps >= 0) {
media->set_bandwidth(bandwidth_kbps * 1000);
@@ -454,6 +461,23 @@
return true;
}
+bool ParseJingleStreamsOrLegacySsrc(const buzz::XmlElement* desc_elem,
+ MediaContentDescription* media,
+ ParseError* error) {
+ if (HasJingleStreams(desc_elem)) {
+ if (!ParseJingleStreams(desc_elem, &(media->mutable_streams()), error)) {
+ return false;
+ }
+ } else {
+ const std::string ssrc_str = desc_elem->Attr(QN_SSRC);
+ if (!ParseSsrcAsLegacyStream(
+ ssrc_str, &(media->mutable_streams()), error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
const ContentDescription** content,
ParseError* error) {
@@ -469,12 +493,16 @@
}
}
- ParseJingleSsrc(content_elem, audio);
+ if (!ParseJingleStreamsOrLegacySsrc(content_elem, audio, error)) {
+ return false;
+ }
if (!ParseJingleEncryption(content_elem, audio, error)) {
return false;
}
- // TODO: Figure out how to integrate SSRC into Jingle.
+
+ audio->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
*content = audio;
return true;
}
@@ -494,13 +522,17 @@
}
}
- ParseJingleSsrc(content_elem, video);
+ if (!ParseJingleStreamsOrLegacySsrc(content_elem, video, error)) {
+ return false;
+ }
ParseBandwidth(content_elem, video);
if (!ParseJingleEncryption(content_elem, video, error)) {
return false;
}
- // TODO: Figure out how to integrate SSRC into Jingle.
+
+ video->set_rtcp_mux(content_elem->FirstNamed(QN_JINGLE_RTCP_MUX) != NULL);
+
*content = video;
return true;
}
@@ -625,9 +657,9 @@
codec != audio->codecs().end(); ++codec) {
elem->AddElement(CreateGingleAudioCodecElem(*codec));
}
- if (audio->ssrc_set()) {
+ if (audio->has_ssrcs()) {
elem->AddElement(CreateGingleSsrcElem(
- QN_GINGLE_AUDIO_SRCID, audio->ssrc()));
+ QN_GINGLE_AUDIO_SRCID, audio->first_ssrc()));
}
const CryptoParamsVec& cryptos = audio->cryptos();
@@ -636,8 +668,6 @@
QN_GINGLE_AUDIO_CRYPTO_USAGE,
crypto_required));
}
-
-
return elem;
}
@@ -651,9 +681,9 @@
codec != video->codecs().end(); ++codec) {
elem->AddElement(CreateGingleVideoCodecElem(*codec));
}
- if (video->ssrc_set()) {
+ if (video->has_ssrcs()) {
elem->AddElement(CreateGingleSsrcElem(
- QN_GINGLE_VIDEO_SRCID, video->ssrc()));
+ QN_GINGLE_VIDEO_SRCID, video->first_ssrc()));
}
if (video->bandwidth() != kAutoBandwidth) {
elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
@@ -714,15 +744,19 @@
return elem;
}
-void WriteJingleSsrc(const MediaContentDescription* media,
- buzz::XmlElement* elem) {
- // TODO: Right now, we have an ssrc=0 to mean "let the
- // server choose". In Jingle, it would make the most sense to just
- // leave off the attribute. But then we can't have an ssrc of 0.
- // Once we have initiator-choosen ssrcs, we can remove this check
- // and write out ssrc=0.
- if (media->ssrc_set() && (media->ssrc() != 0)) {
- AddXmlAttr(elem, QN_JINGLE_SSRC, media->ssrc());
+void WriteLegacyJingleSsrc(const MediaContentDescription* media,
+ buzz::XmlElement* elem) {
+ if (media->has_ssrcs()) {
+ AddXmlAttr(elem, QN_SSRC, media->first_ssrc());
+ }
+}
+
+void WriteJingleStreamsOrLegacySsrc(const MediaContentDescription* media,
+ buzz::XmlElement* desc_elem) {
+ if (!media->multistream()) {
+ WriteLegacyJingleSsrc(media, desc_elem);
+ } else {
+ WriteJingleStreams(media->streams(), desc_elem);
}
}
@@ -732,7 +766,7 @@
new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
- WriteJingleSsrc(audio, elem);
+ WriteJingleStreamsOrLegacySsrc(audio, elem);
for (AudioCodecs::const_iterator codec = audio->codecs().begin();
codec != audio->codecs().end(); ++codec) {
@@ -757,7 +791,7 @@
new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
- WriteJingleSsrc(video, elem);
+ WriteJingleStreamsOrLegacySsrc(video, elem);
for (VideoCodecs::const_iterator codec = video->codecs().begin();
codec != video->codecs().end(); ++codec) {
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
index c5c455f..893e95f 100644
--- a/talk/session/phone/mediasessionclient.h
+++ b/talk/session/phone/mediasessionclient.h
@@ -98,11 +98,11 @@
}
SessionDescription* CreateOffer(const CallOptions& options) {
- return desc_factory_.CreateOffer(options);
+ return desc_factory_.CreateOffer(options, NULL);
}
SessionDescription* CreateAnswer(const SessionDescription* offer,
const CallOptions& options) {
- return desc_factory_.CreateAnswer(offer, options);
+ return desc_factory_.CreateAnswer(offer, options, NULL);
}
sigslot::signal2<Call *, Call *> SignalFocus;
diff --git a/talk/session/phone/mediasessionclient_unittest.cc b/talk/session/phone/mediasessionclient_unittest.cc
new file mode 100644
index 0000000..8232d3d
--- /dev/null
+++ b/talk/session/phone/mediasessionclient_unittest.cc
@@ -0,0 +1,2775 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/fakedevicemanager.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/constants.h"
+
+// The codecs that our FakeMediaEngine will support. Order is important, since
+// the tests check that our messages have codecs in the correct order.
+static const cricket::AudioCodec kAudioCodecs[] = {
+ cricket::AudioCodec(103, "ISAC", 16000, -1, 1, 18),
+ cricket::AudioCodec(104, "ISAC", 32000, -1, 1, 17),
+ cricket::AudioCodec(119, "ISACLC", 16000, 40000, 1, 16),
+ cricket::AudioCodec(99, "speex", 16000, 22000, 1, 15),
+ cricket::AudioCodec(97, "IPCMWB", 16000, 80000, 1, 14),
+ cricket::AudioCodec(9, "G722", 16000, 64000, 1, 13),
+ cricket::AudioCodec(102, "iLBC", 8000, 13300, 1, 12),
+ cricket::AudioCodec(98, "speex", 8000, 11000, 1, 11),
+ cricket::AudioCodec(3, "GSM", 8000, 13000, 1, 10),
+ cricket::AudioCodec(100, "EG711U", 8000, 64000, 1, 9),
+ cricket::AudioCodec(101, "EG711A", 8000, 64000, 1, 8),
+ cricket::AudioCodec(0, "PCMU", 8000, 64000, 1, 7),
+ cricket::AudioCodec(8, "PCMA", 8000, 64000, 1, 6),
+ cricket::AudioCodec(126, "CN", 32000, 0, 1, 5),
+ cricket::AudioCodec(105, "CN", 16000, 0, 1, 4),
+ cricket::AudioCodec(13, "CN", 8000, 0, 1, 3),
+ cricket::AudioCodec(117, "red", 8000, 0, 1, 2),
+ cricket::AudioCodec(106, "telephone-event", 8000, 0, 1, 1)
+};
+
+static const cricket::VideoCodec kVideoCodecs[] = {
+ cricket::VideoCodec(96, "H264-SVC", 320, 200, 30, 1)
+};
+
+const std::string kGingleCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'> " \
+ " <usage/> " \
+ " <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+// Jingle offer does not have any <usage> element.
+const std::string kJingleCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'> " \
+ " <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+
+const std::string kGingleRequiredCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+ " <usage/> " \
+ " <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+const std::string kJingleRequiredCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'> "\
+ " <rtp:crypto tag='145' crypto-suite='AES_CM_128_HMAC_SHA1_32'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='AES_CM_128_HMAC_SHA1_80'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+
+const std::string kGingleUnsupportedCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'> " \
+ " <usage/> " \
+ " <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+const std::string kJingleUnsupportedCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1'> " \
+ " <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+
+// With unsupported but with required="true"
+const std::string kGingleRequiredUnsupportedCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+ " <usage/> " \
+ " <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1'" \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/>" \
+ " <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2'" \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+const std::string kJingleRequiredUnsupportedCryptoOffer = \
+ "<rtp:encryption xmlns:rtp='urn:xmpp:jingle:apps:rtp:1' required='true'>" \
+ " <rtp:crypto tag='145' crypto-suite='NOT_SUPPORTED_1' " \
+ " key-params='inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9'/> " \
+ " <rtp:crypto tag='51' crypto-suite='NOT_SUPPORTED_2' " \
+ " key-params='inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy'/>" \
+ "</rtp:encryption> ";
+
+const std::string kGingleInitiate(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='104' name='ISAC' clockrate='32000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='119' name='ISACLC' clockrate='16000' bitrate='40000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='99' name='speex' clockrate='16000' bitrate='22000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='97' name='IPCMWB' clockrate='16000' bitrate='80000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='98' name='speex' clockrate='8000' bitrate='11000' />" \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='3' name='GSM' clockrate='8000' bitrate='13000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='100' name='EG711U' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='101' name='EG711A' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='0' name='PCMU' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='8' name='PCMA' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='126' name='CN' clockrate='32000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='105' name='CN' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='13' name='CN' clockrate='8000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='117' name='red' clockrate='8000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='106' name='telephone-event' clockrate='8000' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiate(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " <payload-type id='104' name='ISAC' clockrate='32000'/> " \
+ " <payload-type " \
+ " id='119' name='ISACLC' clockrate='16000'> " \
+ " <parameter name='bitrate' value='40000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='99' name='speex' clockrate='16000'> " \
+ " <parameter name='bitrate' value='22000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='97' name='IPCMWB' clockrate='16000'> " \
+ " <parameter name='bitrate' value='80000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='9' name='G722' clockrate='16000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='102' name='iLBC' clockrate='8000'> " \
+ " <parameter name='bitrate' value='13300'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='98' name='speex' clockrate='8000'> " \
+ " <parameter name='bitrate' value='11000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='3' name='GSM' clockrate='8000'> " \
+ " <parameter name='bitrate' value='13000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='100' name='EG711U' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='101' name='EG711A' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='0' name='PCMU' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='8' name='PCMA' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='126' name='CN' clockrate='32000' /> " \
+ " <payload-type " \
+ " id='105' name='CN' clockrate='16000' /> " \
+ " <payload-type " \
+ " id='13' name='CN' clockrate='8000' /> " \
+ " <payload-type " \
+ " id='117' name='red' clockrate='8000' /> " \
+ " <payload-type " \
+ " id='106' name='telephone-event' clockrate='8000' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate string with a different order of supported codecs.
+// Should accept the supported ones, but with our desired order.
+const std::string kGingleInitiateDifferentPreference(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='104' name='ISAC' clockrate='32000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='97' name='IPCMWB' clockrate='16000' bitrate='80000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='9' name='G722' clockrate='16000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='119' name='ISACLC' clockrate='16000' bitrate='40000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='99' name='speex' clockrate='16000' bitrate='22000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='100' name='EG711U' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='101' name='EG711A' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='0' name='PCMU' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='8' name='PCMA' clockrate='8000' bitrate='64000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='102' name='iLBC' clockrate='8000' bitrate='13300' />" \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='3' name='GSM' clockrate='8000' bitrate='13000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='98' name='speex' clockrate='8000' bitrate='11000' />" \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='126' name='CN' clockrate='32000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='105' name='CN' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='13' name='CN' clockrate='8000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='117' name='red' clockrate='8000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='106' name='telephone-event' clockrate='8000' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateDifferentPreference(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type id='104' name='ISAC' clockrate='32000'/> " \
+ " <payload-type " \
+ " id='97' name='IPCMWB' clockrate='16000'> " \
+ " <parameter name='bitrate' value='80000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='9' name='G722' clockrate='16000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='119' name='ISACLC' clockrate='16000'> " \
+ " <parameter name='bitrate' value='40000'/> " \
+ " </payload-type> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " <payload-type " \
+ " id='99' name='speex' clockrate='16000'> " \
+ " <parameter name='bitrate' value='22000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='100' name='EG711U' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='101' name='EG711A' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='0' name='PCMU' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='8' name='PCMA' clockrate='8000'> " \
+ " <parameter name='bitrate' value='64000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='102' name='iLBC' clockrate='8000'> " \
+ " <parameter name='bitrate' value='13300'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='3' name='GSM' clockrate='8000'> " \
+ " <parameter name='bitrate' value='13000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='98' name='speex' clockrate='8000'> " \
+ " <parameter name='bitrate' value='11000'/> " \
+ " </payload-type> " \
+ " <payload-type " \
+ " id='126' name='CN' clockrate='32000' /> " \
+ " <payload-type " \
+ " id='105' name='CN' clockrate='16000' /> " \
+ " <payload-type " \
+ " id='13' name='CN' clockrate='8000' /> " \
+ " <payload-type " \
+ " id='117' name='red' clockrate='8000' /> " \
+ " <payload-type " \
+ " id='106' name='telephone-event' clockrate='8000' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+const std::string kGingleVideoInitiate(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/video'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/video' " \
+ " id='99' name='H264-SVC' framerate='30' " \
+ " height='200' width='320'/> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleVideoInitiate(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " <content name='test video'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+ " <payload-type id='99' name='H264-SVC'> " \
+ " <parameter name='height' value='200'/> " \
+ " <parameter name='width' value='320'/> " \
+ " <parameter name='framerate' value='30'/> " \
+ " </payload-type> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+const std::string kJingleVideoInitiateWithBandwidth(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " <content name='test video'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+ " <payload-type id='99' name='H264-SVC'> " \
+ " <parameter name='height' value='200'/> " \
+ " <parameter name='width' value='320'/> " \
+ " <parameter name='framerate' value='30'/> " \
+ " </payload-type> " \
+ " <bandwidth type='AS'>42</bandwidth> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+const std::string kJingleVideoInitiateWithRtcpMux(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " <content name='test video'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='video'> " \
+ " <payload-type id='99' name='H264-SVC'> " \
+ " <parameter name='height' value='200'/> " \
+ " <parameter name='width' value='320'/> " \
+ " <parameter name='framerate' value='30'/> " \
+ " </payload-type> " \
+ " <rtcp-mux/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate string with a combination of supported and unsupported codecs
+// Should accept the supported ones
+const std::string kGingleInitiateSomeUnsupported(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='97' name='ASDFDS' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='102' name='1010' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='107' name='DFAS' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='100' name='EG711U' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='101' name='EG711A' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='0' name='PCMU' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='110' name=':)' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='13' name='CN' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateSomeUnsupported(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'> " \
+ " <payload-type " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type " \
+ " id='97' name='ASDFDS' /> " \
+ " <payload-type " \
+ " id='102' name='1010' /> " \
+ " <payload-type " \
+ " id='107' name='DFAS' /> " \
+ " <payload-type " \
+ " id='100' name='EG711U' /> " \
+ " <payload-type " \
+ " id='101' name='EG711A' /> " \
+ " <payload-type " \
+ " id='0' name='PCMU' /> " \
+ " <payload-type " \
+ " id='110' name=':)' /> " \
+ " <payload-type " \
+ " id='13' name='CN' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+const std::string kGingleVideoInitiateWithBandwidth(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/video'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/video' " \
+ " id='99' name='H264-SVC' framerate='30' " \
+ " height='200' width='320'/> " \
+ " <bandwidth type='AS'>42</bandwidth> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+// Initiate string without any supported codecs. Should send a reject.
+const std::string kGingleInitiateNoSupportedAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='123' name='Supercodec6000' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateNoSupportedAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type " \
+ " id='123' name='Supercodec6000' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate string without any codecs. Assumes ancient version of Cricket
+// and tries a session with ISAC and PCMU
+const std::string kGingleInitiateNoAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateNoAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// The codecs are supported, but not at the given clockrates. Should send
+// a reject.
+const std::string kGingleInitiateWrongClockrates(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='8000'/> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='97' name='IPCMWB' clockrate='1337'/> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='102' name='iLBC' clockrate='1982' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateWrongClockrates(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type " \
+ " id='103' name='ISAC' clockrate='8000'/> " \
+ " <payload-type " \
+ " id='97' name='IPCMWB' clockrate='1337'/> " \
+ " <payload-type " \
+ " id='102' name='iLBC' clockrate='1982' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// The codecs are supported, but not with the given number of channels.
+// Should send a reject.
+const std::string kGingleInitiateWrongChannels(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' channels='2'/> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='97' name='IPCMWB' channels='3'/> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateWrongChannels(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type " \
+ " id='103' name='ISAC' channels='2'/> " \
+ " <payload-type " \
+ " id='97' name='IPCMWB' channels='3'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate with a dynamic codec not using GIPS default payload id. Should
+// accept with provided payload id.
+const std::string kGingleInitiateDynamicAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='123' name='speex' clockrate='16000'/> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateDynamicAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type " \
+ " id='123' name='speex' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate string with nothing but static codec id's. Should accept.
+const std::string kGingleInitiateStaticAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='3' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='0' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='8' /> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateStaticAudioCodecs(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type id='3' /> " \
+ " <payload-type id='0' /> " \
+ " <payload-type id='8' /> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate with payload type-less codecs. Should reject.
+const std::string kGingleInitiateNoPayloadTypes(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " name='ISAC' clockrate='16000'/> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateNoPayloadTypes(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'> " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type name='ISAC' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+// Initiate with unnamed dynamic codces. Should reject.
+const std::string kGingleInitiateDynamicWithoutNames(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <session xmlns='http://www.google.com/session' type='initiate'" \
+ " id='abcdef' initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/phone'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='100' clockrate='16000'/> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleInitiateDynamicWithoutNames(
+ "<iq xmlns='jabber:client' from='me@domain.com/resource' " \
+ " to='user@domain.com/resource' type='set' id='123'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-initiate'> " \
+ " sid='abcdef' initiator='me@domain.com/resource'> " \
+ " <content name='test audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' media='audio'>" \
+ " <payload-type id='100' clockrate='16000'/> " \
+ " </description> " \
+ " <transport xmlns=\"http://www.google.com/transport/p2p\"/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+const uint32 kAudioSsrc = 4294967295U;
+const uint32 kVideoSsrc = 87654321;
+// Note that this message does not specify a session ID. It must be populated
+// before use.
+const std::string kGingleAcceptWithSsrcs(
+ "<iq xmlns='jabber:client' from='me@mydomain.com' " \
+ " to='user@domain.com/resource' type='set' id='150'> " \
+ " <session xmlns='http://www.google.com/session' type='accept' " \
+ " initiator='me@domain.com/resource'> " \
+ " <description xmlns='http://www.google.com/session/video'> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='103' name='ISAC' clockrate='16000' /> " \
+ " <payload-type xmlns='http://www.google.com/session/phone' " \
+ " id='104' name='ISAC' clockrate='32000' /> " \
+ " <src-id xmlns='http://www.google.com/session/phone'> " \
+ " 4294967295</src-id> " \
+ " <src-id>87654321</src-id> " \
+ " </description> " \
+ " </session> " \
+ "</iq> ");
+
+const std::string kJingleAcceptWithSsrcs(
+ "<iq xmlns='jabber:client' from='me@mydomain.com' " \
+ " to='user@domain.com/resource' type='set' id='150'> " \
+ " <jingle xmlns='urn:xmpp:jingle:1' action='session-accept' " \
+ " initiator='me@domain.com/resource'> " \
+ " <content name='audio'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' " \
+ " media='audio' ssrc='4294967295'> " \
+ " <payload-type id='103' name='ISAC' clockrate='16000'/> " \
+ " <payload-type id='104' name='ISAC' clockrate='32000'/> " \
+ " </description> " \
+ " <transport xmlns='http://www.google.com/transport/p2p'/> " \
+ " </content> " \
+ " <content name='video'> " \
+ " <description xmlns='urn:xmpp:jingle:apps:rtp:1' " \
+ " media='video' ssrc='87654321'> " \
+ " </description> " \
+ " <transport xmlns='http://www.google.com/transport/p2p'/> " \
+ " </content> " \
+ " </jingle> " \
+ "</iq> ");
+
+std::string JingleView(const std::string& ssrc,
+ const std::string& width,
+ const std::string& height,
+ const std::string& framerate) {
+ // We have some slightly weird whitespace formatting to make the
+ // actual XML generated match the expected XML here.
+ return \
+ "<cli:iq"
+ " to='me@mydomain.com'"
+ " type='set'"
+ " xmlns:cli='jabber:client'>"
+ "<jingle"
+ " xmlns='urn:xmpp:jingle:1'"
+ " action='session-info'"
+ " sid=''>"
+ "<view xmlns='google:jingle'"
+ " name='video'"
+ " type='static'"
+ " ssrc='" + ssrc + "'>"
+ "<params"
+ " width='" + width + "'"
+ " height='" + height + "'"
+ " framerate='" + framerate + "'"
+ " preference='0'/>"
+ "</view>"
+ "</jingle>"
+ "</cli:iq>";
+}
+
+std::string JingleStreamAdd(const std::string& content_name,
+ const std::string& nick,
+ const std::string& name,
+ const std::string& ssrc) {
+ return \
+ "<iq"
+ " xmlns='jabber:client'"
+ " from='me@mydomain.com'"
+ " to='user@domain.com/resource'"
+ " type='set'"
+ " id='150'>"
+ " <jingle"
+ " xmlns='urn:xmpp:jingle:1'"
+ " action='description-info'>"
+ " <content"
+ " xmlns='urn:xmpp:jingle:1'"
+ " name='" + content_name + "'>"
+ " <description"
+ " xmlns='urn:xmpp:jingle:apps:rtp:1'"
+ " media='" + content_name + "'>"
+ " <streams"
+ " xmlns='google:jingle'>"
+ " <stream"
+ " nick='" + nick + "'"
+ " name='" + name + "'>"
+ " <ssrc>" + ssrc + "</ssrc>"
+ " </stream>"
+ " </streams>"
+ " </description>"
+ " </content>"
+ " </jingle>"
+ "</iq>";
+}
+
+std::string JingleStreamAddWithoutSsrc(const std::string& content_name,
+ const std::string& nick,
+ const std::string& name) {
+ return \
+ "<iq"
+ " xmlns='jabber:client'"
+ " from='me@mydomain.com'"
+ " to='user@domain.com/resource'"
+ " type='set'"
+ " id='150'>"
+ " <jingle"
+ " xmlns='urn:xmpp:jingle:1'"
+ " action='description-info'>"
+ " <content"
+ " xmlns='urn:xmpp:jingle:1'"
+ " name='" + content_name + "'>"
+ " <description"
+ " xmlns='urn:xmpp:jingle:apps:rtp:1'"
+ " media='" + content_name + "'>"
+ " <streams"
+ " xmlns='google:jingle'>"
+ " <stream"
+ " nick='" + nick + "'"
+ " name='" + name + "'>"
+ " </stream>"
+ " </streams>"
+ " </description>"
+ " </content>"
+ " </jingle>"
+ "</iq>";
+}
+
+std::string JingleStreamRemove(const std::string& content_name,
+ const std::string& nick,
+ const std::string& name) {
+ return \
+ "<iq"
+ " xmlns='jabber:client'"
+ " from='me@mydomain.com'"
+ " to='user@domain.com/resource'"
+ " type='set'"
+ " id='150'>"
+ " <jingle"
+ " xmlns='urn:xmpp:jingle:1'"
+ " action='description-info'>"
+ " <content"
+ " xmlns='urn:xmpp:jingle:1'"
+ " name='" + content_name + "'>"
+ " <description"
+ " xmlns='urn:xmpp:jingle:apps:rtp:1'"
+ " media='" + content_name + "'>"
+ " <streams"
+ " xmlns='google:jingle'>"
+ " <stream"
+ " nick='" + nick + "'"
+ " name='" + name + "'/>"
+ " </streams>"
+ " </description>"
+ " </content>"
+ " </jingle>"
+ "</iq>";
+}
+
+buzz::XmlElement* CopyElement(const buzz::XmlElement* elem) {
+ return new buzz::XmlElement(*elem);
+}
+
+static std::string AddEncryption(std::string stanza, std::string encryption) {
+ std::string::size_type pos = stanza.find("</description>");
+ while (pos != std::string::npos) {
+ stanza = stanza.insert(pos, encryption);
+ pos = stanza.find("</description>", pos + encryption.length() + 1);
+ }
+ return stanza;
+}
+
+
+int IntFromJingleCodecParameter(const buzz::XmlElement* parameter,
+ const std::string& expected_name) {
+ if (parameter) {
+ const std::string& actual_name =
+ parameter->Attr(cricket::QN_PAYLOADTYPE_PARAMETER_NAME);
+
+ EXPECT_EQ(expected_name, actual_name)
+ << "wrong parameter name. Expected '"
+ << expected_name << "'. Actually '"
+ << actual_name << "'.";
+
+ return atoi(parameter->Attr(
+ cricket::QN_PAYLOADTYPE_PARAMETER_VALUE).c_str());
+ }
+ return 0;
+}
+
+// Parses and extracts payload and codec info from test XML. Since
+// that XML will be in various contents (Gingle and Jingle), we need an
+// abstract parser with one concrete implementation per XML content.
+class MediaSessionTestParser {
+ public:
+ virtual buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) = 0;
+ virtual buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) = 0;
+ virtual buzz::XmlElement* NextContent(buzz::XmlElement* content) = 0;
+ virtual buzz::XmlElement* PayloadTypeFromContent(
+ buzz::XmlElement* content) = 0;
+ virtual buzz::XmlElement* NextFromPayloadType(
+ buzz::XmlElement* payload_type) = 0;
+ virtual cricket::AudioCodec AudioCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) = 0;
+ virtual cricket::VideoCodec VideoCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) = 0;
+ virtual buzz::XmlElement* EncryptionFromContent(
+ buzz::XmlElement* content) = 0;
+ virtual buzz::XmlElement* NextFromEncryption(
+ buzz::XmlElement* encryption) = 0;
+ virtual const buzz::XmlElement* BandwidthFromContent(
+ buzz::XmlElement* content) = 0;
+ virtual const buzz::XmlElement* RtcpMuxFromContent(
+ buzz::XmlElement* content) = 0;
+ virtual bool ActionIsTerminate(const buzz::XmlElement* action) = 0;
+ virtual ~MediaSessionTestParser() {}
+};
+
+class JingleSessionTestParser : public MediaSessionTestParser {
+ public:
+ JingleSessionTestParser() : action_(NULL) {}
+
+ ~JingleSessionTestParser() {
+ delete action_;
+ }
+
+ buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+ return stanza->FirstNamed(cricket::QN_JINGLE);
+ }
+
+ buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
+ // We need to be able to use multiple contents, but the action
+ // gets deleted before we can call NextContent, so we need to
+ // stash away a copy.
+ action_ = CopyElement(action);
+ return action_->FirstNamed(cricket::QN_JINGLE_CONTENT);
+ }
+
+ buzz::XmlElement* NextContent(buzz::XmlElement* content) {
+ // For some reason, content->NextNamed(cricket::QN_JINGLE_CONTENT)
+ // doesn't work.
+ return action_->FirstNamed(cricket::QN_JINGLE_CONTENT)
+ ->NextNamed(cricket::QN_JINGLE_CONTENT);
+ }
+
+ buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
+ buzz::XmlElement* content_desc =
+ content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+ if (!content_desc)
+ return NULL;
+
+ return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
+ }
+
+ buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+ return payload_type->NextNamed(cricket::QN_JINGLE_RTP_PAYLOADTYPE);
+ }
+
+ cricket::AudioCodec AudioCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) {
+ int id = 0;
+ if (payload_type->HasAttr(cricket::QN_ID))
+ id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+ std::string name = "";
+ if (payload_type->HasAttr(cricket::QN_NAME))
+ name = payload_type->Attr(cricket::QN_NAME);
+
+ int clockrate = 0;
+ if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
+ clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());
+
+ int bitrate = IntFromJingleCodecParameter(
+ payload_type->FirstNamed(cricket::QN_PARAMETER), "bitrate");
+
+ int channels = 1;
+ if (payload_type->HasAttr(cricket::QN_CHANNELS))
+ channels = atoi(payload_type->Attr(
+ cricket::QN_CHANNELS).c_str());
+
+ return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
+ }
+
+ cricket::VideoCodec VideoCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) {
+ int id = 0;
+ if (payload_type->HasAttr(cricket::QN_ID))
+ id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+ std::string name = "";
+ if (payload_type->HasAttr(cricket::QN_NAME))
+ name = payload_type->Attr(cricket::QN_NAME);
+
+ int width = 0;
+ int height = 0;
+ int framerate = 0;
+ const buzz::XmlElement* param =
+ payload_type->FirstNamed(cricket::QN_PARAMETER);
+ if (param) {
+ width = IntFromJingleCodecParameter(param, "width");
+ param = param->NextNamed(cricket::QN_PARAMETER);
+ if (param) {
+ height = IntFromJingleCodecParameter(param, "height");
+ param = param->NextNamed(cricket::QN_PARAMETER);
+ if (param) {
+ framerate = IntFromJingleCodecParameter(param, "framerate");
+ }
+ }
+ }
+
+ return cricket::VideoCodec(id, name, width, height, framerate, 0);
+ }
+
+ bool ActionIsTerminate(const buzz::XmlElement* action) {
+ return (action->HasAttr(cricket::QN_ACTION) &&
+ action->Attr(cricket::QN_ACTION) == "session-terminate");
+ }
+
+ buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
+ buzz::XmlElement* content_desc =
+ content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+ if (!content_desc)
+ return NULL;
+
+ return content_desc->FirstNamed(cricket::QN_ENCRYPTION);
+ }
+
+ buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+ return encryption->NextNamed(cricket::QN_ENCRYPTION);
+ }
+
+ const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
+ buzz::XmlElement* content_desc =
+ content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+ if (!content_desc)
+ return NULL;
+
+ return content_desc->FirstNamed(cricket::QN_JINGLE_RTP_BANDWIDTH);
+ }
+
+ const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
+ return content->FirstNamed(cricket::QN_JINGLE_RTCP_MUX);
+ }
+
+ private:
+ buzz::XmlElement* action_;
+};
+
+class GingleSessionTestParser : public MediaSessionTestParser {
+ public:
+ GingleSessionTestParser() : found_content_count_(0) {}
+
+ buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+ return stanza->FirstNamed(cricket::QN_GINGLE_SESSION);
+ }
+
+ buzz::XmlElement* ContentFromAction(buzz::XmlElement* session) {
+ buzz::XmlElement* content =
+ session->FirstNamed(cricket::QN_GINGLE_AUDIO_CONTENT);
+ if (content == NULL)
+ content = session->FirstNamed(cricket::QN_GINGLE_VIDEO_CONTENT);
+ return content;
+ }
+
+ // Assumes contents are in order of audio, and then video.
+ buzz::XmlElement* NextContent(buzz::XmlElement* content) {
+ found_content_count_++;
+ return content;
+ }
+
+ buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* content) {
+ if (found_content_count_ > 0) {
+ return content->FirstNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
+ } else {
+ return content->FirstNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
+ }
+ }
+
+ buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+ if (found_content_count_ > 0) {
+ return payload_type->NextNamed(cricket::QN_GINGLE_VIDEO_PAYLOADTYPE);
+ } else {
+ return payload_type->NextNamed(cricket::QN_GINGLE_AUDIO_PAYLOADTYPE);
+ }
+ }
+
+ cricket::AudioCodec AudioCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) {
+ int id = 0;
+ if (payload_type->HasAttr(cricket::QN_ID))
+ id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+ std::string name;
+ if (payload_type->HasAttr(cricket::QN_NAME))
+ name = payload_type->Attr(cricket::QN_NAME);
+
+ int clockrate = 0;
+ if (payload_type->HasAttr(cricket::QN_CLOCKRATE))
+ clockrate = atoi(payload_type->Attr(cricket::QN_CLOCKRATE).c_str());
+
+ int bitrate = 0;
+ if (payload_type->HasAttr(cricket::QN_BITRATE))
+ bitrate = atoi(payload_type->Attr(cricket::QN_BITRATE).c_str());
+
+ int channels = 1;
+ if (payload_type->HasAttr(cricket::QN_CHANNELS))
+ channels = atoi(payload_type->Attr(cricket::QN_CHANNELS).c_str());
+
+ return cricket::AudioCodec(id, name, clockrate, bitrate, channels, 0);
+ }
+
+ cricket::VideoCodec VideoCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) {
+ int id = 0;
+ if (payload_type->HasAttr(cricket::QN_ID))
+ id = atoi(payload_type->Attr(cricket::QN_ID).c_str());
+
+ std::string name;
+ if (payload_type->HasAttr(cricket::QN_NAME))
+ name = payload_type->Attr(cricket::QN_NAME);
+
+ int width = 0;
+ if (payload_type->HasAttr(cricket::QN_WIDTH))
+ width = atoi(payload_type->Attr(cricket::QN_WIDTH).c_str());
+
+ int height = 0;
+ if (payload_type->HasAttr(cricket::QN_HEIGHT))
+ height = atoi(payload_type->Attr(cricket::QN_HEIGHT).c_str());
+
+ int framerate = 1;
+ if (payload_type->HasAttr(cricket::QN_FRAMERATE))
+ framerate = atoi(payload_type->Attr(cricket::QN_FRAMERATE).c_str());
+
+ return cricket::VideoCodec(id, name, width, height, framerate, 0);
+ }
+
+ buzz::XmlElement* EncryptionFromContent(
+ buzz::XmlElement* content) {
+ return content->FirstNamed(cricket::QN_ENCRYPTION);
+ }
+
+ buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+ return encryption->NextNamed(cricket::QN_ENCRYPTION);
+ }
+
+ const buzz::XmlElement* BandwidthFromContent(buzz::XmlElement* content) {
+ return content->FirstNamed(cricket::QN_GINGLE_VIDEO_BANDWIDTH);
+ }
+
+ const buzz::XmlElement* RtcpMuxFromContent(buzz::XmlElement* content) {
+ return NULL;
+ }
+
+ bool ActionIsTerminate(const buzz::XmlElement* session) {
+ return (session->HasAttr(buzz::QN_TYPE) &&
+ session->Attr(buzz::QN_TYPE) == "terminate");
+ }
+
+ int found_content_count_;
+};
+
+class MediaSessionClientTest : public sigslot::has_slots<> {
+ public:
+ explicit MediaSessionClientTest(MediaSessionTestParser* parser,
+ cricket::SignalingProtocol initial_protocol) {
+ nm_ = new talk_base::BasicNetworkManager();
+ pa_ = new cricket::BasicPortAllocator(nm_);
+ sm_ = new cricket::SessionManager(pa_, NULL);
+ fme_ = new cricket::FakeMediaEngine();
+
+ std::vector<cricket::AudioCodec>
+ audio_codecs(kAudioCodecs, kAudioCodecs + ARRAY_SIZE(kAudioCodecs));
+ fme_->SetAudioCodecs(audio_codecs);
+ std::vector<cricket::VideoCodec>
+ video_codecs(kVideoCodecs, kVideoCodecs + ARRAY_SIZE(kVideoCodecs));
+ fme_->SetVideoCodecs(video_codecs);
+
+ client_ = new cricket::MediaSessionClient(
+ buzz::Jid("user@domain.com/resource"), sm_,
+ fme_, new cricket::FakeDeviceManager());
+ client_->session_manager()->SignalOutgoingMessage.connect(
+ this, &MediaSessionClientTest::OnSendStanza);
+ client_->session_manager()->SignalSessionCreate.connect(
+ this, &MediaSessionClientTest::OnSessionCreate);
+ client_->SignalCallCreate.connect(
+ this, &MediaSessionClientTest::OnCallCreate);
+ client_->SignalCallDestroy.connect(
+ this, &MediaSessionClientTest::OnCallDestroy);
+
+ call_ = NULL;
+ parser_ = parser;
+ initial_protocol_ = initial_protocol;
+ expect_incoming_crypto_ = false;
+ expect_outgoing_crypto_ = false;
+ expected_video_bandwidth_ = cricket::kAutoBandwidth;
+ expected_video_rtcp_mux_ = false;
+ }
+
+ ~MediaSessionClientTest() {
+ delete client_;
+ delete sm_;
+ delete pa_;
+ delete nm_;
+ delete parser_;
+ }
+
+ buzz::XmlElement* ActionFromStanza(buzz::XmlElement* stanza) {
+ return parser_->ActionFromStanza(stanza);
+ }
+
+ buzz::XmlElement* ContentFromAction(buzz::XmlElement* action) {
+ return parser_->ContentFromAction(action);
+ }
+
+ buzz::XmlElement* PayloadTypeFromContent(buzz::XmlElement* payload) {
+ return parser_->PayloadTypeFromContent(payload);
+ }
+
+ buzz::XmlElement* NextFromPayloadType(buzz::XmlElement* payload_type) {
+ return parser_->NextFromPayloadType(payload_type);
+ }
+
+ buzz::XmlElement* EncryptionFromContent(buzz::XmlElement* content) {
+ return parser_->EncryptionFromContent(content);
+ }
+
+ buzz::XmlElement* NextFromEncryption(buzz::XmlElement* encryption) {
+ return parser_->NextFromEncryption(encryption);
+ }
+
+ cricket::AudioCodec AudioCodecFromPayloadType(
+ const buzz::XmlElement* payload_type) {
+ return parser_->AudioCodecFromPayloadType(payload_type);
+ }
+
+ const cricket::AudioContentDescription* GetFirstAudioContentDescription(
+ const cricket::SessionDescription* sdesc) {
+ const cricket::ContentInfo* content =
+ cricket::GetFirstAudioContent(sdesc);
+ if (content == NULL)
+ return NULL;
+ return static_cast<const cricket::AudioContentDescription*>(
+ content->description);
+ }
+
+ const cricket::VideoContentDescription* GetFirstVideoContentDescription(
+ const cricket::SessionDescription* sdesc) {
+ const cricket::ContentInfo* content =
+ cricket::GetFirstVideoContent(sdesc);
+ if (content == NULL)
+ return NULL;
+ return static_cast<const cricket::VideoContentDescription*>(
+ content->description);
+ }
+
+ void CheckCryptoFromGoodIncomingInitiate(const cricket::Session* session) {
+ ASSERT_TRUE(session != NULL);
+ const cricket::AudioContentDescription* content =
+ GetFirstAudioContentDescription(session->remote_description());
+ ASSERT_TRUE(content != NULL);
+ ASSERT_EQ(2U, content->cryptos().size());
+ ASSERT_EQ(145, content->cryptos()[0].tag);
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
+ ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
+ content->cryptos()[0].key_params);
+ ASSERT_EQ(51, content->cryptos()[1].tag);
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", content->cryptos()[1].cipher_suite);
+ ASSERT_EQ("inline:J4lfdUL8W1F7TNJKcbuygaQuA429SJy2e9JctPUy",
+ content->cryptos()[1].key_params);
+ }
+
+ void CheckCryptoForGoodOutgoingAccept(const cricket::Session* session) {
+ const cricket::AudioContentDescription* content =
+ GetFirstAudioContentDescription(session->local_description());
+ ASSERT_EQ(1U, content->cryptos().size());
+ ASSERT_EQ(145, content->cryptos()[0].tag);
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", content->cryptos()[0].cipher_suite);
+ ASSERT_EQ(47U, content->cryptos()[0].key_params.size());
+ }
+
+ void CheckBadCryptoFromIncomingInitiate(const cricket::Session* session) {
+ const cricket::AudioContentDescription* content =
+ GetFirstAudioContentDescription(session->remote_description());
+ ASSERT_EQ(1U, content->cryptos().size());
+ ASSERT_EQ(145, content->cryptos()[0].tag);
+ ASSERT_EQ("NOT_SUPPORTED", content->cryptos()[0].cipher_suite);
+ ASSERT_EQ("inline:hsWuSQJxx7przmb8HM+ZkeNcG3HezSNID7LmfDa9",
+ content->cryptos()[0].key_params);
+ }
+
+ void CheckNoCryptoForOutgoingAccept(const cricket::Session* session) {
+ const cricket::AudioContentDescription* content =
+ GetFirstAudioContentDescription(session->local_description());
+ ASSERT_TRUE(content->cryptos().empty());
+ }
+
+ void CheckVideoBandwidth(int expected_bandwidth,
+ const cricket::SessionDescription* sdesc) {
+ const cricket::VideoContentDescription* video =
+ GetFirstVideoContentDescription(sdesc);
+ if (video != NULL) {
+ ASSERT_EQ(expected_bandwidth, video->bandwidth());
+ }
+ }
+
+ void CheckVideoRtcpMux(bool expected_video_rtcp_mux,
+ const cricket::SessionDescription* sdesc) {
+ const cricket::VideoContentDescription* video =
+ GetFirstVideoContentDescription(sdesc);
+ if (video != NULL) {
+ ASSERT_EQ(expected_video_rtcp_mux, video->rtcp_mux());
+ }
+ }
+
+ void CheckAudioSsrcForIncomingAccept(const cricket::Session* session) {
+ const cricket::AudioContentDescription* audio =
+ GetFirstAudioContentDescription(session->remote_description());
+ ASSERT_TRUE(audio != NULL);
+ ASSERT_EQ(kAudioSsrc, audio->first_ssrc());
+ }
+
+ void CheckVideoSsrcForIncomingAccept(const cricket::Session* session) {
+ const cricket::VideoContentDescription* video =
+ GetFirstVideoContentDescription(session->remote_description());
+ ASSERT_TRUE(video != NULL);
+ ASSERT_EQ(kVideoSsrc, video->first_ssrc());
+ }
+
+ void TestGoodIncomingInitiate(const std::string &initiate_string,
+ buzz::XmlElement** element) {
+ *element = NULL;
+
+ buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+ client_->session_manager()->OnIncomingMessage(el);
+ ASSERT_TRUE(call_ != NULL);
+ ASSERT_TRUE(call_->sessions()[0] != NULL);
+ ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+ delete stanzas_[0];
+ stanzas_.clear();
+ CheckVideoBandwidth(expected_video_bandwidth_,
+ call_->sessions()[0]->remote_description());
+ CheckVideoRtcpMux(expected_video_rtcp_mux_,
+ call_->sessions()[0]->remote_description());
+ if (expect_incoming_crypto_) {
+ CheckCryptoFromGoodIncomingInitiate(call_->sessions()[0]);
+ }
+
+ // TODO: Add tests for sending <bandwidth> in accept.
+ cricket::CallOptions opts;
+ opts.has_video = (GetFirstVideoContentDescription(
+ call_->sessions()[0]->remote_description()) != NULL);
+ call_->AcceptSession(call_->sessions()[0], opts);
+ ASSERT_EQ(cricket::Session::STATE_SENTACCEPT,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+
+ buzz::XmlElement* e = ActionFromStanza(stanzas_[0]);
+ ASSERT_TRUE(e != NULL);
+ ASSERT_TRUE(ContentFromAction(e) != NULL);
+ *element = CopyElement(ContentFromAction(e));
+ ASSERT_TRUE(*element != NULL);
+ delete stanzas_[0];
+ stanzas_.clear();
+ if (expect_outgoing_crypto_) {
+ CheckCryptoForGoodOutgoingAccept(call_->sessions()[0]);
+ }
+
+ call_->Terminate();
+ ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+ e = ActionFromStanza(stanzas_[0]);
+ ASSERT_TRUE(e != NULL);
+ ASSERT_TRUE(parser_->ActionIsTerminate(e));
+ delete stanzas_[0];
+ stanzas_.clear();
+ }
+
+ void TestBadIncomingInitiate(const std::string& initiate_string) {
+ buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+ client_->session_manager()->OnIncomingMessage(el);
+ ASSERT_TRUE(call_ != NULL);
+ ASSERT_TRUE(call_->sessions()[0] != NULL);
+ ASSERT_EQ(cricket::Session::STATE_SENTREJECT,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(2U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[1]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[1]->Attr(buzz::QN_TYPE));
+ delete stanzas_[0];
+ stanzas_.clear();
+ }
+
+ void TestGoodOutgoingInitiate() {
+ cricket::CallOptions options;
+ TestGoodOutgoingInitiate(options);
+ }
+
+ void TestGoodOutgoingInitiate(const cricket::CallOptions& options) {
+ client_->CreateCall();
+ ASSERT_TRUE(call_ != NULL);
+ call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+ ASSERT_TRUE(call_->sessions()[0] != NULL);
+ ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+ buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+ ASSERT_TRUE(action != NULL);
+ buzz::XmlElement* content = ContentFromAction(action);
+ ASSERT_TRUE(content != NULL);
+
+ buzz::XmlElement* e = PayloadTypeFromContent(content);
+ ASSERT_TRUE(e != NULL);
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(103, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(0, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(104, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(32000, codec.clockrate);
+ ASSERT_EQ(0, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(119, codec.id);
+ ASSERT_EQ("ISACLC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(40000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(99, codec.id);
+ ASSERT_EQ("speex", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(22000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(97, codec.id);
+ ASSERT_EQ("IPCMWB", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(80000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(9, codec.id);
+ ASSERT_EQ("G722", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(102, codec.id);
+ ASSERT_EQ("iLBC", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(13300, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(98, codec.id);
+ ASSERT_EQ("speex", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(11000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(3, codec.id);
+ ASSERT_EQ("GSM", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(13000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(100, codec.id);
+ ASSERT_EQ("EG711U", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(101, codec.id);
+ ASSERT_EQ("EG711A", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(0, codec.id);
+ ASSERT_EQ("PCMU", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(8, codec.id);
+ ASSERT_EQ("PCMA", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(126, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(32000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(105, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(13, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(117, codec.id);
+ ASSERT_EQ("red", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(106, codec.id);
+ ASSERT_EQ("telephone-event", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+
+ if (expect_outgoing_crypto_) {
+ buzz::XmlElement* encryption = EncryptionFromContent(content);
+ ASSERT_TRUE(encryption != NULL);
+
+ if (client_->secure() == cricket::SEC_REQUIRED) {
+ ASSERT_TRUE(cricket::GetXmlAttr(
+ encryption, cricket::QN_ENCRYPTION_REQUIRED, false));
+ }
+
+ if (content->Name().Namespace() == cricket::NS_GINGLE_AUDIO) {
+ e = encryption->FirstNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE);
+ ASSERT_TRUE(e != NULL);
+ ASSERT_TRUE(
+ e->NextNamed(cricket::QN_GINGLE_AUDIO_CRYPTO_USAGE) == NULL);
+ ASSERT_TRUE(
+ e->FirstNamed(cricket::QN_GINGLE_VIDEO_CRYPTO_USAGE) == NULL);
+ }
+
+ e = encryption->FirstNamed(cricket::QN_CRYPTO);
+ ASSERT_TRUE(e != NULL);
+ ASSERT_EQ("0", e->Attr(cricket::QN_CRYPTO_TAG));
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_32", e->Attr(cricket::QN_CRYPTO_SUITE));
+ std::string key_0 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
+ ASSERT_EQ(47U, key_0.length());
+ ASSERT_EQ("inline:", key_0.substr(0, 7));
+
+ e = e->NextNamed(cricket::QN_CRYPTO);
+ ASSERT_TRUE(e != NULL);
+ ASSERT_EQ("1", e->Attr(cricket::QN_CRYPTO_TAG));
+ ASSERT_EQ("AES_CM_128_HMAC_SHA1_80", e->Attr(cricket::QN_CRYPTO_SUITE));
+ std::string key_1 = e->Attr(cricket::QN_CRYPTO_KEY_PARAMS);
+ ASSERT_EQ(47U, key_1.length());
+ ASSERT_EQ("inline:", key_1.substr(0, 7));
+ ASSERT_NE(key_0, key_1);
+
+ encryption = NextFromEncryption(encryption);
+ ASSERT_TRUE(encryption == NULL);
+ }
+
+ if (options.has_video) {
+ CheckVideoBandwidth(options.video_bandwidth,
+ call_->sessions()[0]->local_description());
+ CheckVideoRtcpMux(expected_video_rtcp_mux_,
+ call_->sessions()[0]->remote_description());
+ content = parser_->NextContent(content);
+ const buzz::XmlElement* bandwidth =
+ parser_->BandwidthFromContent(content);
+ if (options.video_bandwidth == cricket::kAutoBandwidth) {
+ ASSERT_TRUE(bandwidth == NULL);
+ } else {
+ ASSERT_TRUE(bandwidth != NULL);
+ ASSERT_EQ("AS", bandwidth->Attr(buzz::QName("", "type")));
+ ASSERT_EQ(talk_base::ToString(options.video_bandwidth / 1000),
+ bandwidth->BodyText());
+ }
+ }
+
+ delete stanzas_[0];
+ stanzas_.clear();
+ }
+
+ void TestHasAllSupportedAudioCodecs(buzz::XmlElement* e) {
+ ASSERT_TRUE(e != NULL);
+
+ e = PayloadTypeFromContent(e);
+ ASSERT_TRUE(e != NULL);
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(103, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(104, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(32000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(119, codec.id);
+ ASSERT_EQ("ISACLC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(40000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(99, codec.id);
+ ASSERT_EQ("speex", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(22000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(97, codec.id);
+ ASSERT_EQ("IPCMWB", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(80000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(9, codec.id);
+ ASSERT_EQ("G722", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(102, codec.id);
+ ASSERT_EQ("iLBC", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(13300, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(98, codec.id);
+ ASSERT_EQ("speex", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(11000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(3, codec.id);
+ ASSERT_EQ("GSM", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(13000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(100, codec.id);
+ ASSERT_EQ("EG711U", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(101, codec.id);
+ ASSERT_EQ("EG711A", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(0, codec.id);
+ ASSERT_EQ("PCMU", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(8, codec.id);
+ ASSERT_EQ("PCMA", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(64000, codec.bitrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(126, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(32000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(105, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(13, codec.id);
+ ASSERT_EQ("CN", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(117, codec.id);
+ ASSERT_EQ("red", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(106, codec.id);
+ ASSERT_EQ("telephone-event", codec.name);
+ ASSERT_EQ(8000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+ }
+
+ void TestCodecsOfVideoInitiate(buzz::XmlElement* content) {
+ ASSERT_TRUE(content != NULL);
+ buzz::XmlElement* payload_type = PayloadTypeFromContent(content);
+ ASSERT_TRUE(payload_type != NULL);
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(payload_type);
+ ASSERT_EQ(103, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ content = parser_->NextContent(content);
+ ASSERT_TRUE(content != NULL);
+ payload_type = PayloadTypeFromContent(content);
+ ASSERT_TRUE(payload_type != NULL);
+ cricket::VideoCodec vcodec =
+ parser_->VideoCodecFromPayloadType(payload_type);
+ ASSERT_EQ(99, vcodec.id);
+ ASSERT_EQ("H264-SVC", vcodec.name);
+ ASSERT_EQ(320, vcodec.width);
+ ASSERT_EQ(200, vcodec.height);
+ ASSERT_EQ(30, vcodec.framerate);
+ }
+
+ void TestHasAudioCodecsFromInitiateSomeUnsupported(buzz::XmlElement* e) {
+ ASSERT_TRUE(e != NULL);
+ e = PayloadTypeFromContent(e);
+ ASSERT_TRUE(e != NULL);
+
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(103, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(100, codec.id);
+ ASSERT_EQ("EG711U", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(101, codec.id);
+ ASSERT_EQ("EG711A", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(0, codec.id);
+ ASSERT_EQ("PCMU", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(13, codec.id);
+ ASSERT_EQ("CN", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+ }
+
+ void TestHasAudioCodecsFromInitiateDynamicAudioCodecs(
+ buzz::XmlElement* e) {
+ ASSERT_TRUE(e != NULL);
+ e = PayloadTypeFromContent(e);
+ ASSERT_TRUE(e != NULL);
+
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(123, codec.id);
+ ASSERT_EQ(16000, codec.clockrate);
+ ASSERT_EQ(1, codec.channels);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+ }
+
+ void TestHasDefaultAudioCodecs(buzz::XmlElement* e) {
+ ASSERT_TRUE(e != NULL);
+ e = PayloadTypeFromContent(e);
+ ASSERT_TRUE(e != NULL);
+
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(103, codec.id);
+ ASSERT_EQ("ISAC", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(0, codec.id);
+ ASSERT_EQ("PCMU", codec.name);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+ }
+
+ void TestHasAudioCodecsFromInitiateStaticAudioCodecs(
+ buzz::XmlElement* e) {
+ ASSERT_TRUE(e != NULL);
+ e = PayloadTypeFromContent(e);
+ ASSERT_TRUE(e != NULL);
+
+ cricket::AudioCodec codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(3, codec.id);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(0, codec.id);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e != NULL);
+ codec = AudioCodecFromPayloadType(e);
+ ASSERT_EQ(8, codec.id);
+
+ e = NextFromPayloadType(e);
+ ASSERT_TRUE(e == NULL);
+ }
+
+ void TestGingleInitiateWithUnsupportedCrypto(
+ const std::string &initiate_string,
+ buzz::XmlElement** element) {
+ *element = NULL;
+
+ buzz::XmlElement* el = buzz::XmlElement::ForStr(initiate_string);
+ client_->session_manager()->OnIncomingMessage(el);
+
+ ASSERT_EQ(cricket::Session::STATE_RECEIVEDINITIATE,
+ call_->sessions()[0]->state());
+ delete stanzas_[0];
+ stanzas_.clear();
+ CheckBadCryptoFromIncomingInitiate(call_->sessions()[0]);
+
+ call_->AcceptSession(call_->sessions()[0], cricket::CallOptions());
+ delete stanzas_[0];
+ stanzas_.clear();
+ CheckNoCryptoForOutgoingAccept(call_->sessions()[0]);
+
+ call_->Terminate();
+ ASSERT_EQ(cricket::Session::STATE_SENTTERMINATE,
+ call_->sessions()[0]->state());
+ delete stanzas_[0];
+ stanzas_.clear();
+ }
+
+ void TestIncomingAcceptWithSsrcs(const std::string& accept_string) {
+ client_->CreateCall();
+ ASSERT_TRUE(call_ != NULL);
+
+ cricket::CallOptions options;
+ options.has_video = true;
+ options.is_muc = true;
+
+ call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+ ASSERT_TRUE(call_->sessions()[0] != NULL);
+ ASSERT_EQ(cricket::Session::STATE_SENTINITIATE,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_SET), stanzas_[0]->Attr(buzz::QN_TYPE));
+ buzz::XmlElement* action = ActionFromStanza(stanzas_[0]);
+ ASSERT_TRUE(action != NULL);
+ buzz::XmlElement* content = ContentFromAction(action);
+ ASSERT_TRUE(content != NULL);
+ if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
+ buzz::XmlElement* content_desc =
+ content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
+ ASSERT_TRUE(content_desc != NULL);
+ ASSERT_EQ("", content_desc->Attr(cricket::QN_SSRC));
+ }
+ delete stanzas_[0];
+ stanzas_.clear();
+
+ // We need to insert the session ID into the session accept message.
+ buzz::XmlElement* el = buzz::XmlElement::ForStr(accept_string);
+ const std::string sid = call_->sessions()[0]->id();
+ if (initial_protocol_ == cricket::PROTOCOL_JINGLE) {
+ buzz::XmlElement* jingle = el->FirstNamed(cricket::QN_JINGLE);
+ jingle->SetAttr(cricket::QN_SID, sid);
+ } else {
+ buzz::XmlElement* session = el->FirstNamed(cricket::QN_GINGLE_SESSION);
+ session->SetAttr(cricket::QN_ID, sid);
+ }
+
+ client_->session_manager()->OnIncomingMessage(el);
+
+ ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_TRUE(buzz::QN_IQ == stanzas_[0]->Name());
+ ASSERT_TRUE(stanzas_[0]->HasAttr(buzz::QN_TYPE));
+ ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+ delete stanzas_[0];
+ stanzas_.clear();
+
+ CheckAudioSsrcForIncomingAccept(call_->sessions()[0]);
+ CheckVideoSsrcForIncomingAccept(call_->sessions()[0]);
+ }
+
+ size_t ClearStanzas() {
+ size_t size = stanzas_.size();
+ for (size_t i = 0; i < size; i++) {
+ delete stanzas_[i];
+ }
+ stanzas_.clear();
+ return size;
+ }
+
+ void SetJingleSid(buzz::XmlElement* stanza) {
+ buzz::XmlElement* jingle =
+ stanza->FirstNamed(cricket::QN_JINGLE);
+ jingle->SetAttr(cricket::QN_SID, call_->sessions()[0]->id());
+ }
+
+ void TestStreamsUpdateAndViewRequests() {
+ cricket::CallOptions options;
+ options.has_video = true;
+ options.is_muc = true;
+
+ client_->CreateCall();
+ call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
+ ASSERT_EQ(1U, ClearStanzas());
+ ASSERT_EQ(0U, last_streams_added_.audio().size());
+ ASSERT_EQ(0U, last_streams_added_.video().size());
+ ASSERT_EQ(0U, last_streams_removed_.audio().size());
+ ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+ talk_base::scoped_ptr<buzz::XmlElement> accept_stanza(
+ buzz::XmlElement::ForStr(kJingleAcceptWithSsrcs));
+ SetJingleSid(accept_stanza.get());
+ client_->session_manager()->OnIncomingMessage(accept_stanza.get());
+ ASSERT_EQ(cricket::Session::STATE_RECEIVEDACCEPT,
+ call_->sessions()[0]->state());
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+ ClearStanzas();
+ call_->sessions()[0]->SetState(cricket::Session::STATE_INPROGRESS);
+
+ talk_base::scoped_ptr<buzz::XmlElement> streams_stanza(
+ buzz::XmlElement::ForStr(
+ JingleStreamAdd("video", "Bob", "video1", "ABC")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ // First one is ignored because of bad syntax.
+ ASSERT_EQ(1U, stanzas_.size());
+ // TODO: Figure out how to make this an ERROR rather than RESULT.
+ ASSERT_EQ(std::string(buzz::STR_ERROR), stanzas_[0]->Attr(buzz::QN_TYPE));
+ ClearStanzas();
+ ASSERT_EQ(0U, last_streams_added_.audio().size());
+ ASSERT_EQ(0U, last_streams_added_.video().size());
+ ASSERT_EQ(0U, last_streams_removed_.audio().size());
+ ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAdd("audio", "Bob", "audio1", "1234")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_added_.audio().size());
+ ASSERT_EQ("Bob", last_streams_added_.audio()[0].nick);
+ ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+ ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+ // Ignores adds without ssrcs.
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAddWithoutSsrc("audio", "Bob", "audioX")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_added_.audio().size());
+ ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
+
+ // Ignores stream updates with unknown content names. (Don't terminate).
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAddWithoutSsrc("foo", "Bob", "foo")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAdd("audio", "Joe", "audio1", "2468")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_added_.audio().size());
+ ASSERT_EQ("Joe", last_streams_added_.audio()[0].nick);
+ ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+ ASSERT_EQ(2468U, last_streams_added_.audio()[0].first_ssrc());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAdd("video", "Bob", "video1", "5678")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_added_.video().size());
+ ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+ ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+ ASSERT_EQ(5678U, last_streams_added_.video()[0].first_ssrc());
+
+ // We're testing that a "duplicate" is effectively ignored.
+ last_streams_added_.mutable_video()->clear();
+ last_streams_removed_.mutable_video()->clear();
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAdd("video", "Bob", "video1", "5678")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(0U, last_streams_added_.video().size());
+ ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamAdd("video", "Bob", "video2", "5679")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_added_.video().size());
+ ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+ ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+ ASSERT_EQ(5679U, last_streams_added_.video()[0].first_ssrc());
+
+ cricket::FakeVoiceMediaChannel* voice_channel = fme_->GetVoiceChannel(0);
+ ASSERT_TRUE(voice_channel != NULL);
+ ASSERT_TRUE(voice_channel->HasRecvStream(1234U));
+ ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
+ cricket::FakeVideoMediaChannel* video_channel = fme_->GetVideoChannel(0);
+ ASSERT_TRUE(video_channel != NULL);
+ ASSERT_TRUE(video_channel->HasRecvStream(5678U));
+ ClearStanzas();
+
+ cricket::ViewRequest viewRequest;
+ cricket::StaticVideoView staticVideoView(5678U, 640, 480, 30);
+ viewRequest.static_video_views.push_back(staticVideoView);
+ talk_base::scoped_ptr<buzz::XmlElement> expected_view_elem(
+ buzz::XmlElement::ForStr(JingleView("5678", "640", "480", "30")));
+ SetJingleSid(expected_view_elem.get());
+
+ ASSERT_TRUE(
+ call_->SendViewRequest(call_->sessions()[0], viewRequest));
+ ASSERT_EQ(1U, stanzas_.size());
+ ASSERT_EQ(expected_view_elem->Str(), stanzas_[0]->Str());
+ ClearStanzas();
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamRemove("audio", "Bob", "audio1")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_removed_.audio().size());
+ ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
+ EXPECT_EQ(1234U, last_streams_removed_.audio()[0].first_ssrc());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamRemove("video", "Bob", "video1")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_removed_.video().size());
+ ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
+ EXPECT_EQ(5678U, last_streams_removed_.video()[0].first_ssrc());
+
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamRemove("video", "Bob", "video2")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(1U, last_streams_removed_.video().size());
+ ASSERT_EQ(1U, last_streams_removed_.video()[0].ssrcs.size());
+ EXPECT_EQ(5679U, last_streams_removed_.video()[0].first_ssrc());
+
+ // Duplicate removal: should be ignored.
+ last_streams_removed_.mutable_audio()->clear();
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamRemove("audio", "Bob", "audio1")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(0U, last_streams_removed_.audio().size());
+
+ // Duplicate removal: should be ignored.
+ last_streams_removed_.mutable_video()->clear();
+ streams_stanza.reset(buzz::XmlElement::ForStr(
+ JingleStreamRemove("video", "Bob", "video1")));
+ SetJingleSid(streams_stanza.get());
+ client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+ ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+ voice_channel = fme_->GetVoiceChannel(0);
+ ASSERT_TRUE(voice_channel != NULL);
+ ASSERT_FALSE(voice_channel->HasRecvStream(1234U));
+ ASSERT_TRUE(voice_channel->HasRecvStream(2468U));
+ video_channel = fme_->GetVideoChannel(0);
+ ASSERT_TRUE(video_channel != NULL);
+ ASSERT_FALSE(video_channel->HasRecvStream(5678U));
+
+ // Fails because ssrc is now invalid.
+ ASSERT_FALSE(
+ call_->SendViewRequest(call_->sessions()[0], viewRequest));
+
+ ClearStanzas();
+ }
+
+ void MakeSignalingSecure(cricket::SecureMediaPolicy secure) {
+ client_->set_secure(secure);
+ }
+
+ void ExpectCrypto(cricket::SecureMediaPolicy secure) {
+ MakeSignalingSecure(secure);
+ expect_incoming_crypto_ = true;
+#ifdef HAVE_SRTP
+ expect_outgoing_crypto_ = true;
+#endif
+ }
+
+ void ExpectVideoBandwidth(int bandwidth) {
+ expected_video_bandwidth_ = bandwidth;
+ }
+
+ void ExpectVideoRtcpMux(bool rtcp_mux) {
+ expected_video_rtcp_mux_ = rtcp_mux;
+ }
+
+ private:
+ void OnSendStanza(cricket::SessionManager* manager,
+ const buzz::XmlElement* stanza) {
+ LOG(LS_INFO) << stanza->Str();
+ stanzas_.push_back(new buzz::XmlElement(*stanza));
+ }
+
+ void OnSessionCreate(cricket::Session* session, bool initiate) {
+ session->set_current_protocol(initial_protocol_);
+ }
+
+ void OnCallCreate(cricket::Call *call) {
+ call_ = call;
+ call->SignalMediaStreamsUpdate.connect(
+ this, &MediaSessionClientTest::OnMediaStreamsUpdate);
+ }
+
+ void OnCallDestroy(cricket::Call *call) {
+ call_ = NULL;
+ }
+
+ void OnMediaStreamsUpdate(cricket::Call *call,
+ cricket::Session *session,
+ const cricket::MediaStreams& added,
+ const cricket::MediaStreams& removed) {
+ last_streams_added_.CopyFrom(added);
+ last_streams_removed_.CopyFrom(removed);
+ }
+
+ talk_base::NetworkManager* nm_;
+ cricket::PortAllocator* pa_;
+ cricket::SessionManager* sm_;
+ cricket::FakeMediaEngine* fme_;
+ cricket::MediaSessionClient* client_;
+
+ cricket::Call* call_;
+ std::vector<buzz::XmlElement* > stanzas_;
+ MediaSessionTestParser* parser_;
+ cricket::SignalingProtocol initial_protocol_;
+ bool expect_incoming_crypto_;
+ bool expect_outgoing_crypto_;
+ int expected_video_bandwidth_;
+ bool expected_video_rtcp_mux_;
+ cricket::MediaStreams last_streams_added_;
+ cricket::MediaStreams last_streams_removed_;
+};
+
+MediaSessionClientTest* GingleTest() {
+ return new MediaSessionClientTest(new GingleSessionTestParser(),
+ cricket::PROTOCOL_GINGLE);
+}
+
+MediaSessionClientTest* JingleTest() {
+ return new MediaSessionClientTest(new JingleSessionTestParser(),
+ cricket::PROTOCOL_JINGLE);
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiate) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleVideoInitiate, elem.use());
+ test->TestCodecsOfVideoInitiate(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithBandwidth) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->ExpectVideoBandwidth(42000);
+ test->TestGoodIncomingInitiate(kJingleVideoInitiateWithBandwidth, elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodVideoInitiateWithRtcpMux) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->ExpectVideoRtcpMux(true);
+ test->TestGoodIncomingInitiate(kJingleVideoInitiateWithRtcpMux, elem.use());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateAllSupportedAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleInitiate, elem.use());
+ test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateDifferentPreferenceAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleInitiateDifferentPreference,
+ elem.use());
+ test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateSomeUnsupportedAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleInitiateSomeUnsupported, elem.use());
+ test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateDynamicAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleInitiateDynamicAudioCodecs, elem.use());
+ test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleGoodInitiateStaticAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(kJingleInitiateStaticAudioCodecs, elem.use());
+ test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateNoAudioCodecs);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoSupportedAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateWrongClockrates) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateWrongClockrates);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateWrongChannels) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateWrongChannels);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateNoPayloadTypes) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateNoPayloadTypes);
+}
+
+TEST(MediaSessionTest, JingleBadInitiateDynamicWithoutNames) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(kJingleInitiateDynamicWithoutNames);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiate) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithBandwidth) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ cricket::CallOptions options;
+ options.has_video = true;
+ options.video_bandwidth = 42000;
+ test->TestGoodOutgoingInitiate(options);
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithRtcpMux) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ cricket::CallOptions options;
+ options.has_video = true;
+ options.rtcp_mux_enabled = true;
+ test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, JingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+ kJingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithCryptoRequiredWhenNotSecured) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+ kJingleRequiredCryptoOffer));
+}
+
+// Offer has no crypto but the session is secure required, fail.
+TEST(MediaSessionTest, JingleInitiateWithNoCryptoFailsWhenSecureRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestBadIncomingInitiate(kJingleInitiate);
+}
+
+// Offer has crypto and session is secure, expect crypto in the answer.
+TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureEnabled) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->ExpectCrypto(cricket::SEC_ENABLED);
+ test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+ kJingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto and session is secure required, expect crypto in
+// the answer.
+TEST(MediaSessionTest, JingleInitiateWithCryptoWhenSecureRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestGoodIncomingInitiate(AddEncryption(kJingleVideoInitiate,
+ kJingleCryptoOffer), elem.use());
+}
+
+// Offer has unsupported crypto and session is secure, no crypto in
+// the answer.
+TEST(MediaSessionTest, JingleInitiateWithUnsupportedCrypto) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ test->MakeSignalingSecure(cricket::SEC_ENABLED);
+ test->TestGoodIncomingInitiate(
+ AddEncryption(kJingleInitiate, kJingleUnsupportedCryptoOffer),
+ elem.use());
+}
+
+// Offer has unsupported REQUIRED crypto and session is not secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCrypto) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestBadIncomingInitiate(
+ AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST(MediaSessionTest, JingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->MakeSignalingSecure(cricket::SEC_ENABLED);
+ test->TestBadIncomingInitiate(
+ AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is required secure, fail.
+TEST(MediaSessionTest,
+ JingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+ test->TestBadIncomingInitiate(
+ AddEncryption(kJingleInitiate, kJingleRequiredUnsupportedCryptoOffer));
+}
+
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCrypto) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->ExpectCrypto(cricket::SEC_ENABLED);
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleGoodOutgoingInitiateWithCryptoRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, JingleIncomingAcceptWithSsrcs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs);
+}
+
+TEST(MediaSessionTest, JingleStreamsUpdateAndView) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
+ test->TestStreamsUpdateAndViewRequests();
+}
+
+// Gingle tests
+
+TEST(MediaSessionTest, GingleGoodVideoInitiate) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleVideoInitiate, elem.use());
+ test->TestCodecsOfVideoInitiate(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodVideoInitiateWithBandwidth) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectVideoBandwidth(42000);
+ test->TestGoodIncomingInitiate(kGingleVideoInitiateWithBandwidth, elem.use());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiate, elem.use());
+ test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateAllSupportedAudioCodecsWithCrypto) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_ENABLED);
+ test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+ kGingleCryptoOffer), elem.use());
+ test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateDifferentPreferenceAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiateDifferentPreference,
+ elem.use());
+ test->TestHasAllSupportedAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateSomeUnsupportedAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiateSomeUnsupported, elem.use());
+ test->TestHasAudioCodecsFromInitiateSomeUnsupported(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateDynamicAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiateDynamicAudioCodecs, elem.use());
+ test->TestHasAudioCodecsFromInitiateDynamicAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateStaticAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiateStaticAudioCodecs, elem.use());
+ test->TestHasAudioCodecsFromInitiateStaticAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleGoodInitiateNoAudioCodecs) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(kGingleInitiateNoAudioCodecs, elem.use());
+ test->TestHasDefaultAudioCodecs(elem.get());
+}
+
+TEST(MediaSessionTest, GingleBadInitiateNoSupportedAudioCodecs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(kGingleInitiateNoSupportedAudioCodecs);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateWrongClockrates) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(kGingleInitiateWrongClockrates);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateWrongChannels) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(kGingleInitiateWrongChannels);
+}
+
+
+TEST(MediaSessionTest, GingleBadInitiateNoPayloadTypes) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(kGingleInitiateNoPayloadTypes);
+}
+
+TEST(MediaSessionTest, GingleBadInitiateDynamicWithoutNames) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(kGingleInitiateDynamicWithoutNames);
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiate) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithBandwidth) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ cricket::CallOptions options;
+ options.has_video = true;
+ options.video_bandwidth = 42000;
+ test->TestGoodOutgoingInitiate(options);
+}
+
+// Crypto related tests.
+
+// Offer has crypto but the session is not secured, just ignore it.
+TEST(MediaSessionTest, GingleInitiateWithCryptoIsIgnoredWhenNotSecured) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+ kGingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto required but the session is not secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithCryptoRequiredWhenNotSecured) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(AddEncryption(kGingleInitiate,
+ kGingleRequiredCryptoOffer));
+}
+
+// Offer has no crypto but the session is secure required, fail.
+TEST(MediaSessionTest, GingleInitiateWithNoCryptoFailsWhenSecureRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestBadIncomingInitiate(kGingleInitiate);
+}
+
+// Offer has crypto and session is secure, expect crypto in the answer.
+TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureEnabled) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_ENABLED);
+ test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+ kGingleCryptoOffer), elem.use());
+}
+
+// Offer has crypto and session is secure required, expect crypto in
+// the answer.
+TEST(MediaSessionTest, GingleInitiateWithCryptoWhenSecureRequired) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestGoodIncomingInitiate(AddEncryption(kGingleInitiate,
+ kGingleCryptoOffer), elem.use());
+}
+
+// Offer has unsupported crypto and session is secure, no crypto in
+// the answer.
+TEST(MediaSessionTest, GingleInitiateWithUnsupportedCrypto) {
+ talk_base::scoped_ptr<buzz::XmlElement> elem;
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->MakeSignalingSecure(cricket::SEC_ENABLED);
+ test->TestGoodIncomingInitiate(
+ AddEncryption(kGingleInitiate, kGingleUnsupportedCryptoOffer),
+ elem.use());
+}
+
+// Offer has unsupported REQUIRED crypto and session is not secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCrypto) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestBadIncomingInitiate(
+ AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is secure, fail.
+TEST(MediaSessionTest, GingleInitiateWithRequiredUnsupportedCryptoWhenSecure) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->MakeSignalingSecure(cricket::SEC_ENABLED);
+ test->TestBadIncomingInitiate(
+ AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+// Offer has unsupported REQUIRED crypto and session is required secure, fail.
+TEST(MediaSessionTest,
+ GingleInitiateWithRequiredUnsupportedCryptoWhenSecureRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->MakeSignalingSecure(cricket::SEC_REQUIRED);
+ test->TestBadIncomingInitiate(
+ AddEncryption(kGingleInitiate, kGingleRequiredUnsupportedCryptoOffer));
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCrypto) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_ENABLED);
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleGoodOutgoingInitiateWithCryptoRequired) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->ExpectCrypto(cricket::SEC_REQUIRED);
+ test->TestGoodOutgoingInitiate();
+}
+
+TEST(MediaSessionTest, GingleIncomingAcceptWithSsrcs) {
+ talk_base::scoped_ptr<MediaSessionClientTest> test(GingleTest());
+ test->TestIncomingAcceptWithSsrcs(kGingleAcceptWithSsrcs);
+}
diff --git a/talk/session/phone/nullvideorenderer.h b/talk/session/phone/nullvideorenderer.h
new file mode 100644
index 0000000..aa48205
--- /dev/null
+++ b/talk/session/phone/nullvideorenderer.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
+#define TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
+
+#include "talk/session/phone/videorenderer.h"
+
+namespace cricket {
+
+// Simple implementation for use in tests.
+class NullVideoRenderer : public VideoRenderer {
+ virtual bool SetSize(int width, int height, int reserved) {
+ return true;
+ }
+ // Called when a new frame is available for display.
+ virtual bool RenderFrame(const VideoFrame *frame) {
+ return true;
+ }
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_NULLVIDEORENDERER_H_
diff --git a/talk/session/phone/rtcpmuxfilter.cc b/talk/session/phone/rtcpmuxfilter.cc
index 8654214..1ca71e2 100644
--- a/talk/session/phone/rtcpmuxfilter.cc
+++ b/talk/session/phone/rtcpmuxfilter.cc
@@ -35,21 +35,22 @@
}
bool RtcpMuxFilter::IsActive() const {
- // We can receive muxed media prior to the accept, so we have to be able to
- // deal with that.
- return (state_ == ST_SENTOFFER || state_ == ST_ACTIVE);
+ return state_ == ST_ACTIVE;
}
bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource source) {
- bool ret = false;
- if (state_ == ST_INIT) {
- offer_enable_ = offer_enable;
- state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
- ret = true;
- } else {
- LOG(LS_ERROR) << "Invalid state for RTCP mux offer";
+ // Allow to SetOffer in |state_| ST_INIT. Also allow to call SetOffer in
+ // |state_| ST_ACTIVE if |offer_enable| is true.
+ // We can't disable the filter once it has been activated.
+ if ((state_ != ST_INIT && state_ != ST_ACTIVE) ||
+ (state_ == ST_ACTIVE && offer_enable == false)) {
+ LOG(LS_ERROR) << "Invalid state for change of RTCP mux offer";
+ return false;
}
- return ret;
+
+ offer_enable_ = offer_enable;
+ state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+ return true;
}
bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource source) {
@@ -68,6 +69,9 @@
LOG(LS_WARNING) << "Invalid parameters in RTCP mux answer";
}
}
+ } else if (state_ == ST_ACTIVE && answer_enable == offer_enable_) {
+ // It is ok as long as the answer state matches the current filter state.
+ ret = true;
} else {
LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
}
@@ -81,7 +85,7 @@
// http://tools.ietf.org/html/rfc5761.
// Note that if we offer RTCP mux, we may receive muxed RTCP before we
// receive the answer, so we operate in that state too.
- if (!IsActive()) {
+ if (state_ != ST_SENTOFFER && state_ != ST_ACTIVE) {
return false;
}
diff --git a/talk/session/phone/rtcpmuxfilter_unittest.cc b/talk/session/phone/rtcpmuxfilter_unittest.cc
new file mode 100644
index 0000000..6a4c0e3
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter_unittest.cc
@@ -0,0 +1,128 @@
+// libjingle
+// Copyright 2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/session/phone/rtcpmuxfilter.h"
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/testutils.h"
+
+TEST(RtcpMuxFilterTest, DemuxRtcpSender) {
+ cricket::RtcpMuxFilter filter;
+ const char data[] = { 0, 73, 0, 0 };
+ const int len = 4;
+
+ // Init state - refuse to demux
+ EXPECT_FALSE(filter.DemuxRtcp(data, len));
+ // After sent offer, demux should be enabled
+ filter.SetOffer(true, cricket::CS_LOCAL);
+ EXPECT_TRUE(filter.DemuxRtcp(data, len));
+ // Remote accepted, demux should be enabled
+ filter.SetAnswer(true, cricket::CS_REMOTE);
+ EXPECT_TRUE(filter.DemuxRtcp(data, len));
+}
+
+TEST(RtcpMuxFilterTest, DemuxRtcpReceiver) {
+ cricket::RtcpMuxFilter filter;
+ const char data[] = { 0, 73, 0, 0 };
+ const int len = 4;
+
+ // Init state - refuse to demux
+ EXPECT_FALSE(filter.DemuxRtcp(data, len));
+ // After received offer, demux should not be enabled
+ filter.SetOffer(true, cricket::CS_REMOTE);
+ EXPECT_FALSE(filter.DemuxRtcp(data, len));
+ // We accept, demux is now enabled.
+ filter.SetAnswer(true, cricket::CS_LOCAL);
+ EXPECT_TRUE(filter.DemuxRtcp(data, len));
+}
+
+TEST(RtcpMuxFilterTest, IsActiveSender) {
+ cricket::RtcpMuxFilter filter;
+ // Init state - not active.
+ EXPECT_FALSE(filter.IsActive());
+ // After sent offer, demux should not be active
+ filter.SetOffer(true, cricket::CS_LOCAL);
+ EXPECT_FALSE(filter.IsActive());
+ // Remote accepted, filter is now active
+ filter.SetAnswer(true, cricket::CS_REMOTE);
+ EXPECT_TRUE(filter.IsActive());
+}
+
+TEST(RtcpMuxFilterTest, IsActiveReceiver) {
+ cricket::RtcpMuxFilter filter;
+ // Init state - not active.
+ EXPECT_FALSE(filter.IsActive());
+ // After received offer, demux should not be active
+ filter.SetOffer(true, cricket::CS_REMOTE);
+ EXPECT_FALSE(filter.IsActive());
+ // We accept, filter is now active
+ filter.SetAnswer(true, cricket::CS_LOCAL);
+ EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that we can enable the filter in an update.
+// We can not disable the filter later since that would mean we need to
+// recreate a rtcp transport channel.
+TEST(RtcpMuxFilterTest, EnableFilterDuringUpdate) {
+ cricket::RtcpMuxFilter filter;
+ EXPECT_FALSE(filter.IsActive());
+ EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+ EXPECT_FALSE(filter.IsActive());
+
+ EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+ EXPECT_TRUE(filter.IsActive());
+
+ EXPECT_FALSE(filter.SetOffer(false, cricket::CS_REMOTE));
+ EXPECT_FALSE(filter.SetAnswer(false, cricket::CS_LOCAL));
+ EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that the filter can be enabled twice.
+TEST(RtcpMuxFilterTest, EnableFilterTwiceDuringUpdate) {
+ cricket::RtcpMuxFilter filter;
+
+ EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+ EXPECT_TRUE(filter.IsActive());
+
+ EXPECT_TRUE(filter.SetOffer(true, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(true, cricket::CS_LOCAL));
+ EXPECT_TRUE(filter.IsActive());
+}
+
+// Test that the filter can be kept disabled during updates.
+TEST(RtcpMuxFilterTest, KeepFilterDisabledDuringUpdate) {
+ cricket::RtcpMuxFilter filter;
+
+ EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+ EXPECT_FALSE(filter.IsActive());
+
+ EXPECT_TRUE(filter.SetOffer(false, cricket::CS_REMOTE));
+ EXPECT_TRUE(filter.SetAnswer(false, cricket::CS_LOCAL));
+ EXPECT_FALSE(filter.IsActive());
+}
diff --git a/talk/session/phone/rtpdump.cc b/talk/session/phone/rtpdump.cc
index 50ebd2a..2db2990 100644
--- a/talk/session/phone/rtpdump.cc
+++ b/talk/session/phone/rtpdump.cc
@@ -33,7 +33,7 @@
#include "talk/base/byteorder.h"
#include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/session/phone/rtputils.h"
namespace cricket {
diff --git a/talk/session/phone/rtpdump_unittest.cc b/talk/session/phone/rtpdump_unittest.cc
new file mode 100644
index 0000000..fc48858
--- /dev/null
+++ b/talk/session/phone/rtpdump_unittest.cc
@@ -0,0 +1,289 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/testutils.h"
+
+namespace cricket {
+
+static const uint32 kTestSsrc = 1;
+
+// Test that we read the correct header fields from the RTP/RTCP packet.
+TEST(RtpDumpTest, ReadRtpDumpPacket) {
+ talk_base::ByteBuffer rtp_buf;
+ RtpTestUtility::kTestRawRtpPackets[0].WriteToByteBuffer(kTestSsrc, &rtp_buf);
+ RtpDumpPacket rtp_packet(rtp_buf.Data(), rtp_buf.Length(), 0, false);
+
+ int type;
+ int seq_num;
+ uint32 ts;
+ uint32 ssrc;
+ EXPECT_TRUE(rtp_packet.IsValidRtpPacket());
+ EXPECT_FALSE(rtp_packet.IsValidRtcpPacket());
+ EXPECT_TRUE(rtp_packet.GetRtpPayloadType(&type));
+ EXPECT_EQ(0, type);
+ EXPECT_TRUE(rtp_packet.GetRtpSeqNum(&seq_num));
+ EXPECT_EQ(0, seq_num);
+ EXPECT_TRUE(rtp_packet.GetRtpTimestamp(&ts));
+ EXPECT_EQ(0U, ts);
+ EXPECT_TRUE(rtp_packet.GetRtpSsrc(&ssrc));
+ EXPECT_EQ(kTestSsrc, ssrc);
+ EXPECT_FALSE(rtp_packet.GetRtcpType(&type));
+
+ talk_base::ByteBuffer rtcp_buf;
+ RtpTestUtility::kTestRawRtcpPackets[0].WriteToByteBuffer(&rtcp_buf);
+ RtpDumpPacket rtcp_packet(rtcp_buf.Data(), rtcp_buf.Length(), 0, true);
+
+ EXPECT_FALSE(rtcp_packet.IsValidRtpPacket());
+ EXPECT_TRUE(rtcp_packet.IsValidRtcpPacket());
+ EXPECT_TRUE(rtcp_packet.GetRtcpType(&type));
+ EXPECT_EQ(0, type);
+}
+
+// Test that we read only the RTP dump file.
+TEST(RtpDumpTest, ReadRtpDumpFile) {
+ RtpDumpPacket packet;
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ talk_base::scoped_ptr<RtpDumpReader> reader;
+
+ // Write a RTP packet to the stream, which is a valid RTP dump. Next, we will
+ // change the first line to make the RTP dump valid or invalid.
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, false, kTestSsrc, &writer));
+ stream.Rewind();
+ reader.reset(new RtpDumpReader(&stream));
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader->ReadPacket(&packet));
+
+ // The first line is correct.
+ stream.Rewind();
+ const char new_line[] = "#!rtpplay1.0 1.1.1.1/1\n";
+ EXPECT_EQ(talk_base::SR_SUCCESS,
+ stream.WriteAll(new_line, strlen(new_line), NULL, NULL));
+ stream.Rewind();
+ reader.reset(new RtpDumpReader(&stream));
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader->ReadPacket(&packet));
+
+ // The first line is not correct: not started with #!rtpplay1.0.
+ stream.Rewind();
+ const char new_line2[] = "#!rtpplaz1.0 0.0.0.0/0\n";
+ EXPECT_EQ(talk_base::SR_SUCCESS,
+ stream.WriteAll(new_line2, strlen(new_line2), NULL, NULL));
+ stream.Rewind();
+ reader.reset(new RtpDumpReader(&stream));
+ EXPECT_EQ(talk_base::SR_ERROR, reader->ReadPacket(&packet));
+
+ // The first line is not correct: no port.
+ stream.Rewind();
+ const char new_line3[] = "#!rtpplay1.0 0.0.0.0//\n";
+ EXPECT_EQ(talk_base::SR_SUCCESS,
+ stream.WriteAll(new_line3, strlen(new_line3), NULL, NULL));
+ stream.Rewind();
+ reader.reset(new RtpDumpReader(&stream));
+ EXPECT_EQ(talk_base::SR_ERROR, reader->ReadPacket(&packet));
+}
+
+// Test that we read the same RTP packets that rtp dump writes.
+TEST(RtpDumpTest, WriteReadSameRtp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+
+ // Check stream has only RtpTestUtility::GetTestPacketCount() packets.
+ RtpDumpPacket packet;
+ RtpDumpReader reader(&stream);
+ for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+ EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+ uint32 ssrc;
+ EXPECT_TRUE(GetRtpSsrc(&packet.data[0], packet.data.size(), &ssrc));
+ EXPECT_EQ(kTestSsrc, ssrc);
+ }
+ // No more packets to read.
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+ // Rewind the stream and read again with a specified ssrc.
+ stream.Rewind();
+ RtpDumpReader reader_w_ssrc(&stream);
+ const uint32 send_ssrc = kTestSsrc + 1;
+ reader_w_ssrc.SetSsrc(send_ssrc);
+ for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+ EXPECT_EQ(talk_base::SR_SUCCESS , reader_w_ssrc.ReadPacket(&packet));
+ uint32 ssrc;
+ EXPECT_TRUE(GetRtpSsrc(&packet.data[0], packet.data.size(), &ssrc));
+ EXPECT_EQ(send_ssrc, ssrc);
+ }
+ // No more packets to read.
+ EXPECT_EQ(talk_base::SR_EOS, reader_w_ssrc.ReadPacket(&packet));
+}
+
+// Test that we read the same RTCP packets that rtp dump writes.
+TEST(RtpDumpTest, WriteReadSameRtcp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+
+ // Check stream has only RtpTestUtility::GetTestPacketCount() packets.
+ RtpDumpPacket packet;
+ RtpDumpReader reader(&stream);
+ reader.SetSsrc(kTestSsrc + 1); // Does not affect RTCP packet.
+ for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+ EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+ }
+ // No more packets to read.
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test dumping only RTP packet headers.
+TEST(RtpDumpTest, WriteReadRtpHeadersOnly) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ writer.set_packet_filter(PF_RTPHEADER);
+
+ // Write some RTP and RTCP packets. RTP packets should only have headers;
+ // RTCP packets should be eaten.
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+ stream.Rewind();
+
+ // Check that only RTP packet headers are present.
+ RtpDumpPacket packet;
+ RtpDumpReader reader(&stream);
+ for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+ EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+ EXPECT_FALSE(packet.is_rtcp);
+ size_t len = 0;
+ packet.GetRtpHeaderLen(&len);
+ EXPECT_EQ(len, packet.data.size());
+ }
+ // No more packets to read.
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test dumping only RTCP packets.
+TEST(RtpDumpTest, WriteReadRtcpOnly) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ writer.set_packet_filter(PF_RTCPPACKET);
+
+ // Write some RTP and RTCP packets. RTP packets should be eaten.
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+ stream.Rewind();
+
+ // Check that only RTCP packets are present.
+ RtpDumpPacket packet;
+ RtpDumpReader reader(&stream);
+ for (size_t i = 0; i < RtpTestUtility::GetTestPacketCount(); ++i) {
+ EXPECT_EQ(talk_base::SR_SUCCESS , reader.ReadPacket(&packet));
+ EXPECT_TRUE(packet.is_rtcp);
+ }
+ // No more packets to read.
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+}
+
+// Test that RtpDumpLoopReader reads RTP packets continously and the elapsed
+// time, the sequence number, and timestamp are maintained properly.
+TEST(RtpDumpTest, LoopReadRtp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), false, kTestSsrc, &writer));
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 3 * RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+}
+
+// Test that RtpDumpLoopReader reads RTCP packets continously and the elapsed
+// time is maintained properly.
+TEST(RtpDumpTest, LoopReadRtcp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(
+ RtpTestUtility::GetTestPacketCount(), true, kTestSsrc, &writer));
+ EXPECT_TRUE(RtpTestUtility::VerifyTestPacketsFromStream(
+ 3 * RtpTestUtility::GetTestPacketCount(), &stream, kTestSsrc));
+}
+
+// Test that RtpDumpLoopReader reads continously from stream with a single RTP
+// packets.
+TEST(RtpDumpTest, LoopReadSingleRtp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, false, kTestSsrc, &writer));
+
+ // The regular reader can read only one packet.
+ RtpDumpPacket packet;
+ stream.Rewind();
+ RtpDumpReader reader(&stream);
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+ // The loop reader reads three packets from the input stream.
+ stream.Rewind();
+ RtpDumpLoopReader loop_reader(&stream);
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+}
+
+// Test that RtpDumpLoopReader reads continously from stream with a single RTCP
+// packets.
+TEST(RtpDumpTest, LoopReadSingleRtcp) {
+ talk_base::MemoryStream stream;
+ RtpDumpWriter writer(&stream);
+ ASSERT_TRUE(RtpTestUtility::WriteTestPackets(1, true, kTestSsrc, &writer));
+
+ // The regular reader can read only one packet.
+ RtpDumpPacket packet;
+ stream.Rewind();
+ RtpDumpReader reader(&stream);
+ EXPECT_EQ(talk_base::SR_SUCCESS, reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_EOS, reader.ReadPacket(&packet));
+
+ // The loop reader reads three packets from the input stream.
+ stream.Rewind();
+ RtpDumpLoopReader loop_reader(&stream);
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+ EXPECT_EQ(talk_base::SR_SUCCESS, loop_reader.ReadPacket(&packet));
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/rtputils.cc b/talk/session/phone/rtputils.cc
index e37c390..ded84a5 100644
--- a/talk/session/phone/rtputils.cc
+++ b/talk/session/phone/rtputils.cc
@@ -77,5 +77,18 @@
return true;
}
-} // namespace cricket
+// This method returns SSRC first of RTCP packet, except if packet is SDES.
+// TODO - Fully implement RFC 5506. This standard doesn't restrict
+// to send non-compound packets only to feedback messages.
+bool GetRtcpSsrc(const void* data, size_t len, uint32* value) {
+ // Packet should be at least of 8 bytes, to get SSRC from a RTCP packet.
+ if (!data || len < kMinRtcpPacketLen + 4 || !value) return false;
+ int pl_type;
+ if (!GetRtcpType(data, len, &pl_type)) return false;
+ // SDES packet parsing is not supported.
+ if (pl_type == kRtcpTypeSDES) return false;
+ *value = talk_base::GetBE32(static_cast<const uint8*>(data) + 4);
+ return true;
+}
+} // namespace cricket
diff --git a/talk/session/phone/rtputils.h b/talk/session/phone/rtputils.h
index 69daa83..bc2aaff 100644
--- a/talk/session/phone/rtputils.h
+++ b/talk/session/phone/rtputils.h
@@ -36,12 +36,23 @@
const size_t kMaxRtpPacketLen = 2048;
const size_t kMinRtcpPacketLen = 4;
+enum RtcpTypes {
+ kRtcpTypeSR = 200, // Sender report payload type.
+ kRtcpTypeRR = 201, // Receiver report payload type.
+ kRtcpTypeSDES = 202, // SDES payload type.
+ kRtcpTypeBye = 203, // BYE payload type.
+ kRtcpTypeApp = 204, // APP payload type.
+ kRtcpTypeRTPFB = 205, // Transport layer Feedback message payload type.
+ kRtcpTypePSFB = 206, // Payload-specific Feedback message payload type.
+};
+
bool GetRtpPayloadType(const void* data, size_t len, int* value);
bool GetRtpSeqNum(const void* data, size_t len, int* value);
bool GetRtpTimestamp(const void* data, size_t len, uint32* value);
bool GetRtpSsrc(const void* data, size_t len, uint32* value);
bool GetRtpHeaderLen(const void* data, size_t len, size_t* value);
bool GetRtcpType(const void* data, size_t len, int* value);
+bool GetRtcpSsrc(const void* data, size_t len, uint32* value);
} // namespace cricket
diff --git a/talk/session/phone/rtputils_unittest.cc b/talk/session/phone/rtputils_unittest.cc
new file mode 100644
index 0000000..b1efd0c
--- /dev/null
+++ b/talk/session/phone/rtputils_unittest.cc
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+static const unsigned char kRtpPacketWithMarker[] = {
+ 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+};
+// 3 CSRCs (0x01020304, 0x12345678, 0xAABBCCDD)
+// Extension (0xBEDE, 0x1122334455667788)
+static const unsigned char kRtpPacketWithMarkerAndCsrcAndExtension[] = {
+ 0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xBE, 0xDE, 0x00, 0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
+};
+static const unsigned char kInvalidPacket[] = { 0x80, 0x00 };
+static const unsigned char kInvalidPacketWithCsrc[] = {
+ 0x83, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC
+};
+static const unsigned char kInvalidPacketWithCsrcAndExtension1[] = {
+ 0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xBE, 0xDE, 0x00
+};
+static const unsigned char kInvalidPacketWithCsrcAndExtension2[] = {
+ 0x93, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04, 0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD,
+ 0xBE, 0xDE, 0x00, 0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77
+};
+
+// PT = 206, FMT = 1, Sender SSRC = 0x1111, Media SSRC = 0x1111
+// No FCI information is needed for PLI.
+static const unsigned char kNonCompoundRtcpPliFeedbackPacket[] = {
+ 0x81, 0xCE, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x11, 0x11
+};
+
+// Packet has only mandatory fixed RTCP header
+// PT = 204, SSRC = 0x1111
+static const unsigned char kNonCompoundRtcpAppPacket[] = {
+ 0x81, 0xCC, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11
+};
+
+// PT = 202, Source count = 0
+static const unsigned char kNonCompoundRtcpSDESPacket[] = {
+ 0x80, 0xCA, 0x00, 0x00
+};
+
+TEST(RtpUtilsTest, GetRtp) {
+ int pt;
+ EXPECT_TRUE(GetRtpPayloadType(kPcmuFrame, sizeof(kPcmuFrame), &pt));
+ EXPECT_EQ(0, pt);
+ EXPECT_TRUE(GetRtpPayloadType(kRtpPacketWithMarker,
+ sizeof(kRtpPacketWithMarker), &pt));
+ EXPECT_EQ(0, pt);
+
+ int seq_num;
+ EXPECT_TRUE(GetRtpSeqNum(kPcmuFrame, sizeof(kPcmuFrame), &seq_num));
+ EXPECT_EQ(1, seq_num);
+
+ uint32 ts;
+ EXPECT_TRUE(GetRtpTimestamp(kPcmuFrame, sizeof(kPcmuFrame), &ts));
+ EXPECT_EQ(0u, ts);
+
+ uint32 ssrc;
+ EXPECT_TRUE(GetRtpSsrc(kPcmuFrame, sizeof(kPcmuFrame), &ssrc));
+ EXPECT_EQ(1u, ssrc);
+
+ EXPECT_FALSE(GetRtpPayloadType(kInvalidPacket, sizeof(kInvalidPacket), &pt));
+ EXPECT_FALSE(GetRtpSeqNum(kInvalidPacket, sizeof(kInvalidPacket), &seq_num));
+ EXPECT_FALSE(GetRtpTimestamp(kInvalidPacket, sizeof(kInvalidPacket), &ts));
+ EXPECT_FALSE(GetRtpSsrc(kInvalidPacket, sizeof(kInvalidPacket), &ssrc));
+}
+
+TEST(RtpUtilsTest, GetRtpHeaderLen) {
+ size_t len;
+ EXPECT_TRUE(GetRtpHeaderLen(kPcmuFrame, sizeof(kPcmuFrame), &len));
+ EXPECT_EQ(12U, len);
+
+ EXPECT_TRUE(GetRtpHeaderLen(kRtpPacketWithMarkerAndCsrcAndExtension,
+ sizeof(kRtpPacketWithMarkerAndCsrcAndExtension),
+ &len));
+ EXPECT_EQ(sizeof(kRtpPacketWithMarkerAndCsrcAndExtension), len);
+
+ EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacket, sizeof(kInvalidPacket), &len));
+ EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrc,
+ sizeof(kInvalidPacketWithCsrc), &len));
+ EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrcAndExtension1,
+ sizeof(kInvalidPacketWithCsrcAndExtension1),
+ &len));
+ EXPECT_FALSE(GetRtpHeaderLen(kInvalidPacketWithCsrcAndExtension2,
+ sizeof(kInvalidPacketWithCsrcAndExtension2),
+ &len));
+}
+
+TEST(RtpUtilsTest, GetRtcp) {
+ int pt;
+ EXPECT_TRUE(GetRtcpType(kRtcpReport, sizeof(kRtcpReport), &pt));
+ EXPECT_EQ(0xc9, pt);
+
+ EXPECT_FALSE(GetRtcpType(kInvalidPacket, sizeof(kInvalidPacket), &pt));
+
+ uint32 ssrc;
+ EXPECT_TRUE(GetRtcpSsrc(kNonCompoundRtcpPliFeedbackPacket,
+ sizeof(kNonCompoundRtcpPliFeedbackPacket),
+ &ssrc));
+ EXPECT_TRUE(GetRtcpSsrc(kNonCompoundRtcpAppPacket,
+ sizeof(kNonCompoundRtcpAppPacket),
+ &ssrc));
+ EXPECT_FALSE(GetRtcpSsrc(kNonCompoundRtcpSDESPacket,
+ sizeof(kNonCompoundRtcpSDESPacket),
+ &ssrc));
+}
+
+} // namespace cricket
+
diff --git a/talk/session/phone/screencastid.h b/talk/session/phone/screencastid.h
new file mode 100644
index 0000000..0acc7de
--- /dev/null
+++ b/talk/session/phone/screencastid.h
@@ -0,0 +1,88 @@
+// Copyright 2012 Google Inc. All Rights Reserved
+
+//
+// Defines variant class ScreencastId that combines WindowId and DesktopId.
+
+#ifndef TALK_SESSION_PHONE_SCREENCASTID_H_
+#define TALK_SESSION_PHONE_SCREENCASTID_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/window.h"
+#include "talk/base/windowpicker.h"
+
+namespace cricket {
+
+class ScreencastId;
+typedef std::vector<ScreencastId> ScreencastIdList;
+
+// Used for identifying a window or desktop to be screencast.
+class ScreencastId {
+ public:
+ enum Type { INVALID, WINDOW, DESKTOP };
+
+ // Default constructor indicates invalid ScreencastId.
+ ScreencastId() : type_(INVALID) {}
+ explicit ScreencastId(const talk_base::WindowId& id)
+ : type_(WINDOW), window_(id) {
+ }
+ explicit ScreencastId(const talk_base::DesktopId& id)
+ : type_(DESKTOP), desktop_(id) {
+ }
+
+ Type type() const { return type_; }
+ const talk_base::WindowId& window() const { return window_; }
+ const talk_base::DesktopId& desktop() const { return desktop_; }
+
+ // Title is an optional parameter.
+ const std::string& title() const { return title_; }
+ void set_title(const std::string& desc) { title_ = desc; }
+
+ bool IsValid() const {
+ if (type_ == INVALID) {
+ return false;
+ } else if (type_ == WINDOW) {
+ return window_.IsValid();
+ } else {
+ return desktop_.IsValid();
+ }
+ }
+ bool IsWindow() const { return type_ == WINDOW; }
+ bool IsDesktop() const { return type_ == DESKTOP; }
+ bool EqualsId(const ScreencastId& other) const {
+ if (type_ != other.type_) {
+ return false;
+ }
+ if (type_ == INVALID) {
+ return true;
+ } else if (type_ == WINDOW) {
+ return window_.Equals(other.window());
+ }
+ return desktop_.Equals(other.desktop());
+ }
+
+ // T is assumed to be WindowDescription or DesktopDescription.
+ template<class T>
+ static cricket::ScreencastIdList Convert(const std::vector<T>& list) {
+ ScreencastIdList screencast_list;
+ screencast_list.reserve(list.size());
+ for (typename std::vector<T>::const_iterator it = list.begin();
+ it != list.end(); ++it) {
+ ScreencastId id(it->id());
+ id.set_title(it->title());
+ screencast_list.push_back(id);
+ }
+ return screencast_list;
+ }
+
+ private:
+ Type type_;
+ talk_base::WindowId window_;
+ talk_base::DesktopId desktop_;
+ std::string title_; // Optional.
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_SCREENCASTID_H_
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
index b3578e1..7a1cb78 100644
--- a/talk/session/phone/srtpfilter.cc
+++ b/talk/session/phone/srtpfilter.cc
@@ -25,22 +25,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-// talk's config.h, generated from mac_config_dot_h for OSX, conflicts with the
-// one included by the libsrtp headers. Don't use it. Instead, we keep HAVE_SRTP
-// and LOGGING defined in config.h.
#undef HAVE_CONFIG_H
-#ifdef OSX
-// TODO: For the XCode build, we force SRTP (b/2500074)
-#ifndef HAVE_SRTP
-#define HAVE_SRTP 1
-#endif // HAVE_SRTP
-// If LOGGING is not defined, define it to 1 (b/3245816)
-#ifndef LOGGING
-#define LOGGING 1
-#endif // HAVE_SRTP
-#endif
-
#include "talk/session/phone/srtpfilter.h"
#include <algorithm>
@@ -48,7 +34,8 @@
#include "talk/base/base64.h"
#include "talk/base/logging.h"
-#include "talk/base/time.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/timeutils.h"
#include "talk/session/phone/rtputils.h"
// Enable this line to turn on SRTP debugging
@@ -79,6 +66,8 @@
const char CS_AES_CM_128_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
const char CS_AES_CM_128_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32";
const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3;
+const int SRTP_MASTER_KEY_KEY_LEN = 16;
+const int SRTP_MASTER_KEY_SALT_LEN = 14;
#ifndef HAVE_SRTP
@@ -110,44 +99,41 @@
SrtpFilter::SrtpFilter()
: state_(ST_INIT),
- send_session_(new SrtpSession()),
- recv_session_(new SrtpSession()) {
- SignalSrtpError.repeat(send_session_->SignalSrtpError);
- SignalSrtpError.repeat(recv_session_->SignalSrtpError);
+ signal_silent_time_in_ms_(0) {
}
SrtpFilter::~SrtpFilter() {
}
bool SrtpFilter::IsActive() const {
- return (state_ == ST_ACTIVE);
+ return state_ >= ST_ACTIVE;
}
bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
ContentSource source) {
- bool ret = false;
- if (state_ == ST_INIT) {
- ret = StoreParams(offer_params, source);
- } else {
- LOG(LS_ERROR) << "Invalid state for SRTP offer";
+ if (state_ != ST_INIT && state_ != ST_ACTIVE) {
+ LOG(LS_ERROR) << "Wrong state to update SRTP offer";
+ return false;
}
- return ret;
+ return StoreParams(offer_params, source);
}
bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source) {
bool ret = false;
if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
- (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+ (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL) ||
+ (state_ == ST_SENTUPDATEDOFFER && source == CS_REMOTE) ||
+ (state_ == ST_RECEIVEDUPDATEDOFFER && source == CS_LOCAL)) {
// If the answer requests crypto, finalize the parameters and apply them.
// Otherwise, complete the negotiation of a unencrypted session.
if (!answer_params.empty()) {
CryptoParams selected_params;
ret = NegotiateParams(answer_params, &selected_params);
if (ret) {
- if (state_ == ST_SENTOFFER) {
+ if (state_ == ST_SENTOFFER || state_ == ST_SENTUPDATEDOFFER) {
ret = ApplyParams(selected_params, answer_params[0]);
- } else { // ST_RECEIVEDOFFER
+ } else { // ST_RECEIVEDOFFER || ST_RECEIVEDUPDATEDOFFER
ret = ApplyParams(answer_params[0], selected_params);
}
}
@@ -160,6 +146,68 @@
return ret;
}
+bool SrtpFilter::SetRtpParams(const std::string& send_cs,
+ const uint8* send_key, int send_key_len,
+ const std::string& recv_cs,
+ const uint8* recv_key, int recv_key_len) {
+ if (state_ == ST_ACTIVE) {
+ LOG(LS_ERROR) << "Tried to set SRTP Params when filter already active";
+ return false;
+ }
+ CreateSrtpSessions();
+ if (!send_session_->SetSend(send_cs, send_key, send_key_len))
+ return false;
+
+ if (!recv_session_->SetRecv(recv_cs, recv_key, recv_key_len))
+ return false;
+
+ state_ = ST_ACTIVE;
+
+ LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
+ << " send cipher_suite " << send_cs
+ << " recv cipher_suite " << recv_cs;
+
+ return true;
+}
+
+// This function is provided separately because DTLS-SRTP behaves
+// differently in RTP/RTCP mux and non-mux modes.
+//
+// - In the non-muxed case, RTP and RTCP are keyed with different
+// keys (from different DTLS handshakes), and so we need a new
+// SrtpSession.
+// - In the muxed case, they are keyed with the same keys, so
+// this function is not needed
+bool SrtpFilter::SetRtcpParams(const std::string& send_cs,
+ const uint8* send_key, int send_key_len,
+ const std::string& recv_cs,
+ const uint8* recv_key, int recv_key_len) {
+ // This can only be called once, but can be safely called after
+ // SetRtpParams
+ if (send_rtcp_session_.get() || send_rtcp_session_.get()) {
+ LOG(LS_ERROR) << "Tried to set SRTCP Params when filter already active";
+ return false;
+ }
+
+ send_rtcp_session_.reset(new SrtpSession());
+ SignalSrtpError.repeat(send_rtcp_session_->SignalSrtpError);
+ send_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+ if (!send_rtcp_session_->SetRecv(send_cs, send_key, send_key_len))
+ return false;
+
+ recv_rtcp_session_.reset(new SrtpSession());
+ SignalSrtpError.repeat(recv_rtcp_session_->SignalSrtpError);
+ recv_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+ if (!recv_rtcp_session_->SetRecv(recv_cs, recv_key, recv_key_len))
+ return false;
+
+ LOG(LS_INFO) << "SRTCP activated with negotiated parameters:"
+ << " send cipher_suite " << send_cs
+ << " recv cipher_suite " << recv_cs;
+
+ return true;
+}
+
bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
if (!IsActive()) {
LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
@@ -173,7 +221,11 @@
LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
return false;
}
- return send_session_->ProtectRtcp(p, in_len, max_len, out_len);
+ if (send_rtcp_session_.get()) {
+ return send_rtcp_session_->ProtectRtcp(p, in_len, max_len, out_len);
+ } else {
+ return send_session_->ProtectRtcp(p, in_len, max_len, out_len);
+ }
}
bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
@@ -189,21 +241,47 @@
LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
return false;
}
- return recv_session_->UnprotectRtcp(p, in_len, out_len);
+ if (recv_rtcp_session_.get()) {
+ return recv_rtcp_session_->UnprotectRtcp(p, in_len, out_len);
+ } else {
+ return recv_session_->UnprotectRtcp(p, in_len, out_len);
+ }
}
void SrtpFilter::set_signal_silent_time(uint32 signal_silent_time_in_ms) {
- send_session_->set_signal_silent_time(signal_silent_time_in_ms);
- recv_session_->set_signal_silent_time(signal_silent_time_in_ms);
+ signal_silent_time_in_ms_ = signal_silent_time_in_ms;
+ if (state_ == ST_ACTIVE) {
+ send_session_->set_signal_silent_time(signal_silent_time_in_ms);
+ recv_session_->set_signal_silent_time(signal_silent_time_in_ms);
+ if (send_rtcp_session_.get())
+ send_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+ if (recv_rtcp_session_.get())
+ recv_rtcp_session_->set_signal_silent_time(signal_silent_time_in_ms);
+ }
}
bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
ContentSource source) {
offer_params_ = params;
- state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+ if (state_ == ST_INIT) {
+ state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+ } else { // ST_ACTIVE
+ state_ =
+ (source == CS_LOCAL) ? ST_SENTUPDATEDOFFER : ST_RECEIVEDUPDATEDOFFER;
+ }
return true;
}
+void SrtpFilter::CreateSrtpSessions() {
+ send_session_.reset(new SrtpSession());
+ recv_session_.reset(new SrtpSession());
+ SignalSrtpError.repeat(send_session_->SignalSrtpError);
+ SignalSrtpError.repeat(recv_session_->SignalSrtpError);
+
+ send_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+ recv_session_->set_signal_silent_time(signal_silent_time_in_ms_);
+}
+
bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
CryptoParams* selected_params) {
// We're processing an accept. We should have exactly one set of params,
@@ -239,6 +317,7 @@
ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
if (ret) {
+ CreateSrtpSessions();
ret = (send_session_->SetSend(send_params.cipher_suite,
send_key, sizeof(send_key)) &&
recv_session_->SetRecv(recv_params.cipher_suite,
@@ -290,7 +369,6 @@
#ifdef HAVE_SRTP
bool SrtpSession::inited_ = false;
-std::list<SrtpSession*> SrtpSession::sessions_;
SrtpSession::SrtpSession()
: session_(NULL),
@@ -298,12 +376,12 @@
rtcp_auth_tag_len_(0),
srtp_stat_(new SrtpStat()),
last_send_seq_num_(-1) {
- sessions_.push_back(this);
+ sessions()->push_back(this);
SignalSrtpError.repeat(srtp_stat_->SignalSrtpError);
}
SrtpSession::~SrtpSession() {
- sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
+ sessions()->erase(std::find(sessions()->begin(), sessions()->end(), this));
if (session_) {
srtp_dealloc(session_);
}
@@ -503,8 +581,8 @@
}
void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
- for (std::list<SrtpSession*>::iterator it = sessions_.begin();
- it != sessions_.end(); ++it) {
+ for (std::list<SrtpSession*>::iterator it = sessions()->begin();
+ it != sessions()->end(); ++it) {
if ((*it)->session_ == ev->session) {
(*it)->HandleEvent(ev);
break;
@@ -512,8 +590,14 @@
}
}
+std::list<SrtpSession*>* SrtpSession::sessions() {
+ LIBJINGLE_DEFINE_STATIC_LOCAL(std::list<SrtpSession*>, sessions, ());
+ return &sessions;
+}
+
#else // !HAVE_SRTP
+// On some systems, SRTP is not (yet) available.
SrtpSession::SrtpSession() {
LOG(WARNING) << "SRTP implementation is missing.";
@@ -628,6 +712,7 @@
#else // !HAVE_SRTP
+// On some systems, SRTP is not (yet) available.
SrtpStat::SrtpStat()
: signal_silent_time_(1000) {
diff --git a/talk/session/phone/srtpfilter.h b/talk/session/phone/srtpfilter.h
index 409a3a9..991d4bf 100644
--- a/talk/session/phone/srtpfilter.h
+++ b/talk/session/phone/srtpfilter.h
@@ -57,6 +57,10 @@
// Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes.
extern const int SRTP_MASTER_KEY_BASE64_LEN;
+// Needed for DTLS-SRTP
+extern const int SRTP_MASTER_KEY_KEY_LEN;
+extern const int SRTP_MASTER_KEY_SALT_LEN;
+
class SrtpSession;
class SrtpStat;
@@ -99,6 +103,17 @@
bool SetAnswer(const std::vector<CryptoParams>& answer_params,
ContentSource source);
+ // Just set up both sets of keys directly.
+ // Used with DTLS-SRTP.
+ bool SetRtpParams(const std::string& send_cs,
+ const uint8* send_key, int send_key_len,
+ const std::string& recv_cs,
+ const uint8* recv_key, int recv_key_len);
+ bool SetRtcpParams(const std::string& send_cs,
+ const uint8* send_key, int send_key_len,
+ const std::string& recv_cs,
+ const uint8* recv_key, int recv_key_len);
+
// Encrypts/signs an individual RTP/RTCP packet, in-place.
// If an HMAC is used, this will increase the packet size.
bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
@@ -114,8 +129,9 @@
sigslot::repeater3<uint32, Mode, Error> SignalSrtpError;
protected:
- bool StoreParams(const std::vector<CryptoParams>& offer_params,
+ bool StoreParams(const std::vector<CryptoParams>& params,
ContentSource source);
+ void CreateSrtpSessions();
bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
CryptoParams* selected_params);
bool ApplyParams(const CryptoParams& send_params,
@@ -124,11 +140,25 @@
static bool ParseKeyParams(const std::string& params, uint8* key, int len);
private:
- enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+ enum State {
+ ST_INIT, // SRTP filter unused.
+ ST_SENTOFFER, // Offer with SRTP parameters sent.
+ ST_RECEIVEDOFFER, // Offer with SRTP parameters received.
+ ST_ACTIVE, // Offer and answer set.
+ // SRTP filter is active but new parameters are offered.
+ // When the answer is set, the state transitions to ST_ACTIVE or ST_INIT.
+ ST_SENTUPDATEDOFFER,
+ // SRTP filter is active but new parameters are received.
+ // When the answer is set, the state transitions back to ST_ACTIVE.
+ ST_RECEIVEDUPDATEDOFFER
+ };
State state_;
+ uint32 signal_silent_time_in_ms_;
std::vector<CryptoParams> offer_params_;
talk_base::scoped_ptr<SrtpSession> send_session_;
talk_base::scoped_ptr<SrtpSession> recv_session_;
+ talk_base::scoped_ptr<SrtpSession> send_rtcp_session_;
+ talk_base::scoped_ptr<SrtpSession> recv_rtcp_session_;
};
// Class that wraps a libSRTP session.
@@ -164,13 +194,13 @@
static bool Init();
void HandleEvent(const srtp_event_data_t* ev);
static void HandleEventThunk(srtp_event_data_t* ev);
+ static std::list<SrtpSession*>* sessions();
srtp_t session_;
int rtp_auth_tag_len_;
int rtcp_auth_tag_len_;
talk_base::scoped_ptr<SrtpStat> srtp_stat_;
static bool inited_;
- static std::list<SrtpSession*> sessions_;
int last_send_seq_num_;
DISALLOW_COPY_AND_ASSIGN(SrtpSession);
};
@@ -228,7 +258,7 @@
FailureStat()
: last_signal_time(0) {
}
- FailureStat(uint32 in_last_signal_time)
+ explicit FailureStat(uint32 in_last_signal_time)
: last_signal_time(in_last_signal_time) {
}
void Reset() {
diff --git a/talk/session/phone/srtpfilter_unittest.cc b/talk/session/phone/srtpfilter_unittest.cc
new file mode 100644
index 0000000..276c341
--- /dev/null
+++ b/talk/session/phone/srtpfilter_unittest.cc
@@ -0,0 +1,764 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/srtpfilter.h"
+#ifdef SRTP_RELATIVE_PATH
+#include "crypto/include/err.h"
+#else
+#include "third_party/libsrtp/crypto/include/err.h"
+#endif
+
+using cricket::CS_AES_CM_128_HMAC_SHA1_80;
+using cricket::CS_AES_CM_128_HMAC_SHA1_32;
+using cricket::CryptoParams;
+using cricket::CS_LOCAL;
+using cricket::CS_REMOTE;
+
+static const uint8 kTestKey1[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234";
+static const uint8 kTestKey2[] = "4321ZYXWVUTSRQPONMLKJIHGFEDCBA";
+static const int kTestKeyLen = 30;
+static const std::string kTestKeyParams1 =
+ "inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const std::string kTestKeyParams2 =
+ "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const std::string kTestKeyParams3 =
+ "inline:1234X19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz";
+static const std::string kTestKeyParams4 =
+ "inline:4567QCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+static const cricket::CryptoParams kTestCryptoParams1(
+ 1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams1, "");
+static const cricket::CryptoParams kTestCryptoParams2(
+ 1, "AES_CM_128_HMAC_SHA1_80", kTestKeyParams2, "");
+
+static int rtp_auth_tag_len(const std::string& cs) {
+ return (cs == CS_AES_CM_128_HMAC_SHA1_32) ? 4 : 10;
+}
+static int rtcp_auth_tag_len(const std::string& cs) {
+ return 10;
+}
+
+class SrtpFilterTest : public testing::Test {
+ protected:
+ SrtpFilterTest()
+ // Need to initialize |sequence_number_|, the value does not matter.
+ : sequence_number_(1) {
+ }
+ static std::vector<CryptoParams> MakeVector(const CryptoParams& params) {
+ std::vector<CryptoParams> vec;
+ vec.push_back(params);
+ return vec;
+ }
+ void TestSetParams(const std::vector<CryptoParams>& params1,
+ const std::vector<CryptoParams>& params2) {
+ EXPECT_TRUE(f1_.SetOffer(params1, CS_LOCAL));
+ EXPECT_TRUE(f2_.SetOffer(params1, CS_REMOTE));
+ EXPECT_TRUE(f2_.SetAnswer(params2, CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(params2, CS_REMOTE));
+ EXPECT_TRUE(f1_.IsActive());
+ }
+ void TestProtectUnprotect(const std::string& cs1, const std::string& cs2) {
+ char rtp_packet[sizeof(kPcmuFrame) + 10];
+ char original_rtp_packet[sizeof(kPcmuFrame)];
+ char rtcp_packet[sizeof(kRtcpReport) + 4 + 10];
+ int rtp_len = sizeof(kPcmuFrame), rtcp_len = sizeof(kRtcpReport), out_len;
+ memcpy(rtp_packet, kPcmuFrame, rtp_len);
+ // In order to be able to run this test function multiple times we can not
+ // use the same sequence number twice. Increase the sequence number by one.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet) + 2,
+ ++sequence_number_);
+ memcpy(original_rtp_packet, rtp_packet, rtp_len);
+ memcpy(rtcp_packet, kRtcpReport, rtcp_len);
+
+ EXPECT_TRUE(f1_.ProtectRtp(rtp_packet, rtp_len,
+ sizeof(rtp_packet), &out_len));
+ EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs1));
+ EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+ EXPECT_TRUE(f2_.UnprotectRtp(rtp_packet, out_len, &out_len));
+ EXPECT_EQ(rtp_len, out_len);
+ EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+
+ EXPECT_TRUE(f2_.ProtectRtp(rtp_packet, rtp_len,
+ sizeof(rtp_packet), &out_len));
+ EXPECT_EQ(out_len, rtp_len + rtp_auth_tag_len(cs2));
+ EXPECT_NE(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+ EXPECT_TRUE(f1_.UnprotectRtp(rtp_packet, out_len, &out_len));
+ EXPECT_EQ(rtp_len, out_len);
+ EXPECT_EQ(0, memcmp(rtp_packet, original_rtp_packet, rtp_len));
+
+ EXPECT_TRUE(f1_.ProtectRtcp(rtcp_packet, rtcp_len,
+ sizeof(rtcp_packet), &out_len));
+ EXPECT_EQ(out_len, rtcp_len + 4 + rtcp_auth_tag_len(cs1)); // NOLINT
+ EXPECT_NE(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+ EXPECT_TRUE(f2_.UnprotectRtcp(rtcp_packet, out_len, &out_len));
+ EXPECT_EQ(rtcp_len, out_len);
+ EXPECT_EQ(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+
+ EXPECT_TRUE(f2_.ProtectRtcp(rtcp_packet, rtcp_len,
+ sizeof(rtcp_packet), &out_len));
+ EXPECT_EQ(out_len, rtcp_len + 4 + rtcp_auth_tag_len(cs2)); // NOLINT
+ EXPECT_NE(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+ EXPECT_TRUE(f1_.UnprotectRtcp(rtcp_packet, out_len, &out_len));
+ EXPECT_EQ(rtcp_len, out_len);
+ EXPECT_EQ(0, memcmp(rtcp_packet, kRtcpReport, rtcp_len));
+ }
+ cricket::SrtpFilter f1_;
+ cricket::SrtpFilter f2_;
+ int sequence_number_;
+};
+
+// Test that we can set up the session and keys properly.
+TEST_F(SrtpFilterTest, TestGoodSetupOneCipherSuite) {
+ EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+ EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we can set up things with multiple params.
+TEST_F(SrtpFilterTest, TestGoodSetupMultipleCipherSuites) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ offer.push_back(kTestCryptoParams1);
+ offer[1].tag = 2;
+ offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ answer[0].tag = 2;
+ answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_TRUE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired.
+TEST_F(SrtpFilterTest, TestGoodSetupNoCipherSuites) {
+ std::vector<CryptoParams> offer, answer;
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we handle the cases where crypto is not desired by the remote side.
+TEST_F(SrtpFilterTest, TestGoodSetupNoAnswerCipherSuites) {
+ std::vector<CryptoParams> answer;
+ EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we call the functions the wrong way.
+TEST_F(SrtpFilterTest, TestBadSetup) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetOffer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_LOCAL));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we have params in the answer when none were offered.
+TEST_F(SrtpFilterTest, TestNoAnswerCipherSuites) {
+ std::vector<CryptoParams> offer;
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(MakeVector(kTestCryptoParams2), CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we have too many params in our answer.
+TEST_F(SrtpFilterTest, TestMultipleAnswerCipherSuites) {
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer.push_back(kTestCryptoParams2);
+ answer[1].tag = 2;
+ answer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ EXPECT_TRUE(f1_.SetOffer(MakeVector(kTestCryptoParams1), CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we don't support the cipher-suite.
+TEST_F(SrtpFilterTest, TestInvalidCipherSuite) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ offer[0].cipher_suite = answer[0].cipher_suite = "FOO";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a tag.
+TEST_F(SrtpFilterTest, TestNoMatchingTag) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].tag = 99;
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail if we can't agree on a cipher-suite.
+TEST_F(SrtpFilterTest, TestNoMatchingCipherSuite) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].tag = 2;
+ answer[0].cipher_suite = "FOO";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with bad base64 content.
+TEST_F(SrtpFilterTest, TestInvalidKeyData) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].key_params = "inline:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with the wrong key-method.
+TEST_F(SrtpFilterTest, TestWrongKeyMethod) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].key_params = "outline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooShort) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtx";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpFilterTest, TestKeyTooLong) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].key_params = "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBRABCD";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we fail keys with lifetime or MKI set (since we don't support)
+TEST_F(SrtpFilterTest, TestUnsupportedOptions) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ answer[0].key_params =
+ "inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:4";
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_FALSE(f1_.SetAnswer(answer, CS_REMOTE));
+ EXPECT_FALSE(f1_.IsActive());
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_80.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_80) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ offer.push_back(kTestCryptoParams1);
+ offer[1].tag = 2;
+ offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ TestSetParams(offer, answer);
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test that we can encrypt/decrypt after negotiating AES_CM_128_HMAC_SHA1_32.
+TEST_F(SrtpFilterTest, TestProtect_AES_CM_128_HMAC_SHA1_32) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+ offer.push_back(kTestCryptoParams1);
+ offer[1].tag = 2;
+ offer[1].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ answer[0].tag = 2;
+ answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ TestSetParams(offer, answer);
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we can change encryption parameters.
+TEST_F(SrtpFilterTest, TestChangeParameters) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+ TestSetParams(offer, answer);
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+ // Change the key parameters and cipher_suite.
+ offer[0].key_params = kTestKeyParams3;
+ offer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+ answer[0].key_params = kTestKeyParams4;
+ answer[0].cipher_suite = CS_AES_CM_128_HMAC_SHA1_32;
+
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+ EXPECT_TRUE(f1_.IsActive());
+ EXPECT_TRUE(f1_.IsActive());
+
+ // Test that the old keys are valid until the negotiation is complete.
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+ // Complete the negotiation and test that we can still understand each other.
+ EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we can disable encryption.
+TEST_F(SrtpFilterTest, TestDisableEncryption) {
+ std::vector<CryptoParams> offer(MakeVector(kTestCryptoParams1));
+ std::vector<CryptoParams> answer(MakeVector(kTestCryptoParams2));
+
+ TestSetParams(offer, answer);
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+ offer.clear();
+ answer.clear();
+ EXPECT_TRUE(f1_.SetOffer(offer, CS_LOCAL));
+ EXPECT_TRUE(f2_.SetOffer(offer, CS_REMOTE));
+ EXPECT_TRUE(f1_.IsActive());
+ EXPECT_TRUE(f2_.IsActive());
+
+ // Test that the old keys are valid until the negotiation is complete.
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+
+ // Complete the negotiation.
+ EXPECT_TRUE(f2_.SetAnswer(answer, CS_LOCAL));
+ EXPECT_TRUE(f1_.SetAnswer(answer, CS_REMOTE));
+
+ EXPECT_FALSE(f1_.IsActive());
+ EXPECT_FALSE(f2_.IsActive());
+}
+
+// Test directly setting the params with AES_CM_128_HMAC_SHA1_80
+TEST_F(SrtpFilterTest, TestProtect_SetParamsDirect_AES_CM_128_HMAC_SHA1_80) {
+ EXPECT_TRUE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey2, kTestKeyLen));
+ EXPECT_TRUE(f2_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey2, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey2, kTestKeyLen));
+ EXPECT_TRUE(f2_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey2, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(f1_.IsActive());
+ EXPECT_TRUE(f2_.IsActive());
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_80, CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test directly setting the params with AES_CM_128_HMAC_SHA1_32
+TEST_F(SrtpFilterTest, TestProtect_SetParamsDirect_AES_CM_128_HMAC_SHA1_32) {
+ EXPECT_TRUE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey1, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey2, kTestKeyLen));
+ EXPECT_TRUE(f2_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey2, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey1, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey2, kTestKeyLen));
+ EXPECT_TRUE(f2_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey2, kTestKeyLen,
+ CS_AES_CM_128_HMAC_SHA1_32,
+ kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(f1_.IsActive());
+ EXPECT_TRUE(f2_.IsActive());
+ TestProtectUnprotect(CS_AES_CM_128_HMAC_SHA1_32, CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test directly setting the params with bogus keys
+TEST_F(SrtpFilterTest, TestSetParamsKeyTooShort) {
+ EXPECT_FALSE(f1_.SetRtpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen - 1,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen - 1));
+ EXPECT_FALSE(f1_.SetRtcpParams(CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen - 1,
+ CS_AES_CM_128_HMAC_SHA1_80,
+ kTestKey1, kTestKeyLen - 1));
+}
+
+class SrtpSessionTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ rtp_len_ = sizeof(kPcmuFrame);
+ rtcp_len_ = sizeof(kRtcpReport);
+ memcpy(rtp_packet_, kPcmuFrame, rtp_len_);
+ memcpy(rtcp_packet_, kRtcpReport, rtcp_len_);
+ }
+ void TestProtectRtp(const std::string& cs) {
+ int out_len = 0;
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_,
+ sizeof(rtp_packet_), &out_len));
+ EXPECT_EQ(out_len, rtp_len_ + rtp_auth_tag_len(cs));
+ EXPECT_NE(0, memcmp(rtp_packet_, kPcmuFrame, rtp_len_));
+ rtp_len_ = out_len;
+ }
+ void TestProtectRtcp(const std::string& cs) {
+ int out_len = 0;
+ EXPECT_TRUE(s1_.ProtectRtcp(rtcp_packet_, rtcp_len_,
+ sizeof(rtcp_packet_), &out_len));
+ EXPECT_EQ(out_len, rtcp_len_ + 4 + rtcp_auth_tag_len(cs)); // NOLINT
+ EXPECT_NE(0, memcmp(rtcp_packet_, kRtcpReport, rtcp_len_));
+ rtcp_len_ = out_len;
+ }
+ void TestUnprotectRtp(const std::string& cs) {
+ int out_len = 0, expected_len = sizeof(kPcmuFrame);
+ EXPECT_TRUE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+ EXPECT_EQ(expected_len, out_len);
+ EXPECT_EQ(0, memcmp(rtp_packet_, kPcmuFrame, out_len));
+ }
+ void TestUnprotectRtcp(const std::string& cs) {
+ int out_len = 0, expected_len = sizeof(kRtcpReport);
+ EXPECT_TRUE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+ EXPECT_EQ(expected_len, out_len);
+ EXPECT_EQ(0, memcmp(rtcp_packet_, kRtcpReport, out_len));
+ }
+ cricket::SrtpSession s1_;
+ cricket::SrtpSession s2_;
+ char rtp_packet_[sizeof(kPcmuFrame) + 10];
+ char rtcp_packet_[sizeof(kRtcpReport) + 4 + 10];
+ int rtp_len_;
+ int rtcp_len_;
+};
+
+// Test that we can set up the session and keys properly.
+TEST_F(SrtpSessionTest, TestGoodSetup) {
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+}
+
+// Test that we can't change the keys once set.
+TEST_F(SrtpSessionTest, TestBadSetup) {
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_FALSE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey2, kTestKeyLen));
+ EXPECT_FALSE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey2, kTestKeyLen));
+}
+
+// Test that we fail keys of the wrong length.
+TEST_F(SrtpSessionTest, TestKeysTooShort) {
+ EXPECT_FALSE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, 1));
+ EXPECT_FALSE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, 1));
+}
+
+// Test that we can encrypt and decrypt RTP/RTCP using AES_CM_128_HMAC_SHA1_80.
+TEST_F(SrtpSessionTest, TestProtect_AES_CM_128_HMAC_SHA1_80) {
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+ TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+ TestUnprotectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+ TestUnprotectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+}
+
+// Test that we can encrypt and decrypt RTP/RTCP using AES_CM_128_HMAC_SHA1_32.
+TEST_F(SrtpSessionTest, TestProtect_AES_CM_128_HMAC_SHA1_32) {
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_32, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_32, kTestKey1, kTestKeyLen));
+ TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_32);
+ TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_32);
+ TestUnprotectRtp(CS_AES_CM_128_HMAC_SHA1_32);
+ TestUnprotectRtcp(CS_AES_CM_128_HMAC_SHA1_32);
+}
+
+// Test that we fail to unprotect if someone tampers with the RTP/RTCP paylaods.
+TEST_F(SrtpSessionTest, TestTamperReject) {
+ int out_len;
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ TestProtectRtp(CS_AES_CM_128_HMAC_SHA1_80);
+ TestProtectRtcp(CS_AES_CM_128_HMAC_SHA1_80);
+ rtp_packet_[0] = 0x12;
+ rtcp_packet_[1] = 0x34;
+ EXPECT_FALSE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+ EXPECT_FALSE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+}
+
+// Test that we fail to unprotect if the payloads are not authenticated.
+TEST_F(SrtpSessionTest, TestUnencryptReject) {
+ int out_len;
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_FALSE(s2_.UnprotectRtp(rtp_packet_, rtp_len_, &out_len));
+ EXPECT_FALSE(s2_.UnprotectRtcp(rtcp_packet_, rtcp_len_, &out_len));
+}
+
+// Test that we fail when using buffers that are too small.
+TEST_F(SrtpSessionTest, TestBuffersTooSmall) {
+ int out_len;
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_,
+ sizeof(rtp_packet_) - 10, &out_len));
+ EXPECT_FALSE(s1_.ProtectRtcp(rtcp_packet_, rtcp_len_,
+ sizeof(rtcp_packet_) - 14, &out_len));
+}
+
+TEST_F(SrtpSessionTest, TestReplay) {
+ static const uint16 kMaxSeqnum = static_cast<uint16>(-1);
+ static const uint16 seqnum_big = 62275;
+ static const uint16 seqnum_small = 10;
+ static const uint16 replay_window = 1024;
+ int out_len;
+
+ EXPECT_TRUE(s1_.SetSend(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+ EXPECT_TRUE(s2_.SetRecv(CS_AES_CM_128_HMAC_SHA1_80, kTestKey1, kTestKeyLen));
+
+ // Initial sequence number.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum_big);
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+
+ // Replay within the 1024 window should succeed.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+ seqnum_big - replay_window + 1);
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+
+ // Replay out side of the 1024 window should fail.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+ seqnum_big - replay_window - 1);
+ EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+
+ // Increment sequence number to a small number.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum_small);
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+
+ // Replay around 0 but out side of the 1024 window should fail.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+ kMaxSeqnum + seqnum_small - replay_window - 1);
+ EXPECT_FALSE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+
+ // Replay around 0 but within the 1024 window should succeed.
+ for (uint16 seqnum = 65000; seqnum < 65003; ++seqnum) {
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2, seqnum);
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+ }
+
+ // Go back to normal sequence nubmer.
+ // NOTE: without the fix in libsrtp, this would fail. This is because
+ // without the fix, the loop above would keep incrementing local sequence
+ // number in libsrtp, eventually the new sequence number would go out side
+ // of the window.
+ talk_base::SetBE16(reinterpret_cast<uint8*>(rtp_packet_) + 2,
+ seqnum_small + 1);
+ EXPECT_TRUE(s1_.ProtectRtp(rtp_packet_, rtp_len_, sizeof(rtp_packet_),
+ &out_len));
+}
+
+class SrtpStatTest
+ : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ SrtpStatTest()
+ : ssrc_(0U),
+ mode_(-1),
+ error_(cricket::SrtpFilter::ERROR_NONE) {
+ srtp_stat_.SignalSrtpError.connect(this, &SrtpStatTest::OnSrtpError);
+ srtp_stat_.set_signal_silent_time(200);
+ }
+
+ protected:
+ void OnSrtpError(uint32 ssrc, cricket::SrtpFilter::Mode mode,
+ cricket::SrtpFilter::Error error) {
+ ssrc_ = ssrc;
+ mode_ = mode;
+ error_ = error;
+ }
+ void Reset() {
+ ssrc_ = 0U;
+ mode_ = -1;
+ error_ = cricket::SrtpFilter::ERROR_NONE;
+ }
+
+ cricket::SrtpStat srtp_stat_;
+ uint32 ssrc_;
+ int mode_;
+ cricket::SrtpFilter::Error error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SrtpStatTest);
+};
+
+TEST_F(SrtpStatTest, TestProtectRtpError) {
+ Reset();
+ srtp_stat_.AddProtectRtpResult(1, err_status_ok);
+ EXPECT_EQ(0U, ssrc_);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ Reset();
+ srtp_stat_.AddProtectRtpResult(1, err_status_auth_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+ Reset();
+ srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+ // Within 200ms, the error will not be triggered.
+ Reset();
+ srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+ EXPECT_EQ(0U, ssrc_);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ // Now the error will be triggered again.
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddProtectRtpResult(1, err_status_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestUnprotectRtpError) {
+ Reset();
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_ok);
+ EXPECT_EQ(0U, ssrc_);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_auth_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_replay_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_replay_old);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+ // Within 200ms, the error will not be triggered.
+ Reset();
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+ EXPECT_EQ(0U, ssrc_);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ // Now the error will be triggered again.
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddUnprotectRtpResult(1, err_status_fail);
+ EXPECT_EQ(1U, ssrc_);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestProtectRtcpError) {
+ Reset();
+ srtp_stat_.AddProtectRtcpResult(err_status_ok);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ Reset();
+ srtp_stat_.AddProtectRtcpResult(err_status_auth_fail);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+ Reset();
+ srtp_stat_.AddProtectRtcpResult(err_status_fail);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+ // Within 200ms, the error will not be triggered.
+ Reset();
+ srtp_stat_.AddProtectRtcpResult(err_status_fail);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ // Now the error will be triggered again.
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddProtectRtcpResult(err_status_fail);
+ EXPECT_EQ(cricket::SrtpFilter::PROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
+
+TEST_F(SrtpStatTest, TestUnprotectRtcpError) {
+ Reset();
+ srtp_stat_.AddUnprotectRtcpResult(err_status_ok);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtcpResult(err_status_auth_fail);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_AUTH, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtcpResult(err_status_replay_fail);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddUnprotectRtcpResult(err_status_replay_fail);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_REPLAY, error_);
+ Reset();
+ srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+ // Within 200ms, the error will not be triggered.
+ Reset();
+ srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+ EXPECT_EQ(-1, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_NONE, error_);
+ // Now the error will be triggered again.
+ Reset();
+ talk_base::Thread::Current()->SleepMs(210);
+ srtp_stat_.AddUnprotectRtcpResult(err_status_fail);
+ EXPECT_EQ(cricket::SrtpFilter::UNPROTECT, mode_);
+ EXPECT_EQ(cricket::SrtpFilter::ERROR_FAIL, error_);
+}
diff --git a/talk/session/phone/ssrcmuxfilter.cc b/talk/session/phone/ssrcmuxfilter.cc
new file mode 100644
index 0000000..d81feab
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter.cc
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/ssrcmuxfilter.h"
+
+#include <algorithm>
+
+#include "talk/base/logging.h"
+#include "talk/session/phone/rtputils.h"
+
+namespace cricket {
+
+static const uint32 kSsrc01 = 0x01;
+
+SsrcMuxFilter::SsrcMuxFilter() {
+}
+
+SsrcMuxFilter::~SsrcMuxFilter() {
+}
+
+bool SsrcMuxFilter::IsActive() const {
+ return !streams_.empty();
+}
+
+bool SsrcMuxFilter::DemuxPacket(const char* data, size_t len, bool rtcp) {
+ uint32 ssrc = 0;
+ if (!rtcp) {
+ GetRtpSsrc(data, len, &ssrc);
+ } else {
+ int pl_type = 0;
+ if (!GetRtcpType(data, len, &pl_type)) return false;
+ if (pl_type == kRtcpTypeSDES) {
+ // SDES packet parsing not supported.
+ LOG(LS_INFO) << "SDES packet received for demux.";
+ return true;
+ } else {
+ if (!GetRtcpSsrc(data, len, &ssrc)) return false;
+ if (ssrc == kSsrc01) {
+ // SSRC 1 has a special meaning and indicates generic feedback on
+ // some systems and should never be dropped. If it is forwarded
+ // incorrectly it will be ignored by lower layers anyway.
+ return true;
+ }
+ }
+ }
+ return FindStream(ssrc);
+}
+
+bool SsrcMuxFilter::AddStream(const StreamParams& stream) {
+ if (GetStreamBySsrc(streams_, stream.first_ssrc(), NULL)) {
+ LOG(LS_WARNING) << "Stream already added to filter";
+ return false;
+ }
+ streams_.push_back(stream);
+ return true;
+}
+
+bool SsrcMuxFilter::RemoveStream(uint32 ssrc) {
+ return RemoveStreamBySsrc(&streams_, ssrc);
+}
+
+bool SsrcMuxFilter::FindStream(uint32 ssrc) const {
+ return (GetStreamBySsrc(streams_, ssrc, NULL));
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/ssrcmuxfilter.h b/talk/session/phone/ssrcmuxfilter.h
new file mode 100644
index 0000000..2f28799
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter.h
@@ -0,0 +1,67 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_SSRCMUXFILTER_H_
+#define TALK_SESSION_PHONE_SSRCMUXFILTER_H_
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/session/phone/streamparams.h"
+
+namespace cricket {
+
+// This class maintains list of recv SSRC's destined for cricket::BaseChannel.
+// In case of single RTP session and single transport channel, all session
+// ( or media) channels share a common transport channel. Hence they all get
+// SignalReadPacket when packet received on transport channel. This requires
+// cricket::BaseChannel to know all the valid sources, else media channel
+// will decode invalid packets.
+class SsrcMuxFilter {
+ public:
+ SsrcMuxFilter();
+ ~SsrcMuxFilter();
+
+ // Whether the rtp mux is active for a sdp session.
+ // Returns true if the filter contains a stream.
+ bool IsActive() const;
+ // Determines packet belongs to valid cricket::BaseChannel.
+ bool DemuxPacket(const char* data, size_t len, bool rtcp);
+ // Adding a valid source to the filter.
+ bool AddStream(const StreamParams& stream);
+ // Removes source from the filter.
+ bool RemoveStream(uint32 ssrc);
+ // Utility method added for unitest.
+ bool FindStream(uint32 ssrc) const;
+
+ private:
+ std::vector<StreamParams> streams_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_SSRCMUXFILTER_H_
diff --git a/talk/session/phone/ssrcmuxfilter_unittest.cc b/talk/session/phone/ssrcmuxfilter_unittest.cc
new file mode 100644
index 0000000..14f4f73
--- /dev/null
+++ b/talk/session/phone/ssrcmuxfilter_unittest.cc
@@ -0,0 +1,184 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/ssrcmuxfilter.h"
+
+static const int kSsrc1 = 0x1111;
+static const int kSsrc2 = 0x2222;
+static const int kSsrc3 = 0x3333;
+
+using cricket::StreamParams;
+
+// SSRC = 0x1111
+static const unsigned char kRtpPacketSsrc1[] = {
+ 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11,
+};
+
+// SSRC = 0x2222
+static const unsigned char kRtpPacketSsrc2[] = {
+ 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22,
+};
+
+// SSRC = 0
+static const unsigned char kRtpPacketInvalidSsrc[] = {
+ 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+// invalid size
+static const unsigned char kRtpPacketTooSmall[] = {
+ 0x80, 0x80, 0x00, 0x00,
+};
+
+// PT = 200 = SR, len = 28, SSRC of sender = 0x0001
+// NTP TS = 0, RTP TS = 0, packet count = 0
+static const unsigned char kRtcpPacketSrSsrc01[] = {
+ 0x80, 0xC8, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+// PT = 200 = SR, len = 28, SSRC of sender = 0x2222
+// NTP TS = 0, RTP TS = 0, packet count = 0
+static const unsigned char kRtcpPacketSrSsrc2[] = {
+ 0x80, 0xC8, 0x00, 0x1B, 0x00, 0x00, 0x22, 0x22,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+// First packet - SR = PT = 200, len = 0, SSRC of sender = 0x1111
+// NTP TS = 0, RTP TS = 0, packet count = 0
+// second packet - SDES = PT = 202, count = 0, SSRC = 0x1111, cname len = 0
+static const unsigned char kRtcpPacketCompoundSrSdesSsrc1[] = {
+ 0x80, 0xC8, 0x00, 0x01, 0x00, 0x00, 0x11, 0x11,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x81, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x01, 0x00,
+};
+
+// SDES = PT = 202, count = 0, SSRC = 0x2222, cname len = 0
+static const unsigned char kRtcpPacketSdesSsrc2[] = {
+ 0x81, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x01, 0x00,
+};
+
+// Packet has only mandatory fixed RTCP header
+static const unsigned char kRtcpPacketFixedHeaderOnly[] = {
+ 0x80, 0xC8, 0x00, 0x00,
+};
+
+// Small packet for SSRC demux.
+static const unsigned char kRtcpPacketTooSmall[] = {
+ 0x80, 0xC8, 0x00, 0x00, 0x00, 0x00,
+};
+
+// PT = 206, FMT = 1, Sender SSRC = 0x1111, Media SSRC = 0x1111
+// No FCI information is needed for PLI.
+static const unsigned char kRtcpPacketNonCompoundRtcpPliFeedback[] = {
+ 0x81, 0xCE, 0x00, 0x0C, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x11, 0x11,
+};
+
+TEST(SsrcMuxFilterTest, AddRemoveStreamTest) {
+ cricket::SsrcMuxFilter ssrc_filter;
+ EXPECT_FALSE(ssrc_filter.IsActive());
+ EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+ StreamParams stream2;
+ stream2.ssrcs.push_back(kSsrc2);
+ stream2.ssrcs.push_back(kSsrc3);
+ EXPECT_TRUE(ssrc_filter.AddStream(stream2));
+
+ EXPECT_TRUE(ssrc_filter.IsActive());
+ EXPECT_TRUE(ssrc_filter.FindStream(kSsrc1));
+ EXPECT_TRUE(ssrc_filter.FindStream(kSsrc2));
+ EXPECT_TRUE(ssrc_filter.FindStream(kSsrc3));
+ EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc1));
+ EXPECT_FALSE(ssrc_filter.FindStream(kSsrc1));
+ EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc3));
+ EXPECT_FALSE(ssrc_filter.RemoveStream(kSsrc2)); // Already removed.
+ EXPECT_FALSE(ssrc_filter.IsActive());
+}
+
+TEST(SsrcMuxFilterTest, RtpPacketTest) {
+ cricket::SsrcMuxFilter ssrc_filter;
+ EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtpPacketSsrc1),
+ sizeof(kRtpPacketSsrc1), false));
+ EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2)));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtpPacketSsrc2),
+ sizeof(kRtpPacketSsrc2), false));
+ EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtpPacketSsrc2),
+ sizeof(kRtpPacketSsrc2), false));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtpPacketInvalidSsrc),
+ sizeof(kRtpPacketInvalidSsrc), false));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtpPacketTooSmall),
+ sizeof(kRtpPacketTooSmall), false));
+}
+
+TEST(SsrcMuxFilterTest, RtcpPacketTest) {
+ cricket::SsrcMuxFilter ssrc_filter;
+ EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc1)));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketCompoundSrSdesSsrc1),
+ sizeof(kRtcpPacketCompoundSrSdesSsrc1), true));
+ EXPECT_TRUE(ssrc_filter.AddStream(StreamParams::CreateLegacy(kSsrc2)));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketSrSsrc2),
+ sizeof(kRtcpPacketSrSsrc2), true));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketSdesSsrc2),
+ sizeof(kRtcpPacketSdesSsrc2), true));
+ EXPECT_TRUE(ssrc_filter.RemoveStream(kSsrc2));
+ // RTCP Packets other than SR and RR are demuxed regardless of SSRC.
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketSdesSsrc2),
+ sizeof(kRtcpPacketSdesSsrc2), true));
+ // RTCP Packets with 'special' SSRC 0x01 are demuxed also
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketSrSsrc01),
+ sizeof(kRtcpPacketSrSsrc01), true));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketSrSsrc2),
+ sizeof(kRtcpPacketSrSsrc2), true));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketFixedHeaderOnly),
+ sizeof(kRtcpPacketFixedHeaderOnly), true));
+ EXPECT_FALSE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketTooSmall),
+ sizeof(kRtcpPacketTooSmall), true));
+ EXPECT_TRUE(ssrc_filter.DemuxPacket(
+ reinterpret_cast<const char*>(kRtcpPacketNonCompoundRtcpPliFeedback),
+ sizeof(kRtcpPacketNonCompoundRtcpPliFeedback), true));
+}
diff --git a/talk/session/phone/streamparams.cc b/talk/session/phone/streamparams.cc
new file mode 100644
index 0000000..415c4c7
--- /dev/null
+++ b/talk/session/phone/streamparams.cc
@@ -0,0 +1,90 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/streamparams.h"
+
+namespace cricket {
+
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+ StreamParams* stream_out) {
+ for (StreamParamsVec::const_iterator stream = streams.begin();
+ stream != streams.end(); ++stream) {
+ if (stream->has_ssrc(ssrc)) {
+ if (stream_out != NULL)
+ *stream_out = *stream;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GetStreamByNickAndName(const StreamParamsVec& streams,
+ const std::string& nick,
+ const std::string& name,
+ StreamParams* stream_out) {
+ for (StreamParamsVec::const_iterator stream = streams.begin();
+ stream != streams.end(); ++stream) {
+ if (stream->nick == nick && stream->name == name) {
+ if (stream_out != NULL)
+ *stream_out = *stream;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc) {
+ bool ret = false;
+ for (StreamParamsVec::iterator stream = streams->begin();
+ stream != streams->end(); ) {
+ if (stream->has_ssrc(ssrc)) {
+ stream = streams->erase(stream);
+ ret = true;
+ } else {
+ ++stream;
+ }
+ }
+ return ret;
+}
+
+bool RemoveStreamByNickAndName(StreamParamsVec* streams,
+ const std::string& nick,
+ const std::string& name) {
+ bool ret = false;
+ for (StreamParamsVec::iterator stream = streams->begin();
+ stream != streams->end(); ) {
+ if (stream->nick == nick && stream->name == name) {
+ stream = streams->erase(stream);
+ ret = true;
+ } else {
+ ++stream;
+ }
+ }
+ return ret;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/streamparams.h b/talk/session/phone/streamparams.h
new file mode 100644
index 0000000..e202f41
--- /dev/null
+++ b/talk/session/phone/streamparams.h
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This file contains structures for describing SSRCs from a media source such
+// as a MediaStreamTrack when it is sent across an RTP session. Multiple media
+// sources may be sent across the same RTP session, each of them will be
+// described by one StreamParams object
+// SsrcGroup is used to describe the relationship between the SSRCs that
+// are used for this media source.
+// E.x: Consider a source that is sent as 3 simulcast streams
+// Let the simulcast elements have SSRC 10, 20, 30.
+// Let each simulcast element use FEC and let the protection packets have
+// SSRC 11,21,31.
+// To describe this 4 SsrcGroups are needed,
+// StreamParams would then contain ssrc = {10,11,20,21,30,31} and
+// ssrc_groups = {{SIM,{10,20,30}, {FEC,{10,11}, {FEC, {20,21}, {FEC {30,31}}}
+// Please see RFC 5576.
+
+#ifndef TALK_SESSION_PHONE_STREAMPARAMS_H_
+#define TALK_SESSION_PHONE_STREAMPARAMS_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+struct SsrcGroup {
+ SsrcGroup(const std::string& usage, const std::vector<uint32>& ssrcs)
+ : semantics(usage), ssrcs(ssrcs) {
+ }
+
+ bool operator==(const SsrcGroup& other) const {
+ return (semantics == other.semantics && ssrcs == other.ssrcs);
+ }
+ bool operator!=(const SsrcGroup &other) const {
+ return !(*this == other);
+ }
+
+ std::string semantics; // e.g FIX, FEC, SIM.
+ std::vector<uint32> ssrcs; // SSRCs of this type.
+};
+
+struct StreamParams {
+ static StreamParams CreateLegacy(uint32 ssrc) {
+ StreamParams stream;
+ stream.ssrcs.push_back(ssrc);
+ return stream;
+ }
+ bool operator==(const StreamParams& other) const {
+ return (nick == other.nick &&
+ name == other.name &&
+ ssrcs == other.ssrcs &&
+ ssrc_groups == other.ssrc_groups &&
+ type == other.type &&
+ display == other.display &&
+ cname == other.cname &&
+ sync_label == sync_label);
+ }
+ bool operator!=(const StreamParams &other) const {
+ return !(*this == other);
+ }
+
+ uint32 first_ssrc() const {
+ if (ssrcs.empty()) {
+ return 0;
+ }
+
+ return ssrcs[0];
+ }
+ bool has_ssrcs() const {
+ return !ssrcs.empty();
+ }
+ bool has_ssrc(uint32 ssrc) const {
+ return std::find(ssrcs.begin(), ssrcs.end(), ssrc) != ssrcs.end();
+ }
+ void add_ssrc(uint32 ssrc) {
+ ssrcs.push_back(ssrc);
+ }
+
+ // Resource of the MUC jid of the participant of with this stream.
+ // For 1:1 calls, should be left empty (which means remote streams
+ // and local streams should not be mixed together).
+ std::string nick;
+ // Unique name of this source (unique per-nick, not for all nicks)
+ std::string name;
+ std::vector<uint32> ssrcs; // All SSRCs for this source
+ std::vector<SsrcGroup> ssrc_groups; // e.g. FID, FEC, SIM
+ // Examples: "camera", "screencast"
+ std::string type;
+ // Friendly name describing stream
+ std::string display;
+ std::string cname; // RTCP CNAME
+ std::string sync_label; // Friendly name of cname.
+};
+
+typedef std::vector<StreamParams> StreamParamsVec;
+
+// Finds the stream in streams with the specified ssrc.
+// If you are only interested in the stream exist it is ok to call this function
+// stream_out = NULL.
+bool GetStreamBySsrc(const StreamParamsVec& streams, uint32 ssrc,
+ StreamParams* stream_out);
+
+// Finds the stream in streams with the specified nick and name.
+// If you are only interested in the stream exist it is ok to call this function
+// stream_out = NULL.
+bool GetStreamByNickAndName(const StreamParamsVec& streams,
+ const std::string& nick,
+ const std::string& name,
+ StreamParams* stream_out);
+
+// Removes the stream with ssrc from streams. Returns true if a stream is
+// removed, false otherwise.
+bool RemoveStreamBySsrc(StreamParamsVec* streams, uint32 ssrc);
+bool RemoveStreamByNickAndName(StreamParamsVec* streams,
+ const std::string& nick,
+ const std::string& name);
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_STREAMPARAMS_H_
diff --git a/talk/session/phone/testdata/captured-320x240-2s-48.frames b/talk/session/phone/testdata/captured-320x240-2s-48.frames
new file mode 100644
index 0000000..292a170
--- /dev/null
+++ b/talk/session/phone/testdata/captured-320x240-2s-48.frames
Binary files differ
diff --git a/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump b/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump
new file mode 100644
index 0000000..ffa521d
--- /dev/null
+++ b/talk/session/phone/testdata/h264-svc-99-640x360.rtpdump
Binary files differ
diff --git a/talk/session/phone/testutils.cc b/talk/session/phone/testutils.cc
new file mode 100644
index 0000000..f427f96
--- /dev/null
+++ b/talk/session/phone/testutils.cc
@@ -0,0 +1,370 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/testutils.h"
+
+#include <math.h>
+
+#ifdef HAVE_YUV
+#include "libyuv/compare.h"
+#endif
+#include "talk/base/bytebuffer.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/rtpdump.h"
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of RawRtpPacket
+/////////////////////////////////////////////////////////////////////////
+void RawRtpPacket::WriteToByteBuffer(
+ uint32 in_ssrc, talk_base::ByteBuffer *buf) const {
+ if (!buf) return;
+
+ buf->WriteUInt8(ver_to_cc);
+ buf->WriteUInt8(m_to_pt);
+ buf->WriteUInt16(sequence_number);
+ buf->WriteUInt32(timestamp);
+ buf->WriteUInt32(in_ssrc);
+ buf->WriteBytes(payload, sizeof(payload));
+}
+
+bool RawRtpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) {
+ if (!buf) return false;
+
+ bool ret = true;
+ ret &= buf->ReadUInt8(&ver_to_cc);
+ ret &= buf->ReadUInt8(&m_to_pt);
+ ret &= buf->ReadUInt16(&sequence_number);
+ ret &= buf->ReadUInt32(×tamp);
+ ret &= buf->ReadUInt32(&ssrc);
+ ret &= buf->ReadBytes(payload, sizeof(payload));
+ return ret;
+}
+
+bool RawRtpPacket::SameExceptSeqNumTimestampSsrc(
+ const RawRtpPacket& packet, uint16 seq, uint32 ts, uint32 ssc) const {
+ return sequence_number == seq &&
+ timestamp == ts &&
+ ver_to_cc == packet.ver_to_cc &&
+ m_to_pt == packet.m_to_pt &&
+ ssrc == ssc &&
+ 0 == memcmp(payload, packet.payload, sizeof(payload));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of RawRtcpPacket
+/////////////////////////////////////////////////////////////////////////
+void RawRtcpPacket::WriteToByteBuffer(talk_base::ByteBuffer *buf) const {
+ if (!buf) return;
+
+ buf->WriteUInt8(ver_to_count);
+ buf->WriteUInt8(type);
+ buf->WriteUInt16(length);
+ buf->WriteBytes(payload, sizeof(payload));
+}
+
+bool RawRtcpPacket::ReadFromByteBuffer(talk_base::ByteBuffer* buf) {
+ if (!buf) return false;
+
+ bool ret = true;
+ ret &= buf->ReadUInt8(&ver_to_count);
+ ret &= buf->ReadUInt8(&type);
+ ret &= buf->ReadUInt16(&length);
+ ret &= buf->ReadBytes(payload, sizeof(payload));
+ return ret;
+}
+
+bool RawRtcpPacket::EqualsTo(const RawRtcpPacket& packet) const {
+ return ver_to_count == packet.ver_to_count &&
+ type == packet.type &&
+ length == packet.length &&
+ 0 == memcmp(payload, packet.payload, sizeof(payload));
+}
+
+/////////////////////////////////////////////////////////////////////////
+// Implementation of class RtpTestUtility
+/////////////////////////////////////////////////////////////////////////
+const RawRtpPacket RtpTestUtility::kTestRawRtpPackets[] = {
+ {0x80, 0, 0, 0, RtpTestUtility::kDefaultSsrc, "RTP frame 0"},
+ {0x80, 0, 1, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"},
+ {0x80, 0, 2, 30, RtpTestUtility::kDefaultSsrc, "RTP frame 1"},
+ {0x80, 0, 3, 60, RtpTestUtility::kDefaultSsrc, "RTP frame 2"}
+};
+const RawRtcpPacket RtpTestUtility::kTestRawRtcpPackets[] = {
+ // The Version is 2, the Length is 2, and the payload has 8 bytes.
+ {0x80, 0, 2, "RTCP0000"},
+ {0x80, 0, 2, "RTCP0001"},
+ {0x80, 0, 2, "RTCP0002"},
+ {0x80, 0, 2, "RTCP0003"},
+};
+
+size_t RtpTestUtility::GetTestPacketCount() {
+ return talk_base::_min(
+ ARRAY_SIZE(kTestRawRtpPackets),
+ ARRAY_SIZE(kTestRawRtcpPackets));
+}
+
+bool RtpTestUtility::WriteTestPackets(
+ size_t count, bool rtcp, uint32 rtp_ssrc, RtpDumpWriter* writer) {
+ if (!writer || count > GetTestPacketCount()) return false;
+
+ bool result = true;
+ uint32 elapsed_time_ms = 0;
+ for (size_t i = 0; i < count && result; ++i) {
+ talk_base::ByteBuffer buf;
+ if (rtcp) {
+ kTestRawRtcpPackets[i].WriteToByteBuffer(&buf);
+ } else {
+ kTestRawRtpPackets[i].WriteToByteBuffer(rtp_ssrc, &buf);
+ }
+
+ RtpDumpPacket dump_packet(buf.Data(), buf.Length(), elapsed_time_ms, rtcp);
+ elapsed_time_ms += kElapsedTimeInterval;
+ result &= (talk_base::SR_SUCCESS == writer->WritePacket(dump_packet));
+ }
+ return result;
+}
+
+bool RtpTestUtility::VerifyTestPacketsFromStream(
+ size_t count, talk_base::StreamInterface* stream, uint32 ssrc) {
+ if (!stream) return false;
+
+ uint32 prev_elapsed_time = 0;
+ bool result = true;
+ stream->Rewind();
+ RtpDumpLoopReader reader(stream);
+ for (size_t i = 0; i < count && result; ++i) {
+ // Which loop and which index in the loop are we reading now.
+ size_t loop = i / GetTestPacketCount();
+ size_t index = i % GetTestPacketCount();
+
+ RtpDumpPacket packet;
+ result &= (talk_base::SR_SUCCESS == reader.ReadPacket(&packet));
+ // Check the elapsed time of the dump packet.
+ result &= (packet.elapsed_time >= prev_elapsed_time);
+ prev_elapsed_time = packet.elapsed_time;
+
+ // Check the RTP or RTCP packet.
+ talk_base::ByteBuffer buf(reinterpret_cast<const char*>(&packet.data[0]),
+ packet.data.size());
+ if (packet.is_rtcp) {
+ // RTCP packet.
+ RawRtcpPacket rtcp_packet;
+ result &= rtcp_packet.ReadFromByteBuffer(&buf);
+ result &= rtcp_packet.EqualsTo(kTestRawRtcpPackets[index]);
+ } else {
+ // RTP packet.
+ RawRtpPacket rtp_packet;
+ result &= rtp_packet.ReadFromByteBuffer(&buf);
+ result &= rtp_packet.SameExceptSeqNumTimestampSsrc(
+ kTestRawRtpPackets[index],
+ kTestRawRtpPackets[index].sequence_number +
+ loop * GetTestPacketCount(),
+ kTestRawRtpPackets[index].timestamp + loop * kRtpTimestampIncrease,
+ ssrc);
+ }
+ }
+
+ stream->Rewind();
+ return result;
+}
+
+bool RtpTestUtility::VerifyPacket(const RtpDumpPacket* dump,
+ const RawRtpPacket* raw,
+ bool header_only) {
+ if (!dump || !raw) return false;
+
+ talk_base::ByteBuffer buf;
+ raw->WriteToByteBuffer(RtpTestUtility::kDefaultSsrc, &buf);
+
+ if (header_only) {
+ size_t header_len = 0;
+ dump->GetRtpHeaderLen(&header_len);
+ return header_len == dump->data.size() &&
+ buf.Length() > dump->data.size() &&
+ 0 == memcmp(buf.Data(), &dump->data[0], dump->data.size());
+ } else {
+ return buf.Length() == dump->data.size() &&
+ 0 == memcmp(buf.Data(), &dump->data[0], dump->data.size());
+ }
+}
+
+// Implementation of VideoCaptureListener.
+VideoCapturerListener::VideoCapturerListener(VideoCapturer* capturer)
+ : start_result_(CR_PENDING),
+ frame_count_(0),
+ frame_fourcc_(0),
+ frame_width_(0),
+ frame_height_(0),
+ frame_size_(0),
+ resolution_changed_(false) {
+ capturer->SignalStartResult.connect(this,
+ &VideoCapturerListener::OnStartResult);
+ capturer->SignalFrameCaptured.connect(this,
+ &VideoCapturerListener::OnFrameCaptured);
+ capturer->SignalCaptureEvent.connect(this,
+ &VideoCapturerListener::OnCaptureEvent);
+}
+
+void VideoCapturerListener::OnStartResult(VideoCapturer* capturer,
+ CaptureResult result) {
+ start_result_ = result;
+}
+
+void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer,
+ const CapturedFrame* frame) {
+ ++frame_count_;
+ if (1 == frame_count_) {
+ frame_fourcc_ = frame->fourcc;
+ frame_width_ = frame->width;
+ frame_height_ = frame->height;
+ frame_size_ = frame->data_size;
+ } else if (frame_width_ != frame->width || frame_height_ != frame->height) {
+ resolution_changed_ = true;
+ }
+}
+
+void VideoCapturerListener::OnCaptureEvent(VideoCapturer* capturer,
+ CaptureEvent ev) {
+}
+
+
+// Returns the absolute path to a file in the testdata/ directory.
+std::string GetTestFilePath(const std::string& filename) {
+ // Locate test data directory.
+ talk_base::Pathname path = GetTalkDirectory();
+ EXPECT_FALSE(path.empty()); // must be run from inside "talk"
+ path.AppendFolder("session");
+ path.AppendFolder("phone");
+ path.AppendFolder("testdata");
+ path.SetFilename(filename);
+ return path.pathname();
+}
+
+// PSNR formula: psnr = 10 * log10 (Peak Signal^2 / mse)
+// sse is set to a small number for identical frames or sse == 0
+double ComputePSNR(double sse, double size) {
+ if (sse <= 0.)
+ sse = 65025.0 * size / pow(10., 128./10.); // produces max PSNR of 128
+ return 10.0 * log10(65025.0 * size / sse);
+}
+
+double ComputeSumSquareError(const uint8 *org, const uint8 *rec, int size) {
+#ifdef HAVE_YUV
+ return static_cast<double>(libyuv::ComputeSumSquareError(org, rec, size));
+#else
+ double sse = 0.;
+ for (int j = 0; j < size; ++j) {
+ const int diff = static_cast<int>(org[j]) - static_cast<int>(rec[j]);
+ sse += static_cast<double>(diff * diff);
+ }
+ return sse;
+#endif
+}
+
+// Loads the image with the specified prefix and size into |out|.
+bool LoadPlanarYuvTestImage(const std::string& prefix,
+ int width, int height, uint8* out) {
+ std::stringstream ss;
+ ss << prefix << "." << width << "x" << height << "_P420.yuv";
+
+ talk_base::scoped_ptr<talk_base::FileStream> stream(
+ talk_base::Filesystem::OpenFile(talk_base::Pathname(
+ GetTestFilePath(ss.str())), "rb"));
+ if (!stream.get()) {
+ return false;
+ }
+
+ talk_base::StreamResult res =
+ stream->ReadAll(out, I420_SIZE(width, height), NULL, NULL);
+ return (res == talk_base::SR_SUCCESS);
+}
+
+// Dumps the YUV image out to a file, for visual inspection.
+// PYUV tool can be used to view dump files.
+void DumpPlanarYuvTestImage(const std::string& prefix, const uint8* img,
+ int w, int h) {
+ talk_base::FileStream fs;
+ char filename[256];
+ talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
+ prefix.c_str(), w, h);
+ fs.Open(filename, "wb", NULL);
+ fs.Write(img, I420_SIZE(w, h), NULL, NULL);
+}
+
+// Dumps the ARGB image out to a file, for visual inspection.
+// ffplay tool can be used to view dump files.
+void DumpPlanarArgbTestImage(const std::string& prefix, const uint8* img,
+ int w, int h) {
+ talk_base::FileStream fs;
+ char filename[256];
+ talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_ARGB.raw",
+ prefix.c_str(), w, h);
+ fs.Open(filename, "wb", NULL);
+ fs.Write(img, ARGB_SIZE(w, h), NULL, NULL);
+}
+
+bool VideoFrameEqual(const VideoFrame* frame0, const VideoFrame* frame1) {
+ const uint8* y0 = frame0->GetYPlane();
+ const uint8* u0 = frame0->GetUPlane();
+ const uint8* v0 = frame0->GetVPlane();
+ const uint8* y1 = frame1->GetYPlane();
+ const uint8* u1 = frame1->GetUPlane();
+ const uint8* v1 = frame1->GetVPlane();
+
+ for (size_t i = 0; i < frame0->GetHeight(); ++i) {
+ if (0 != memcmp(y0, y1, frame0->GetWidth())) {
+ return false;
+ }
+ y0 += frame0->GetYPitch();
+ y1 += frame1->GetYPitch();
+ }
+
+ for (size_t i = 0; i < frame0->GetChromaHeight(); ++i) {
+ if (0 != memcmp(u0, u1, frame0->GetChromaWidth())) {
+ return false;
+ }
+ if (0 != memcmp(v0, v1, frame0->GetChromaWidth())) {
+ return false;
+ }
+ u0 += frame0->GetUPitch();
+ v0 += frame0->GetVPitch();
+ u1 += frame1->GetUPitch();
+ v1 += frame1->GetVPitch();
+ }
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/testutils.h b/talk/session/phone/testutils.h
new file mode 100644
index 0000000..f5556e7
--- /dev/null
+++ b/talk/session/phone/testutils.h
@@ -0,0 +1,209 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_TESTUTILS_H_
+#define TALK_SESSION_PHONE_TESTUTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/window.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/videocapturer.h"
+
+namespace talk_base {
+class ByteBuffer;
+class StreamInterface;
+}
+
+namespace cricket {
+
+// Returns size of 420 image with rounding on chroma for odd sizes.
+#define I420_SIZE(w, h) (w * h + (((w + 1) / 2) * ((h + 1) / 2)) * 2)
+// Returns size of ARGB image.
+#define ARGB_SIZE(w, h) (w * h * 4)
+
+template <class T> inline std::vector<T> MakeVector(const T a[], size_t s) {
+ return std::vector<T>(a, a + s);
+}
+#define MAKE_VECTOR(a) cricket::MakeVector(a, ARRAY_SIZE(a))
+
+struct RtpDumpPacket;
+class RtpDumpWriter;
+class VideoFrame;
+
+struct RawRtpPacket {
+ void WriteToByteBuffer(uint32 in_ssrc, talk_base::ByteBuffer* buf) const;
+ bool ReadFromByteBuffer(talk_base::ByteBuffer* buf);
+ // Check if this packet is the same as the specified packet except the
+ // sequence number and timestamp, which should be the same as the specified
+ // parameters.
+ bool SameExceptSeqNumTimestampSsrc(
+ const RawRtpPacket& packet, uint16 seq, uint32 ts, uint32 ssc) const;
+ int size() const { return 28; }
+
+ uint8 ver_to_cc;
+ uint8 m_to_pt;
+ uint16 sequence_number;
+ uint32 timestamp;
+ uint32 ssrc;
+ char payload[16];
+};
+
+struct RawRtcpPacket {
+ void WriteToByteBuffer(talk_base::ByteBuffer* buf) const;
+ bool ReadFromByteBuffer(talk_base::ByteBuffer* buf);
+ bool EqualsTo(const RawRtcpPacket& packet) const;
+
+ uint8 ver_to_count;
+ uint8 type;
+ uint16 length;
+ char payload[16];
+};
+
+class RtpTestUtility {
+ public:
+ static size_t GetTestPacketCount();
+
+ // Write the first count number of kTestRawRtcpPackets or kTestRawRtpPackets,
+ // depending on the flag rtcp. If it is RTP, use the specified SSRC. Return
+ // true if successful.
+ static bool WriteTestPackets(
+ size_t count, bool rtcp, uint32 rtp_ssrc, RtpDumpWriter* writer);
+
+ // Loop read the first count number of packets from the specified stream.
+ // Verify the elapsed time of the dump packets increase monotonically. If the
+ // stream is a RTP stream, verify the RTP sequence number, timestamp, and
+ // payload. If the stream is a RTCP stream, verify the RTCP header and
+ // payload.
+ static bool VerifyTestPacketsFromStream(
+ size_t count, talk_base::StreamInterface* stream, uint32 ssrc);
+
+ // Verify the dump packet is the same as the raw RTP packet.
+ static bool VerifyPacket(const RtpDumpPacket* dump,
+ const RawRtpPacket* raw,
+ bool header_only);
+
+ static const uint32 kDefaultSsrc = 1;
+ static const uint32 kRtpTimestampIncrease = 90;
+ static const uint32 kDefaultTimeIncrease = 30;
+ static const uint32 kElapsedTimeInterval = 10;
+ static const RawRtpPacket kTestRawRtpPackets[];
+ static const RawRtcpPacket kTestRawRtcpPackets[];
+
+ private:
+ RtpTestUtility() {}
+};
+
+// Test helper for testing VideoCapturer implementations.
+class VideoCapturerListener : public sigslot::has_slots<> {
+ public:
+ explicit VideoCapturerListener(VideoCapturer* cap);
+
+ CaptureResult start_result() const { return start_result_; }
+ int frame_count() const { return frame_count_; }
+ uint32 frame_fourcc() const { return frame_fourcc_; }
+ int frame_width() const { return frame_width_; }
+ int frame_height() const { return frame_height_; }
+ uint32 frame_size() const { return frame_size_; }
+ bool resolution_changed() const { return resolution_changed_; }
+
+ void OnStartResult(VideoCapturer* capturer, CaptureResult result);
+ void OnFrameCaptured(VideoCapturer* capturer, const CapturedFrame* frame);
+ void OnCaptureEvent(VideoCapturer* capturer, CaptureEvent ev);
+
+ private:
+ CaptureResult start_result_;
+ int frame_count_;
+ uint32 frame_fourcc_;
+ int frame_width_;
+ int frame_height_;
+ uint32 frame_size_;
+ bool resolution_changed_;
+};
+
+class ScreencastEventCatcher : public sigslot::has_slots<> {
+ public:
+ ScreencastEventCatcher() : ssrc_(0), ev_(talk_base::WE_RESIZE) { }
+ uint32 ssrc() const { return ssrc_; }
+ talk_base::WindowEvent event() const { return ev_; }
+ void OnEvent(uint32 ssrc, talk_base::WindowEvent ev) {
+ ssrc_ = ssrc;
+ ev_ = ev;
+ }
+ private:
+ uint32 ssrc_;
+ talk_base::WindowEvent ev_;
+};
+
+class VideoMediaErrorCatcher : public sigslot::has_slots<> {
+ public:
+ VideoMediaErrorCatcher() : ssrc_(0), error_(VideoMediaChannel::ERROR_NONE) { }
+ uint32 ssrc() const { return ssrc_; }
+ VideoMediaChannel::Error error() const { return error_; }
+ void OnError(uint32 ssrc, VideoMediaChannel::Error error) {
+ ssrc_ = ssrc;
+ error_ = error;
+ }
+ private:
+ uint32 ssrc_;
+ VideoMediaChannel::Error error_;
+};
+
+// Returns the absolute path to a file in the testdata/ directory.
+std::string GetTestFilePath(const std::string& filename);
+
+// PSNR formula: psnr = 10 * log10 (Peak Signal^2 / mse)
+// sse is set to a small number for identical frames or sse == 0
+double ComputePSNR(double sse, double size);
+
+double ComputeSumSquareError(const uint8 *org, const uint8 *rec,
+ int size);
+
+// Loads the image with the specified prefix and size into |out|.
+bool LoadPlanarYuvTestImage(const std::string& prefix,
+ int width, int height, uint8* out);
+
+// Dumps the YUV image out to a file, for visual inspection.
+// PYUV tool can be used to view dump files.
+void DumpPlanarYuvTestImage(const std::string& prefix, const uint8* img,
+ int w, int h);
+
+// Dumps the ARGB image out to a file, for visual inspection.
+// ffplay tool can be used to view dump files.
+void DumpPlanarArgbTestImage(const std::string& prefix, const uint8* img,
+ int w, int h);
+
+// Compare two I420 frames.
+bool VideoFrameEqual(const VideoFrame* frame0, const VideoFrame* frame1);
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_TESTUTILS_H_
diff --git a/talk/session/phone/videoadapter.cc b/talk/session/phone/videoadapter.cc
new file mode 100644
index 0000000..4313fc7
--- /dev/null
+++ b/talk/session/phone/videoadapter.cc
@@ -0,0 +1,497 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/session/phone/videoadapter.h"
+
+#include <limits.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/timeutils.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+// TODO: Make downgrades settable
+static const int kMaxCpuDowngrades = 2; // Downgrade at most 2 times for CPU.
+static const int kDefaultDowngradeWaitTimeMs = 2000;
+
+// Cpu system load thresholds relative to max cpus.
+static const float kHighSystemThreshold = 0.95f;
+static const float kLowSystemThreshold = 0.75f;
+
+// Cpu process load thresholds relative to current cpus.
+static const float kMediumProcessThreshold = 0.50f;
+
+// TODO: Consider making scale factor table settable, to allow
+// application to select quality vs performance tradeoff.
+// List of scale factors that adapter will scale by.
+#if defined(IOS) || defined(ANDROID)
+// Mobile needs 1/4 scale for VGA (640x360) to QQVGA (160x90)
+// or 1/4 scale for HVGA (480x270) to QQHVGA (120x67)
+static const int kMinNumPixels = 120 * 67;
+static float kScaleFactors[] = {
+ 1.f, // full size
+ 3.f/4.f, // 3/4 scale
+ 1.f/2.f, // 1/2 scale
+ 3.f/8.f, // 3/8 scale
+ 1.f/4.f, // 1/4 scale
+};
+#else
+// PC needs 1/8 scale for HD (1280x720) to QQVGA (160x90)
+static const int kMinNumPixels = 160 * 100;
+static float kScaleFactors[] = {
+ 1.f, // full size
+ 3.f/4.f, // 3/4 scale
+ 1.f/2.f, // 1/2 scale
+ 3.f/8.f, // 3/8 scale
+ 1.f/4.f, // 1/4 scale
+ 3.f/16.f, // 3/16 scale
+ 1.f/8.f // 1/8 scale
+};
+#endif
+
+// Find scale factor that applied to width and height, is best match
+// to num_pixels.
+float VideoAdapter::FindClosestScale(int width, int height,
+ int target_num_pixels) {
+ if (!target_num_pixels) {
+ return 0.f;
+ }
+ int best_distance = INT_MAX;
+ int best_index = 0; // default to unscaled
+ for (size_t i = 0u; i < ARRAY_SIZE(kScaleFactors); ++i) {
+ int test_num_pixels = static_cast<int>(width * kScaleFactors[i] *
+ height * kScaleFactors[i]);
+ int diff = test_num_pixels - target_num_pixels;
+ if (diff < 0) {
+ diff = -diff;
+ }
+ if (diff < best_distance) {
+ best_distance = diff;
+ best_index = i;
+ if (!best_distance) { // Found exact match
+ break;
+ }
+ }
+ }
+ return kScaleFactors[best_index];
+}
+
+// There are several frame sizes used by Adapter. This explains them
+// input_format - set once by server to frame size expected from the camera.
+// output_format - size that output would like to be. Includes framerate.
+// output_num_pixels - size that output should be constrained to. Used to
+// compute output_format from in_frame.
+// in_frame - actual camera captured frame size, which is typically the same
+// as input_format. This can also be rotated or cropped for aspect ratio.
+// out_frame - actual frame output by adapter. Should be a direct scale of
+// in_frame maintaining rotation and aspect ratio.
+// OnOutputFormatRequest - server requests you send this resolution based on
+// view requests.
+// OnEncoderResolutionRequest - encoder requests you send this resolution based
+// on bandwidth
+// OnCpuLoadUpdated - cpu monitor requests you send this resolution based on
+// cpu load.
+
+///////////////////////////////////////////////////////////////////////
+// Implementation of VideoAdapter
+VideoAdapter::VideoAdapter()
+ : output_num_pixels_(0),
+ black_output_(false),
+ is_black_(false),
+ drop_frame_count_(0) {
+}
+
+VideoAdapter::~VideoAdapter() {
+}
+
+// TODO: Consider SetInputFormat and SetOutputFormat without
+// VideoFormat.
+void VideoAdapter::SetInputFormat(const VideoFormat& format) {
+ talk_base::CritScope cs(&critical_section_);
+ input_format_ = format;
+ output_format_.interval = talk_base::_max(
+ output_format_.interval, input_format_.interval);
+}
+
+void VideoAdapter::SetOutputFormat(const VideoFormat& format) {
+ talk_base::CritScope cs(&critical_section_);
+ output_format_ = format;
+ output_num_pixels_ = output_format_.width * output_format_.height;
+ output_format_.interval = talk_base::_max(
+ output_format_.interval, input_format_.interval);
+ drop_frame_count_ = 0;
+}
+
+const VideoFormat& VideoAdapter::input_format() {
+ talk_base::CritScope cs(&critical_section_);
+ return input_format_;
+}
+
+const VideoFormat& VideoAdapter::output_format() {
+ talk_base::CritScope cs(&critical_section_);
+ return output_format_;
+}
+
+void VideoAdapter::SetBlackOutput(bool black) {
+ talk_base::CritScope cs(&critical_section_);
+ black_output_ = black;
+}
+
+// Constrain output resolution to this many pixels overall
+void VideoAdapter::SetOutputNumPixels(int num_pixels) {
+ output_num_pixels_ = num_pixels;
+}
+
+int VideoAdapter::GetOutputNumPixels() const {
+ return output_num_pixels_;
+}
+
+bool VideoAdapter::AdaptFrame(const VideoFrame* in_frame,
+ const VideoFrame** out_frame) {
+ talk_base::CritScope cs(&critical_section_);
+
+ if (!in_frame || !out_frame || input_format_.IsSize0x0()) {
+ return false;
+ }
+
+ // Drop the input frame if necessary.
+ bool should_drop = false;
+ if (!output_num_pixels_) {
+ // Drop all frames as the output format is 0x0.
+ should_drop = true;
+ } else {
+ // Drop some frames based on the ratio of the input fps and the output fps.
+ // We assume that the output fps is a factor of the input fps. In other
+ // words, the output interval is divided by the input interval evenly.
+ should_drop = (drop_frame_count_ > 0);
+ if (input_format_.interval > 0 &&
+ output_format_.interval > input_format_.interval) {
+ ++drop_frame_count_;
+ drop_frame_count_ %= output_format_.interval / input_format_.interval;
+ }
+ }
+
+ if (output_num_pixels_) {
+ float scale = VideoAdapter::FindClosestScale(in_frame->GetWidth(),
+ in_frame->GetHeight(),
+ output_num_pixels_);
+ output_format_.width = static_cast<int>(in_frame->GetWidth() * scale);
+ output_format_.height = static_cast<int>(in_frame->GetHeight() * scale);
+ }
+
+ if (should_drop) {
+ *out_frame = NULL;
+ return true;
+ }
+
+ if (!StretchToOutputFrame(in_frame)) {
+ return false;
+ }
+
+ *out_frame = output_frame_.get();
+ return true;
+}
+
+bool VideoAdapter::StretchToOutputFrame(const VideoFrame* in_frame) {
+ int output_width = output_format_.width;
+ int output_height = output_format_.height;
+
+ // Create and stretch the output frame if it has not been created yet or its
+ // size is not same as the expected.
+ bool stretched = false;
+ if (!output_frame_.get() ||
+ output_frame_->GetWidth() != static_cast<size_t>(output_width) ||
+ output_frame_->GetHeight() != static_cast<size_t>(output_height)) {
+ output_frame_.reset(
+ in_frame->Stretch(output_width, output_height, true, true));
+ if (!output_frame_.get()) {
+ LOG(LS_WARNING) << "Adapter failed to stretch frame to "
+ << output_width << "x" << output_height;
+ return false;
+ }
+ stretched = true;
+ is_black_ = false;
+ }
+
+ if (!black_output_) {
+ if (!stretched) {
+ // The output frame does not need to be blacken and has not been stretched
+ // from the input frame yet, stretch the input frame. This is the most
+ // common case.
+ in_frame->StretchToFrame(output_frame_.get(), true, true);
+ }
+ is_black_ = false;
+ } else {
+ if (!is_black_) {
+ output_frame_->SetToBlack();
+ is_black_ = true;
+ }
+ output_frame_->SetElapsedTime(in_frame->GetElapsedTime());
+ output_frame_->SetTimeStamp(in_frame->GetTimeStamp());
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////
+// Implementation of CoordinatedVideoAdapter
+CoordinatedVideoAdapter::CoordinatedVideoAdapter()
+ : cpu_adaptation_(false),
+ gd_adaptation_(true),
+ view_adaptation_(true),
+ cpu_downgrade_count_(0),
+ cpu_downgrade_wait_time_(0),
+ view_desired_num_pixels_(INT_MAX),
+ view_desired_interval_(0),
+ encoder_desired_num_pixels_(INT_MAX),
+ cpu_desired_num_pixels_(INT_MAX) {
+}
+
+// Helper function to UPGRADE or DOWNGRADE a number of pixels
+void CoordinatedVideoAdapter::StepPixelCount(
+ CoordinatedVideoAdapter::AdaptRequest request,
+ int* num_pixels) {
+ switch (request) {
+ case CoordinatedVideoAdapter::DOWNGRADE:
+ *num_pixels /= 2;
+ break;
+
+ case CoordinatedVideoAdapter::UPGRADE:
+ *num_pixels *= 2;
+ break;
+
+ default: // No change in pixel count
+ break;
+ }
+ return;
+}
+
+// Find the adaptation request of the cpu based on the load. Return UPGRADE if
+// the load is low, DOWNGRADE if the load is high, and KEEP otherwise.
+CoordinatedVideoAdapter::AdaptRequest CoordinatedVideoAdapter::FindCpuRequest(
+ int current_cpus, int max_cpus,
+ float process_load, float system_load) {
+ // Downgrade if system is high and plugin is at least more than midrange.
+ if (system_load >= kHighSystemThreshold * max_cpus &&
+ process_load >= kMediumProcessThreshold * current_cpus) {
+ return CoordinatedVideoAdapter::DOWNGRADE;
+ // Upgrade if system is low.
+ } else if (system_load < kLowSystemThreshold * max_cpus) {
+ return CoordinatedVideoAdapter::UPGRADE;
+ }
+ return CoordinatedVideoAdapter::KEEP;
+}
+
+// A remote view request for a new resolution.
+void CoordinatedVideoAdapter::OnOutputFormatRequest(const VideoFormat& format) {
+ talk_base::CritScope cs(&request_critical_section_);
+ if (!view_adaptation_) {
+ return;
+ }
+ // Set output for initial aspect ratio in mediachannel unittests.
+ int old_num_pixels = GetOutputNumPixels();
+ SetOutputFormat(format);
+ SetOutputNumPixels(old_num_pixels);
+ view_desired_num_pixels_ = format.width * format.height;
+ view_desired_interval_ = format.interval;
+ bool changed = AdaptToMinimumFormat();
+ LOG(LS_INFO) << "VAdapt View Request: "
+ << format.width << "x" << format.height
+ << " Pixels: " << view_desired_num_pixels_
+ << " Changed: " << (changed ? "true" : "false");
+}
+
+// A Bandwidth GD request for new resolution
+void CoordinatedVideoAdapter::OnEncoderResolutionRequest(
+ int width, int height, AdaptRequest request) {
+ talk_base::CritScope cs(&request_critical_section_);
+ if (!gd_adaptation_) {
+ return;
+ }
+ if (KEEP != request) {
+ int new_encoder_desired_num_pixels = width * height;
+ int old_num_pixels = GetOutputNumPixels();
+ if (new_encoder_desired_num_pixels != old_num_pixels) {
+ LOG(LS_VERBOSE) << "VAdapt GD resolution stale. Ignored";
+ } else {
+ // Update the encoder desired format based on the request.
+ encoder_desired_num_pixels_ = new_encoder_desired_num_pixels;
+ StepPixelCount(request, &encoder_desired_num_pixels_);
+ }
+ }
+ bool changed = AdaptToMinimumFormat();
+ LOG(LS_INFO) << "VAdapt GD Request: "
+ << (DOWNGRADE == request ? "down" :
+ (UPGRADE == request ? "up" : "keep"))
+ << " From: " << width << "x" << height
+ << " Pixels: " << encoder_desired_num_pixels_
+ << " Changed: " << (changed ? "true" : "false");
+}
+
+// A CPU request for new resolution
+void CoordinatedVideoAdapter::OnCpuLoadUpdated(
+ int current_cpus, int max_cpus, float process_load, float system_load) {
+ talk_base::CritScope cs(&request_critical_section_);
+ if (!cpu_adaptation_) {
+ return;
+ }
+ AdaptRequest request = FindCpuRequest(current_cpus, max_cpus,
+ process_load, system_load);
+ // Update how many times we have downgraded due to the cpu load.
+ switch (request) {
+ case DOWNGRADE:
+ if (cpu_downgrade_count_ < kMaxCpuDowngrades) {
+ // Ignore downgrades if we have downgraded the maximum times or we just
+ // downgraded in a short time.
+ if (cpu_downgrade_wait_time_ != 0 &&
+ talk_base::TimeIsLater(talk_base::Time(),
+ cpu_downgrade_wait_time_)) {
+ LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade until "
+ << talk_base::TimeUntil(cpu_downgrade_wait_time_)
+ << " ms.";
+ request = KEEP;
+ } else {
+ ++cpu_downgrade_count_;
+ }
+ } else {
+ LOG(LS_VERBOSE) << "VAdapt CPU load high but do not downgrade "
+ "because maximum downgrades reached";
+ }
+ break;
+ case UPGRADE:
+ if (cpu_downgrade_count_ > 0) {
+ bool is_min = IsMinimumFormat(cpu_desired_num_pixels_);
+ if (is_min) {
+ --cpu_downgrade_count_;
+ } else {
+ LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
+ "because cpu is not limiting resolution";
+ }
+ } else {
+ LOG(LS_VERBOSE) << "VAdapt CPU load low but do not upgrade "
+ "because minimum downgrades reached";
+ }
+ break;
+ case KEEP:
+ default:
+ break;
+ }
+ if (KEEP != request) {
+ // TODO: compute stepping up/down from OutputNumPixels but
+ // clamp to inputpixels / 4 (2 steps)
+ cpu_desired_num_pixels_ = static_cast<int>(
+ input_format().width * input_format().height >> cpu_downgrade_count_);
+ }
+ bool changed = AdaptToMinimumFormat();
+ LOG(LS_INFO) << "VAdapt CPU Request: "
+ << (DOWNGRADE == request ? "down" :
+ (UPGRADE == request ? "up" : "keep"))
+ << " Process: " << process_load
+ << " System: " << system_load
+ << " Steps: " << cpu_downgrade_count_
+ << " Changed: " << (changed ? "true" : "false");
+}
+
+// Called by cpu adapter on up requests.
+bool CoordinatedVideoAdapter::IsMinimumFormat(int pixels) {
+ // Find closest scale factor that matches input resolution to min_num_pixels
+ // and set that for output resolution. This is not needed for VideoAdapter,
+ // but provides feedback to unittests and users on expected resolution.
+ // Actual resolution is based on input frame.
+ VideoFormat new_output = output_format();
+ VideoFormat input = input_format();
+ if (input_format().IsSize0x0()) {
+ input = new_output;
+ }
+ float scale = 1.0f;
+ if (!input.IsSize0x0()) {
+ scale = FindClosestScale(input.width,
+ input.height,
+ pixels);
+ }
+ new_output.width = static_cast<int>(input.width * scale);
+ new_output.height = static_cast<int>(input.height * scale);
+ int new_pixels = new_output.width * new_output.height;
+ int num_pixels = GetOutputNumPixels();
+ return new_pixels <= num_pixels;
+}
+
+// Called by all coordinators when there is a change.
+bool CoordinatedVideoAdapter::AdaptToMinimumFormat() {
+ int old_num_pixels = GetOutputNumPixels();
+ // Get the min of the formats that the server, encoder, and cpu wants.
+ int min_num_pixels = view_desired_num_pixels_;
+ if (encoder_desired_num_pixels_ &&
+ (encoder_desired_num_pixels_ < min_num_pixels)) {
+ min_num_pixels = encoder_desired_num_pixels_;
+ }
+ if (cpu_adaptation_ && cpu_desired_num_pixels_ &&
+ (cpu_desired_num_pixels_ < min_num_pixels)) {
+ min_num_pixels = cpu_desired_num_pixels_;
+ // Update the cpu_downgrade_wait_time_ if we are going to downgrade video.
+ cpu_downgrade_wait_time_ =
+ talk_base::TimeAfter(kDefaultDowngradeWaitTimeMs);
+ }
+ // prevent going below QQVGA
+ if (min_num_pixels > 0 && min_num_pixels < kMinNumPixels) {
+ min_num_pixels = kMinNumPixels;
+ }
+ SetOutputNumPixels(min_num_pixels);
+
+ // Find closest scale factor that matches input resolution to min_num_pixels
+ // and set that for output resolution. This is not needed for VideoAdapter,
+ // but provides feedback to unittests and users on expected resolution.
+ // Actual resolution is based on input frame.
+ VideoFormat new_output = output_format();
+ VideoFormat input = input_format();
+ if (input_format().IsSize0x0()) {
+ input = new_output;
+ }
+ float scale = 1.0f;
+ if (!input.IsSize0x0()) {
+ scale = FindClosestScale(input.width,
+ input.height,
+ min_num_pixels);
+ }
+ new_output.width = static_cast<int>(input.width * scale);
+ new_output.height = static_cast<int>(input.height * scale);
+ new_output.interval = view_desired_interval_;
+ SetOutputFormat(new_output);
+ int new_num_pixels = GetOutputNumPixels();
+ bool changed = new_num_pixels != old_num_pixels;
+
+ LOG(LS_VERBOSE) << "VAdapt Status View: " << view_desired_num_pixels_
+ << " GD: " << encoder_desired_num_pixels_
+ << " CPU: " << cpu_desired_num_pixels_
+ << " Pixels: " << min_num_pixels
+ << " Scale: " << scale
+ << " Resolution: " << new_output.width
+ << "x" << new_output.height
+ << " Changed: " << (changed ? "true" : "false");
+ return changed;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/videoadapter.h b/talk/session/phone/videoadapter.h
new file mode 100644
index 0000000..6c14255
--- /dev/null
+++ b/talk/session/phone/videoadapter.h
@@ -0,0 +1,145 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_VIDEOADAPTER_H_
+#define TALK_SESSION_PHONE_VIDEOADAPTER_H_
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+class VideoFrame;
+
+// VideoAdapter adapts an input video frame to an output frame based on the
+// specified input and output formats. The adaptation includes dropping frames
+// to reduce frame rate and scaling frames. VideoAdapter is thread safe.
+class VideoAdapter {
+ public:
+ VideoAdapter();
+ virtual ~VideoAdapter();
+
+ void SetInputFormat(const VideoFormat& format);
+ void SetOutputFormat(const VideoFormat& format);
+ // Constrain output resolution to this many pixels overall
+ void SetOutputNumPixels(int num_pixels);
+ int GetOutputNumPixels() const;
+
+ const VideoFormat& input_format();
+ const VideoFormat& output_format();
+ // If the parameter black is true, the adapted frames will be black.
+ void SetBlackOutput(bool black);
+
+ // Adapt the input frame from the input format to the output format. Return
+ // true and set the output frame to NULL if the input frame is dropped. Return
+ // true and set the out frame to output_frame_ if the input frame is adapted
+ // successfully. Return false otherwise.
+ // output_frame_ is owned by the VideoAdapter that has the best knowledge on
+ // the output frame.
+ bool AdaptFrame(const VideoFrame* in_frame, const VideoFrame** out_frame);
+
+ protected:
+ float FindClosestScale(int width, int height, int target_num_pixels);
+
+ private:
+ bool StretchToOutputFrame(const VideoFrame* in_frame);
+
+ VideoFormat input_format_;
+ VideoFormat output_format_;
+ int output_num_pixels_;
+ bool black_output_; // Flag to tell if we need to black output_frame_.
+ bool is_black_; // Flag to tell if output_frame_ is currently black.
+ int64 drop_frame_count_;
+ talk_base::scoped_ptr<VideoFrame> output_frame_;
+ // The critical section to protect the above variables.
+ talk_base::CriticalSection critical_section_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoAdapter);
+};
+
+// CoordinatedVideoAdapter adapts the video input to the encoder by coordinating
+// the format request from the server, the resolution request from the encoder,
+// and the CPU load.
+class CoordinatedVideoAdapter
+ : public VideoAdapter, public sigslot::has_slots<> {
+ public:
+ enum AdaptRequest { UPGRADE, KEEP, DOWNGRADE };
+
+ CoordinatedVideoAdapter();
+ virtual ~CoordinatedVideoAdapter() {}
+
+ // Enable or disable video adaptation due to the change of the CPU load.
+ void set_cpu_adaptation(bool enable) { cpu_adaptation_ = enable; }
+ bool cpu_adaptation() const { return cpu_adaptation_; }
+ // Enable or disable video adaptation due to the change of the GD
+ void set_gd_adaptation(bool enable) { gd_adaptation_ = enable; }
+ bool gd_adaptation() const { return gd_adaptation_; }
+ // Enable or disable video adaptation due to the change of the View
+ void set_view_adaptation(bool enable) { view_adaptation_ = enable; }
+ bool view_adaptation() const { return view_adaptation_; }
+ // When the video is decreased, set the waiting time for CPU adaptation to
+ // decrease video again.
+ void set_cpu_downgrade_wait_time(uint32 ms) { cpu_downgrade_wait_time_ = ms; }
+ // Handle the format request from the server via Jingle update message.
+ void OnOutputFormatRequest(const VideoFormat& format);
+ // Handle the resolution request from the encoder due to bandwidth changes.
+ void OnEncoderResolutionRequest(int width, int height, AdaptRequest request);
+ // Handle the CPU load provided by a CPU monitor.
+ void OnCpuLoadUpdated(int current_cpus, int max_cpus,
+ float process_load, float system_load);
+
+ private:
+ // Adapt to the minimum of the formats the server requests, the CPU wants, and
+ // the encoder wants. Returns true if resolution changed.
+ bool AdaptToMinimumFormat();
+ bool IsMinimumFormat(int pixels);
+ void StepPixelCount(CoordinatedVideoAdapter::AdaptRequest request,
+ int* num_pixels);
+ CoordinatedVideoAdapter::AdaptRequest FindCpuRequest(
+ int current_cpus, int max_cpus,
+ float process_load, float system_load);
+
+ bool cpu_adaptation_; // True if cpu adaptation is enabled.
+ bool gd_adaptation_; // True if gd adaptation is enabled.
+ bool view_adaptation_; // True if view adaptation is enabled.
+ int cpu_downgrade_count_;
+ int cpu_downgrade_wait_time_;
+ // Video formats that the server view requests, the CPU wants, and the encoder
+ // wants respectively. The adapted output format is the minimum of these.
+ int view_desired_num_pixels_;
+ int64 view_desired_interval_;
+ int encoder_desired_num_pixels_;
+ int cpu_desired_num_pixels_;
+ // The critical section to protect handling requests.
+ talk_base::CriticalSection request_critical_section_;
+
+ DISALLOW_COPY_AND_ASSIGN(CoordinatedVideoAdapter);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_VIDEOADAPTER_H_
diff --git a/talk/session/phone/videocapturer.cc b/talk/session/phone/videocapturer.cc
index d62bae8..7f86293 100644
--- a/talk/session/phone/videocapturer.cc
+++ b/talk/session/phone/videocapturer.cc
@@ -34,6 +34,7 @@
namespace cricket {
static const int64 kMaxDistance = ~(static_cast<int64>(1) << 63);
+static const int64 kMinDesirableFps = static_cast<int64>(15);
/////////////////////////////////////////////////////////////////////
// Implementation of struct CapturedFrame
@@ -51,7 +52,7 @@
data(NULL) {
}
-// TODO(fbarchard): Remove this function once lmimediaengine stops using it.
+// TODO: Remove this function once lmimediaengine stops using it.
bool CapturedFrame::GetDataSize(uint32* size) const {
if (!size || data_size == CapturedFrame::kUnknownDataSize) {
return false;
@@ -94,7 +95,7 @@
std::vector<VideoFormat>::const_iterator i;
for (i = supported_formats_->begin(); i != supported_formats_->end(); ++i) {
int64 distance = GetFormatDistance(format, *i);
- // TODO(fbarchard): Reduce to LS_VERBOSE if/when camera capture is
+ // TODO: Reduce to LS_VERBOSE if/when camera capture is
// relatively bug free.
LOG(LS_INFO) << " Supported " << i->ToString()
<< " distance " << distance;
@@ -104,6 +105,7 @@
}
}
if (supported_formats_->end() == best) {
+ LOG(LS_ERROR) << " No acceptable camera format found";
return false;
}
@@ -155,10 +157,33 @@
}
// Check resolution and fps.
- int desired_height = desired.height;
int desired_width = desired.width;
- int64 delta_w = supported.width - desired.width;
- int64 delta_fps = VideoFormat::IntervalToFps(supported.interval) -
+ int desired_height = desired.height;
+#ifdef OSX
+ // QVGA on OSX is not well supported. For 16x10, if 320x240 is used, it has
+ // 15x11 pixel aspect ratio on logitech B910/C260 and others. ComputeCrop
+ // in mediaengine does not crop, so we keep 320x240, which magiccam on Mac
+ // can not display. Some other viewers can display 320x240, but do not
+ // support pixel aspect ratio and appear distorted.
+ // This code below bumps the preferred resolution to VGA, maintaining aspect
+ // ratio. ie 320x200 -> 640x400. VGA on logitech and most cameras is 1x1
+ // pixel aspect ratio. The camera will capture 640x480, ComputeCrop will
+ // crop to 640x400, and the adapter will scale down to QVGA due to JUP view
+ // request.
+ static const int kMinWidth = 640;
+ if (desired_width > 0 && desired_width < kMinWidth) {
+ int new_desired_height = desired_height * kMinWidth / desired_width;
+ LOG(LS_VERBOSE) << " Changed desired from "
+ << desired_width << "x" << desired_height
+ << " To "
+ << kMinWidth << "x" << new_desired_height;
+ desired_width = kMinWidth;
+ desired_height = new_desired_height;
+ }
+#endif
+ int64 delta_w = supported.width - desired_width;
+ int64 supported_fps = VideoFormat::IntervalToFps(supported.interval);
+ int64 delta_fps = supported_fps -
VideoFormat::IntervalToFps(desired.interval);
// Check height of supported height compared to height we would like it to be.
int64 aspect_h = desired_width ?
@@ -172,7 +197,7 @@
// strongly avoiding going down in resolution, similar to
// the old method, but not completely ruling it out in extreme situations.
// It also ignores framerate, which is often very low at high resolutions.
- // TODO(fbarchard): Improve logic to use weighted factors.
+ // TODO: Improve logic to use weighted factors.
static const int kDownPenalty = -3;
if (delta_w < 0) {
delta_w = delta_w * kDownPenalty;
@@ -183,8 +208,12 @@
if (delta_fps < 0) {
// For same resolution, prefer higher framerate but accept lower.
// Otherwise prefer higher resolution.
- distance |= static_cast<int64>(1) << 15;
delta_fps = -delta_fps;
+ if (supported_fps < kMinDesirableFps) {
+ distance |= static_cast<int64>(1) << 62;
+ } else {
+ distance |= static_cast<int64>(1) << 15;
+ }
}
// 12 bits for width and height and 8 bits for fps and fourcc.
diff --git a/talk/session/phone/videocapturer_unittest.cc b/talk/session/phone/videocapturer_unittest.cc
new file mode 100644
index 0000000..5bd2419
--- /dev/null
+++ b/talk/session/phone/videocapturer_unittest.cc
@@ -0,0 +1,464 @@
+// Copyright 2008 Google Inc. All Rights Reserved
+
+#include <stdio.h>
+#include <vector>
+
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/videocapturer.h"
+#include "talk/session/phone/testutils.h"
+
+namespace {
+
+class FakeVideoCapturer : public cricket::VideoCapturer {
+ public:
+ FakeVideoCapturer() {
+ // Default supported formats. Use ResetSupportedFormats to over write.
+ std::vector<cricket::VideoFormat> formats;
+ formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ ResetSupportedFormats(formats);
+ }
+ ~FakeVideoCapturer() {}
+
+ void ResetSupportedFormats(const std::vector<cricket::VideoFormat>& formats) {
+ SetSupportedFormats(formats);
+ }
+ virtual cricket::CaptureResult Start(const cricket::VideoFormat& format) {
+ return cricket::CR_SUCCESS;
+ }
+ virtual void Stop() {}
+ virtual bool IsRunning() { return true; }
+ bool GetPreferredFourccs(std::vector<uint32>* fourccs) {
+ fourccs->push_back(cricket::FOURCC_I420);
+ fourccs->push_back(cricket::FOURCC_MJPG);
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeVideoCapturer);
+};
+
+TEST(VideoCapturerTest, TestFourccMatch) {
+ FakeVideoCapturer capturer;
+ cricket::VideoFormat desired(640, 480,
+ cricket::VideoFormat::FpsToInterval(30),
+ cricket::FOURCC_ANY);
+ cricket::VideoFormat best;
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.fourcc = cricket::FOURCC_MJPG;
+ EXPECT_FALSE(capturer.GetBestCaptureFormat(desired, &best));
+
+ desired.fourcc = cricket::FOURCC_I420;
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+}
+
+TEST(VideoCapturerTest, TestResolutionMatch) {
+ FakeVideoCapturer capturer;
+ cricket::VideoFormat desired(960, 720,
+ cricket::VideoFormat::FpsToInterval(30),
+ cricket::FOURCC_ANY);
+ cricket::VideoFormat best;
+ // Ask for 960x720. Get VGA which is the highest.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 360;
+ desired.height = 250;
+ // Ask for a little higher than QVGA. Get QVGA. On OSX gets VGA
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 480;
+ desired.height = 270;
+ // Ask for HVGA. Get VGA.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 320;
+ desired.height = 240;
+ // Ask for QVGA. Get QVGA. On OSX get VGA
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 160;
+ desired.height = 120;
+ // Ask for lower than QVGA. Get QVGA, which is the lowest. OSX gets VGA
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+}
+
+TEST(VideoCapturerTest, TestHDResolutionMatch) {
+ FakeVideoCapturer capturer;
+
+ // Add some HD formats
+ std::vector<cricket::VideoFormat> formats;
+ formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(960, 544,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ formats.push_back(cricket::VideoFormat(2592, 1944,
+ cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(formats);
+
+ cricket::VideoFormat desired(960, 720,
+ cricket::VideoFormat::FpsToInterval(30),
+ cricket::FOURCC_ANY);
+ cricket::VideoFormat best;
+ // Ask for 960x720. Get qHD
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(960, best.width);
+ EXPECT_EQ(544, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 360;
+ desired.height = 250;
+ // Ask for a litter higher than QVGA. Get QVGA. OSX gets VGA
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 480;
+ desired.height = 270;
+ // Ask for HVGA. Get VGA.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 320;
+ desired.height = 240;
+ // Ask for QVGA. Get QVGA. OSX gets VGA
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 160;
+ desired.height = 120;
+ // Ask for lower than QVGA. Get QVGA, which is the lowest.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+#endif
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 1280;
+ desired.height = 720;
+ // Ask for HD. Get qHD.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(960, best.width);
+ EXPECT_EQ(544, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ desired.width = 1920;
+ desired.height = 1080;
+ // Ask for 1080p. Get 2592x1944x15.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(desired, &best));
+ EXPECT_EQ(2592, best.width);
+ EXPECT_EQ(1944, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+}
+
+// Some cameras support 320x240 and 320x640. Verify we choose 320x240.
+// On OSX we choose VGA
+TEST(VideoCapturerTest, TestStrangeFormats) {
+ FakeVideoCapturer capturer;
+ std::vector<cricket::VideoFormat> supported_formats;
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(320, 640,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ std::vector<cricket::VideoFormat> required_formats;
+ required_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ required_formats.push_back(cricket::VideoFormat(320, 200,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ required_formats.push_back(cricket::VideoFormat(320, 180,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ cricket::VideoFormat best;
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+ }
+
+ supported_formats.clear();
+ supported_formats.push_back(cricket::VideoFormat(320, 640,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+ }
+}
+
+// Some cameras only have very low fps. Verify we choose something sensible.
+TEST(VideoCapturerTest, TestPoorFpsFormats) {
+ FakeVideoCapturer capturer;
+ // all formats are low framerate
+ std::vector<cricket::VideoFormat> supported_formats;
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(1280, 720,
+ cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ std::vector<cricket::VideoFormat> required_formats;
+ required_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ required_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ cricket::VideoFormat best;
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+#ifdef OSX
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+#else
+ EXPECT_EQ(required_formats[i].width, best.width);
+ EXPECT_EQ(required_formats[i].height, best.height);
+#endif
+ }
+
+ // Increase framerate of 320x240. Expect low fps VGA avoided.
+ // Except on Mac, where QVGA is avoid due to aspect ratio.
+ supported_formats.clear();
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(7), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(1280, 720,
+ cricket::VideoFormat::FpsToInterval(2), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+ }
+}
+
+// Some cameras support same size with different frame rates. Verify we choose
+// the frame rate properly.
+TEST(VideoCapturerTest, TestSameSizeDifferentFpsFormats) {
+ FakeVideoCapturer capturer;
+ std::vector<cricket::VideoFormat> supported_formats;
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(20), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(320, 240,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ std::vector<cricket::VideoFormat> required_formats = supported_formats;
+ cricket::VideoFormat best;
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(320, best.width);
+ EXPECT_EQ(240, best.height);
+ EXPECT_EQ(required_formats[i].interval, best.interval);
+ }
+}
+
+// Some cameras support the correct resolution but at a lower fps than
+// we'd like. This tests we get the expected resolution and fps.
+TEST(VideoCapturerTest, TestFpsFormats) {
+ FakeVideoCapturer capturer;
+ // We have VGA but low fps. Choose VGA, not HD
+ std::vector<cricket::VideoFormat> supported_formats;
+ supported_formats.push_back(cricket::VideoFormat(1280, 720,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 400,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 360,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ std::vector<cricket::VideoFormat> required_formats;
+ required_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY));
+ required_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(20), cricket::FOURCC_ANY));
+ required_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(10), cricket::FOURCC_ANY));
+ cricket::VideoFormat best;
+
+ // expect 30 fps to choose 15 fps format
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[0], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+
+ // expect 20 fps to choose 15 fps format
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[1], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(15), best.interval);
+
+ // expect 10 fps to choose 15 fps format but set fps to 10
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(10), best.interval);
+
+ // We have VGA 60 fps and 15 fps. Choose best fps.
+ supported_formats.clear();
+ supported_formats.push_back(cricket::VideoFormat(1280, 720,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(60), cricket::FOURCC_MJPG));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(15), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 400,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 360,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ // expect 30 fps to choose 60 fps format, but will set best fps to 30
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[0], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(30), best.interval);
+
+ // expect 20 fps to choose 60 fps format, but will set best fps to 20
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[1], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(20), best.interval);
+
+ // expect 10 fps to choose 10 fps
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ EXPECT_EQ(cricket::VideoFormat::FpsToInterval(10), best.interval);
+}
+
+TEST(VideoCapturerTest, TestRequest16x10_9) {
+ FakeVideoCapturer capturer;
+ std::vector<cricket::VideoFormat> supported_formats;
+ // We do not support HD, expect 4x3 for 4x3, 16x10, and 16x9 requests.
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 400,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 360,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ std::vector<cricket::VideoFormat> required_formats = supported_formats;
+ cricket::VideoFormat best;
+ // Expect 4x3 for 4x3, 16x10, and 16x9 requests.
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ }
+
+ // We do not support 16x9 HD, expect 4x3 for 4x3, 16x10, and 16x9 requests.
+ supported_formats.clear();
+ supported_formats.push_back(cricket::VideoFormat(960, 720,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 400,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 360,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ // Expect 4x3 for 4x3, 16x10, and 16x9 requests.
+ for (size_t i = 0; i < required_formats.size(); ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ }
+
+ // We support 16x9HD, expect 4x3 for 4x3 and 16x10 requests and expect 16x9
+ // for 16x9 request.
+ supported_formats.clear();
+ supported_formats.push_back(cricket::VideoFormat(1280, 720,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 480,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 400,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ supported_formats.push_back(cricket::VideoFormat(640, 360,
+ cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420));
+ capturer.ResetSupportedFormats(supported_formats);
+
+ // Expect 4x3 for 4x3 and 16x10 requests.
+ for (size_t i = 0; i < required_formats.size() - 1; ++i) {
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[i], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(480, best.height);
+ }
+
+ // Expect 16x9 for 16x9 request.
+ EXPECT_TRUE(capturer.GetBestCaptureFormat(required_formats[2], &best));
+ EXPECT_EQ(640, best.width);
+ EXPECT_EQ(360, best.height);
+}
+
+} // unnamed namespace
diff --git a/talk/session/phone/videocommon.cc b/talk/session/phone/videocommon.cc
new file mode 100644
index 0000000..0d07ef1
--- /dev/null
+++ b/talk/session/phone/videocommon.cc
@@ -0,0 +1,90 @@
+// libjingle
+// Copyright 2010 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "talk/session/phone/videocommon.h"
+
+#include <sstream>
+
+#include "talk/base/common.h"
+
+namespace cricket {
+
+struct FourCCAliasEntry {
+ uint32 alias;
+ uint32 canonical;
+};
+
+static const FourCCAliasEntry kFourCCAliases[] = {
+ {FOURCC_IYUV, FOURCC_I420},
+ {FOURCC_YU12, FOURCC_I420},
+ {FOURCC_YU16, FOURCC_I422},
+ {FOURCC_YU24, FOURCC_I444},
+ {FOURCC_YUYV, FOURCC_YUY2},
+ {FOURCC_YUVS, FOURCC_YUY2},
+ {FOURCC_HDYC, FOURCC_UYVY},
+ {FOURCC_2VUY, FOURCC_UYVY},
+ {FOURCC_BA81, FOURCC_BGGR},
+ {FOURCC_JPEG, FOURCC_MJPG}, // Note: JPEG has DHT while MJPG does not.
+ {FOURCC_DMB1, FOURCC_MJPG},
+ {FOURCC_RGB3, FOURCC_RAW},
+ {FOURCC_BGR3, FOURCC_24BG},
+};
+
+uint32 CanonicalFourCC(uint32 fourcc) {
+ for (int i = 0; i < ARRAY_SIZE(kFourCCAliases); ++i) {
+ if (kFourCCAliases[i].alias == fourcc) {
+ return kFourCCAliases[i].canonical;
+ }
+ }
+ // Not an alias, so return it as-is.
+ return fourcc;
+}
+
+// The C++ standard requires a namespace-scope definition of static const
+// integral types even when they are initialized in the declaration (see
+// [class.static.data]/4), but MSVC with /Ze is non-conforming and treats that
+// as a multiply defined symbol error (see
+// http://msdn.microsoft.com/en-us/library/34h23df8.aspx).
+#ifndef _MSC_EXTENSIONS
+const int64 VideoFormat::kMinimumInterval; // Initialized in header.
+#endif
+
+std::string VideoFormat::ToString() const {
+ std::string fourcc_name = GetFourccName(fourcc) + " ";
+ for (std::string::const_iterator i = fourcc_name.begin();
+ i < fourcc_name.end(); ++i) {
+ // Test character is printable; Avoid isprint() which asserts on negatives
+ if (*i < 32 || *i >= 127) {
+ fourcc_name = "";
+ break;
+ }
+ }
+
+ std::ostringstream ss;
+ ss << fourcc_name << width << "x" << height << "x" << IntervalToFps(interval);
+ return ss.str();
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/videocommon.h b/talk/session/phone/videocommon.h
index 995e800..fa695f0 100644
--- a/talk/session/phone/videocommon.h
+++ b/talk/session/phone/videocommon.h
@@ -31,37 +31,34 @@
#include <string>
#include "talk/base/basictypes.h"
+#include "talk/base/timeutils.h"
namespace cricket {
//////////////////////////////////////////////////////////////////////////////
-// Definition of fourcc.
+// Definition of FourCC codes
//////////////////////////////////////////////////////////////////////////////
-// Convert four characters to a fourcc code.
+// Convert four characters to a FourCC code.
// Needs to be a macro otherwise the OS X compiler complains when the kFormat*
// constants are used in a switch.
-#define FOURCC(a, b, c, d) (\
+#define FOURCC(a, b, c, d) ( \
(static_cast<uint32>(a)) | (static_cast<uint32>(b) << 8) | \
(static_cast<uint32>(c) << 16) | (static_cast<uint32>(d) << 24))
-// Get the name, that is, string with four characters, of a fourcc code.
-inline std::string GetFourccName(uint32 fourcc) {
- std::string name;
- name.push_back(static_cast<char>(fourcc & 0xFF));
- name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
- name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
- name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
- return name;
-}
-
-// FourCC codes used in Google Talk.
-// Some good pages discussing FourCC codes:
-// http://developer.apple.com/quicktime/icefloe/dispatch020.html
+// Some pages discussing FourCC codes:
// http://www.fourcc.org/yuv.php
+// http://v4l2spec.bytesex.org/spec/book1.htm
+// http://developer.apple.com/quicktime/icefloe/dispatch020.html
+
enum FourCC {
// Canonical fourcc codes used in our code.
FOURCC_I420 = FOURCC('I', '4', '2', '0'),
+ FOURCC_I422 = FOURCC('I', '4', '2', '2'),
+ FOURCC_I444 = FOURCC('I', '4', '4', '4'),
+ FOURCC_I400 = FOURCC('I', '4', '0', '0'),
FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'),
+ FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'),
+ FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'),
FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'),
FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'),
FOURCC_M420 = FOURCC('M', '4', '2', '0'),
@@ -70,6 +67,9 @@
FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'),
FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'),
FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'),
+ FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'), // bgr565
+ FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'), // abgr1555
+ FOURCC_R444 = FOURCC('R', '4', '4', '4'), // argb4444
FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'),
FOURCC_RAW = FOURCC('r', 'a', 'w', ' '),
FOURCC_NV21 = FOURCC('N', 'V', '2', '1'),
@@ -85,6 +85,8 @@
// equivalents by CanonicalFourCC().
FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'), // Alias for I420
FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'), // Alias for I420
+ FOURCC_YU16 = FOURCC('Y', 'U', '1', '6'), // Alias for I422
+ FOURCC_YU24 = FOURCC('Y', 'U', '2', '4'), // Alias for I444
FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'), // Alias for YUY2
FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'), // Alias for YUY2 on Mac
FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'), // Alias for UYVY
@@ -102,39 +104,59 @@
// Converts fourcc aliases into canonical ones.
uint32 CanonicalFourCC(uint32 fourcc);
+// Get FourCC code as a string
+inline std::string GetFourccName(uint32 fourcc) {
+ std::string name;
+ name.push_back(static_cast<char>(fourcc & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
+ return name;
+}
+
//////////////////////////////////////////////////////////////////////////////
// Definition of VideoFormat.
//////////////////////////////////////////////////////////////////////////////
-static const int64 kNumNanosecsPerSec = 1000000000;
+// VideoFormat with Plain Old Data for global variables
+struct VideoFormatPod {
+ int width; // in number of pixels
+ int height; // in number of pixels
+ int64 interval; // in nanoseconds
+ uint32 fourcc; // color space. FOURCC_ANY means that any color space is OK.
+};
-struct VideoFormat {
- static const int64 kMinimumInterval = kNumNanosecsPerSec / 10000; // 10k fps
+struct VideoFormat : VideoFormatPod{
+ static const int64 kMinimumInterval =
+ talk_base::kNumNanosecsPerSec / 10000; // 10k fps
- VideoFormat() : width(0), height(0), interval(0), fourcc(0) {}
-
- VideoFormat(int w, int h, int64 interval_ns, uint32 cc)
- : width(w),
- height(h),
- interval(interval_ns),
- fourcc(cc) {
+ VideoFormat() {
+ Construct(0, 0, 0, 0);
}
- VideoFormat(const VideoFormat& format)
- : width(format.width),
- height(format.height),
- interval(format.interval),
- fourcc(format.fourcc) {
+ VideoFormat(int w, int h, int64 interval_ns, uint32 cc) {
+ Construct(w, h, interval_ns, cc);
+ }
+
+ explicit VideoFormat(const VideoFormatPod& format) {
+ Construct(format.width, format.height, format.interval, format.fourcc);
+ }
+
+ void Construct(int w, int h, int64 interval_ns, uint32 cc) {
+ width = w;
+ height = h;
+ interval = interval_ns;
+ fourcc = cc;
}
static int64 FpsToInterval(int fps) {
- return fps ? kNumNanosecsPerSec / fps : kMinimumInterval;
+ return fps ? talk_base::kNumNanosecsPerSec / fps : kMinimumInterval;
}
static int IntervalToFps(int64 interval) {
// Normalize the interval first.
interval = talk_base::_max(interval, kMinimumInterval);
- return static_cast<int>(kNumNanosecsPerSec / interval);
+ return static_cast<int>(talk_base::kNumNanosecsPerSec / interval);
}
bool operator==(const VideoFormat& format) const {
@@ -169,11 +191,6 @@
// Get a string presentation in the form of "fourcc width x height x fps"
std::string ToString() const;
-
- int width; // in number of pixels
- int height; // in number of pixels
- int64 interval; // in nanoseconds
- uint32 fourcc; // color space. FOURCC_ANY means that any color space is OK.
};
// Result of video capturer start.
diff --git a/talk/session/phone/videocommon_unittest.cc b/talk/session/phone/videocommon_unittest.cc
new file mode 100644
index 0000000..eb74c35
--- /dev/null
+++ b/talk/session/phone/videocommon_unittest.cc
@@ -0,0 +1,102 @@
+/*
+ * libjingle
+ * Copyright 2008 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+TEST(VideoCommonTest, TestCanonicalFourCC) {
+ // Canonical fourccs are not changed.
+ EXPECT_EQ(FOURCC_I420, CanonicalFourCC(FOURCC_I420));
+ // The special FOURCC_ANY value is not changed.
+ EXPECT_EQ(FOURCC_ANY, CanonicalFourCC(FOURCC_ANY));
+ // Aliases are translated to the canonical equivalent.
+ EXPECT_EQ(FOURCC_I420, CanonicalFourCC(FOURCC_IYUV));
+ EXPECT_EQ(FOURCC_I422, CanonicalFourCC(FOURCC_YU16));
+ EXPECT_EQ(FOURCC_I444, CanonicalFourCC(FOURCC_YU24));
+ EXPECT_EQ(FOURCC_24BG, CanonicalFourCC(FOURCC_BGR3));
+ EXPECT_EQ(FOURCC_RAW, CanonicalFourCC(FOURCC_RGB3));
+ EXPECT_EQ(FOURCC_MJPG, CanonicalFourCC(FOURCC_DMB1));
+}
+
+// Test conversion between interval and fps
+TEST(VideoCommonTest, TestVideoFormatFps) {
+ EXPECT_EQ(VideoFormat::kMinimumInterval, VideoFormat::FpsToInterval(0));
+ EXPECT_EQ(talk_base::kNumNanosecsPerSec / 20, VideoFormat::FpsToInterval(20));
+ EXPECT_EQ(20, VideoFormat::IntervalToFps(talk_base::kNumNanosecsPerSec / 20));
+}
+
+// Test IsSize0x0
+TEST(VideoCommonTest, TestVideoFormatIsSize0x0) {
+ VideoFormat format;
+ EXPECT_TRUE(format.IsSize0x0());
+ format.width = 320;
+ EXPECT_FALSE(format.IsSize0x0());
+}
+
+// Test ToString: print fourcc when it is printable.
+TEST(VideoCommonTest, TestVideoFormatToString) {
+ VideoFormat format;
+ EXPECT_EQ("0x0x10000", format.ToString());
+
+ format.fourcc = FOURCC_I420;
+ format.width = 640;
+ format.height = 480;
+ format.interval = VideoFormat::FpsToInterval(20);
+ EXPECT_EQ("I420 640x480x20", format.ToString());
+
+ format.fourcc = FOURCC_ANY;
+ format.width = 640;
+ format.height = 480;
+ format.interval = VideoFormat::FpsToInterval(20);
+ EXPECT_EQ("640x480x20", format.ToString());
+}
+
+// Test comparison.
+TEST(VideoCommonTest, TestVideoFormatCompare) {
+ VideoFormat format(640, 480, VideoFormat::FpsToInterval(20), FOURCC_I420);
+ VideoFormat format2;
+ EXPECT_NE(format, format2);
+
+ // Same pixelrate, different fourcc.
+ format2 = format;
+ format2.fourcc = FOURCC_YUY2;
+ EXPECT_NE(format, format2);
+ EXPECT_FALSE(format.IsPixelRateLess(format2) ||
+ format2.IsPixelRateLess(format2));
+
+ format2 = format;
+ format2.interval /= 2;
+ EXPECT_TRUE(format.IsPixelRateLess(format2));
+
+ format2 = format;
+ format2.width *= 2;
+ EXPECT_TRUE(format.IsPixelRateLess(format2));
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/videoframe.cc b/talk/session/phone/videoframe.cc
new file mode 100644
index 0000000..dd02a9a
--- /dev/null
+++ b/talk/session/phone/videoframe.cc
@@ -0,0 +1,147 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/videoframe.h"
+
+#include <cstring>
+
+#ifdef HAVE_YUV
+#include "libyuv/planar_functions.h"
+#include "libyuv/scale.h"
+#endif
+
+namespace cricket {
+
+// Round to 2 pixels because Chroma channels are half size.
+#define ROUNDTO2(v) (v & ~1)
+
+// TODO: Handle odd width/height with rounding.
+void VideoFrame::StretchToPlanes(
+ uint8* y, uint8* u, uint8* v,
+ int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v,
+ size_t width, size_t height, bool interpolate, bool vert_crop) const {
+#ifdef HAVE_YUV
+ if (!GetYPlane() || !GetUPlane() || !GetVPlane())
+ return;
+
+ const uint8* in_y = GetYPlane();
+ const uint8* in_u = GetUPlane();
+ const uint8* in_v = GetVPlane();
+ int32 iwidth = GetWidth();
+ int32 iheight = GetHeight();
+
+ if (vert_crop) {
+ // Adjust the input width:height ratio to be the same as the output ratio.
+ if (iwidth * height > iheight * width) {
+ // Reduce the input width, but keep size/position aligned for YuvScaler
+ iwidth = ROUNDTO2(iheight * width / height);
+ int32 iwidth_offset = ROUNDTO2((GetWidth() - iwidth) / 2);
+ in_y += iwidth_offset;
+ in_u += iwidth_offset / 2;
+ in_v += iwidth_offset / 2;
+ } else if (iwidth * height < iheight * width) {
+ // Reduce the input height.
+ iheight = iwidth * height / width;
+ int32 iheight_offset = (GetHeight() - iheight) >> 2;
+ iheight_offset <<= 1; // Ensure that iheight_offset is even.
+ in_y += iheight_offset * GetYPitch();
+ in_u += iheight_offset / 2 * GetUPitch();
+ in_v += iheight_offset / 2 * GetVPitch();
+ }
+ }
+
+ // Scale to the output I420 frame.
+ libyuv::Scale(in_y, in_u, in_v,
+ GetYPitch(),
+ GetUPitch(),
+ GetVPitch(),
+ iwidth, iheight,
+ y, u, v, dst_pitch_y, dst_pitch_u, dst_pitch_v,
+ width, height, interpolate);
+#endif
+}
+
+size_t VideoFrame::StretchToBuffer(size_t w, size_t h,
+ uint8* buffer, size_t size,
+ bool interpolate, bool vert_crop) const {
+ if (!buffer) return 0;
+
+ size_t needed = SizeOf(w, h);
+ if (needed <= size) {
+ uint8* bufy = buffer;
+ uint8* bufu = bufy + w * h;
+ uint8* bufv = bufu + ((w + 1) >> 1) * ((h + 1) >> 1);
+ StretchToPlanes(bufy, bufu, bufv, w, (w + 1) >> 1, (w + 1) >> 1, w, h,
+ interpolate, vert_crop);
+ }
+ return needed;
+}
+
+void VideoFrame::StretchToFrame(VideoFrame *target,
+ bool interpolate, bool vert_crop) const {
+ if (!target) return;
+
+ StretchToPlanes(target->GetYPlane(),
+ target->GetUPlane(),
+ target->GetVPlane(),
+ target->GetYPitch(),
+ target->GetUPitch(),
+ target->GetVPitch(),
+ target->GetWidth(),
+ target->GetHeight(),
+ interpolate, vert_crop);
+ target->SetElapsedTime(GetElapsedTime());
+ target->SetTimeStamp(GetTimeStamp());
+}
+
+VideoFrame* VideoFrame::Stretch(size_t w, size_t h,
+ bool interpolate, bool vert_crop) const {
+ VideoFrame* dest = CreateEmptyFrame(w, h, GetPixelWidth(), GetPixelHeight(),
+ GetElapsedTime(), GetTimeStamp());
+ if (dest) {
+ StretchToFrame(dest, interpolate, vert_crop);
+ }
+ return dest;
+}
+
+bool VideoFrame::SetToBlack() {
+#ifdef HAVE_YUV
+ return libyuv::I420Rect(GetYPlane(), GetYPitch(),
+ GetUPlane(), GetUPitch(),
+ GetVPlane(), GetVPitch(),
+ 0, 0, GetWidth(), GetHeight(),
+ 16, 128, 128) == 0;
+#else
+ int uv_size = GetUPitch() * GetChromaHeight();
+ memset(GetYPlane(), 16, GetWidth() * GetHeight());
+ memset(GetUPlane(), 128, uv_size);
+ memset(GetVPlane(), 128, uv_size);
+ return true;
+#endif
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/videoframe.h b/talk/session/phone/videoframe.h
index 6f331d1..c34db88 100644
--- a/talk/session/phone/videoframe.h
+++ b/talk/session/phone/videoframe.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2004 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -50,13 +50,24 @@
public:
VideoFrame() : rendered_(false) {}
-
virtual ~VideoFrame() {}
+ // Creates a frame from a raw sample with FourCC |format| and size |w| x |h|.
+ // |h| can be negative indicating a vertically flipped image.
+ // |dw| is destination width; can be less than |w| if cropping is desired.
+ // |dh| is destination height, like |dw|, but must be a positive number.
+ // Returns whether the function succeeded or failed.
+ virtual bool Reset(uint32 fourcc, int w, int h, int dw, int dh,
+ uint8 *sample, size_t sample_size,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time, int64 time_stamp, int rotation) = 0;
+
+ // Basic accessors.
virtual size_t GetWidth() const = 0;
virtual size_t GetHeight() const = 0;
size_t GetChromaWidth() const { return (GetWidth() + 1) / 2; }
size_t GetChromaHeight() const { return (GetHeight() + 1) / 2; }
+ size_t GetChromaSize() const { return GetUPitch() * GetChromaHeight(); }
virtual const uint8 *GetYPlane() const = 0;
virtual const uint8 *GetUPlane() const = 0;
virtual const uint8 *GetVPlane() const = 0;
@@ -72,8 +83,6 @@
virtual size_t GetPixelWidth() const = 0;
virtual size_t GetPixelHeight() const = 0;
- // TODO: Add a fourcc format here and probably combine VideoFrame
- // with CapturedFrame.
virtual int64 GetElapsedTime() const = 0;
virtual int64 GetTimeStamp() const = 0;
virtual void SetElapsedTime(int64 elapsed_time) = 0;
@@ -102,10 +111,10 @@
// Converts the I420 data to RGB of a certain type such as ARGB and ABGR.
// Returns the frame's actual size, regardless of whether it was written or
- // not (like snprintf). Parameters size and pitch_rgb are in units of bytes.
+ // not (like snprintf). Parameters size and stride_rgb are in units of bytes.
// If there is insufficient space, nothing is written.
virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
- size_t size, size_t pitch_rgb) const = 0;
+ size_t size, int stride_rgb) const = 0;
// Writes the frame into the given planes, stretched to the given width and
// height. The parameter "interpolate" controls whether to interpolate or just
@@ -114,7 +123,7 @@
virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
int32 pitchY, int32 pitchU, int32 pitchV,
size_t width, size_t height,
- bool interpolate, bool crop) const = 0;
+ bool interpolate, bool crop) const;
// Writes the frame into the given frame buffer, stretched to the given width
// and height, provided that it is of sufficient size. Returns the frame's
@@ -124,23 +133,23 @@
// nearest-point. The parameter "crop" controls whether to crop this frame to
// the aspect ratio of the given dimensions before stretching.
virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
- bool interpolate, bool crop) const = 0;
+ bool interpolate, bool crop) const;
// Writes the frame into the target VideoFrame, stretched to the size of that
// frame. The parameter "interpolate" controls whether to interpolate or just
// take the nearest-point. The parameter "crop" controls whether to crop this
// frame to the aspect ratio of the target frame before stretching.
virtual void StretchToFrame(VideoFrame *target, bool interpolate,
- bool crop) const = 0;
+ bool crop) const;
// Stretches the frame to the given size, creating a new VideoFrame object to
// hold it. The parameter "interpolate" controls whether to interpolate or
// just take the nearest-point. The parameter "crop" controls whether to crop
// this frame to the aspect ratio of the given dimensions before stretching.
virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
- bool crop) const = 0;
+ bool crop) const;
- // Set the video frame to black.
+ // Sets the video frame to black.
bool SetToBlack();
// Size of an I420 image of given dimensions when stored as a frame buffer.
@@ -149,6 +158,12 @@
}
protected:
+ // Creates an empty frame.
+ virtual VideoFrame* CreateEmptyFrame(int w, int h,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time,
+ int64 time_stamp) const = 0;
+
// The frame needs to be rendered to magiccam only once.
// TODO: Remove this flag once magiccam rendering is fully replaced
// by client3d rendering.
diff --git a/talk/session/phone/videoprocessor.h b/talk/session/phone/videoprocessor.h
new file mode 100644
index 0000000..d3b6249
--- /dev/null
+++ b/talk/session/phone/videoprocessor.h
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+#define TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+
+class VideoProcessor : public sigslot::has_slots<> {
+ public:
+ virtual ~VideoProcessor() {}
+ // Contents of frame may be manipulated by the processor.
+ // The processed data is expected to be the same size as the
+ // original data
+ virtual void OnFrame(uint32 ssrc, VideoFrame* frame) = 0;
+};
+
+} // namespace cricket
+#endif // TALK_SESSION_PHONE_VIDEOPROCESSOR_H_
+
diff --git a/talk/session/phone/videorenderer.h b/talk/session/phone/videorenderer.h
index f1fe547..c428d57 100644
--- a/talk/session/phone/videorenderer.h
+++ b/talk/session/phone/videorenderer.h
@@ -28,6 +28,10 @@
#ifndef TALK_SESSION_PHONE_VIDEORENDERER_H_
#define TALK_SESSION_PHONE_VIDEORENDERER_H_
+#ifdef _DEBUG
+#include <string>
+#endif // _DEBUG
+
namespace cricket {
class VideoFrame;
@@ -40,6 +44,11 @@
virtual bool SetSize(int width, int height, int reserved) = 0;
// Called when a new frame is available for display.
virtual bool RenderFrame(const VideoFrame *frame) = 0;
+
+#ifdef _DEBUG
+ // Allow renderer dumping out rendered frames.
+ virtual bool SetDumpPath(const std::string &path) { return true; }
+#endif // _DEBUG
};
} // namespace cricket
diff --git a/talk/session/phone/voiceprocessor.h b/talk/session/phone/voiceprocessor.h
new file mode 100644
index 0000000..39bcfab
--- /dev/null
+++ b/talk/session/phone/voiceprocessor.h
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_VOICEPROCESSOR_H_
+#define TALK_SESSION_PHONE_VOICEPROCESSOR_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/session/phone/audioframe.h"
+
+namespace cricket {
+
+enum MediaProcessorDirection {
+ MPD_RX = 1 << 0,
+ MPD_TX = 1 << 1,
+ MPD_RX_AND_TX = MPD_RX | MPD_TX,
+};
+
+class VoiceProcessor : public sigslot::has_slots<> {
+ public:
+ virtual ~VoiceProcessor() {}
+ // Contents of frame may be manipulated by the processor.
+ // The processed data is expected to be the same size as the
+ // original data
+ virtual void OnFrame(uint32 ssrc, AudioFrame* frame) = 0;
+};
+
+} // namespace cricket
+#endif // TALK_SESSION_PHONE_VOICEPROCESSOR_H_
diff --git a/talk/session/phone/webrtcvideocapturer.cc b/talk/session/phone/webrtcvideocapturer.cc
new file mode 100644
index 0000000..c187238
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer.cc
@@ -0,0 +1,368 @@
+// libjingle
+// Copyright 2004--2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Implementation of class WebRtcVideoCapturer.
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_WEBRTC_VIDEO
+#include "talk/session/phone/webrtcvideocapturer.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/session/phone/webrtcvideoframe.h"
+
+#include "talk/base/win32.h" // Need this to #include the impl files
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture_factory.h"
+#else
+#include "third_party/webrtc/files/include/video_capture_factory.h"
+#endif
+
+namespace cricket {
+
+struct kVideoFourCCEntry {
+ uint32 fourcc;
+ webrtc::RawVideoType webrtc_type;
+};
+
+// This indicates our format preferences and defines a mapping between
+// webrtc::RawVideoType (from video_capture_defines.h) to our FOURCCs.
+static kVideoFourCCEntry kSupportedFourCCs[] = {
+ { FOURCC_I420, webrtc::kVideoI420 }, // 12 bpp, no conversion
+ { FOURCC_YV12, webrtc::kVideoYV12 }, // 12 bpp, no conversion
+ { FOURCC_NV12, webrtc::kVideoNV12 }, // 12 bpp, fast conversion
+ { FOURCC_NV21, webrtc::kVideoNV21 }, // 12 bpp, fast conversion
+ { FOURCC_YUY2, webrtc::kVideoYUY2 }, // 16 bpp, fast conversion
+ { FOURCC_UYVY, webrtc::kVideoUYVY }, // 16 bpp, fast conversion
+ { FOURCC_MJPG, webrtc::kVideoMJPEG }, // compressed, slow conversion
+ { FOURCC_ARGB, webrtc::kVideoARGB }, // 32 bpp, slow conversion
+ { FOURCC_24BG, webrtc::kVideoRGB24 }, // 32 bpp, slow conversion
+};
+
+class WebRtcVcmFactory : public WebRtcVcmFactoryInterface {
+ public:
+ virtual webrtc::VideoCaptureModule* Create(int id,
+ const WebRtc_UWord8* device) {
+ return webrtc::VideoCaptureFactory::Create(id, device);
+ }
+ virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) {
+ return webrtc::VideoCaptureFactory::CreateDeviceInfo(id);
+ }
+ virtual void DestroyDeviceInfo(webrtc::VideoCaptureModule::DeviceInfo* info) {
+ delete info;
+ }
+};
+
+static bool CapabilityToFormat(const webrtc::VideoCaptureCapability& cap,
+ VideoFormat* format) {
+ uint32 fourcc = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+ if (kSupportedFourCCs[i].webrtc_type == cap.rawType) {
+ fourcc = kSupportedFourCCs[i].fourcc;
+ break;
+ }
+ }
+ if (fourcc == 0) {
+ return false;
+ }
+
+ format->fourcc = fourcc;
+ format->width = cap.width;
+ format->height = cap.height;
+ format->interval = VideoFormat::FpsToInterval(cap.maxFPS);
+ return true;
+}
+
+static bool FormatToCapability(const VideoFormat& format,
+ webrtc::VideoCaptureCapability* cap) {
+ webrtc::RawVideoType webrtc_type = webrtc::kVideoUnknown;
+ for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+ if (kSupportedFourCCs[i].fourcc == format.fourcc) {
+ webrtc_type = kSupportedFourCCs[i].webrtc_type;
+ break;
+ }
+ }
+ if (webrtc_type == webrtc::kVideoUnknown) {
+ return false;
+ }
+
+ cap->width = format.width;
+ cap->height = format.height;
+ cap->maxFPS = VideoFormat::IntervalToFps(format.interval);
+ cap->expectedCaptureDelay = 0;
+ cap->rawType = webrtc_type;
+ cap->codecType = webrtc::kVideoCodecUnknown;
+ cap->interlaced = false;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of class WebRtcVideoCapturer
+///////////////////////////////////////////////////////////////////////////
+
+WebRtcVideoCapturer::WebRtcVideoCapturer()
+ : factory_(new WebRtcVcmFactory),
+ module_(NULL),
+ captured_frames_(0) {
+}
+
+WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory)
+ : factory_(factory),
+ module_(NULL),
+ captured_frames_(0) {
+}
+
+WebRtcVideoCapturer::~WebRtcVideoCapturer() {
+ if (module_) {
+ module_->Release();
+ }
+}
+
+bool WebRtcVideoCapturer::Init(const Device& device) {
+ if (module_) {
+ LOG(LS_ERROR) << "The capturer is already initialized";
+ return false;
+ }
+
+ webrtc::VideoCaptureModule::DeviceInfo* info = factory_->CreateDeviceInfo(0);
+ if (!info) {
+ return false;
+ }
+
+ // Find the desired camera, by name.
+ // In the future, comparing IDs will be more robust.
+ // TODO: Figure what's needed to allow this.
+ int num_cams = info->NumberOfDevices();
+ WebRtc_UWord8 vcm_id[256] = "";
+ bool found = false;
+ for (int index = 0; index < num_cams; ++index) {
+ WebRtc_UWord8 vcm_name[256];
+ if (info->GetDeviceName(index, vcm_name, ARRAY_SIZE(vcm_name),
+ vcm_id, ARRAY_SIZE(vcm_id)) != -1) {
+ if (device.name == reinterpret_cast<char*>(vcm_name)) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ LOG(LS_WARNING) << "Failed to find capturer for id: " << device.id;
+ factory_->DestroyDeviceInfo(info);
+ return false;
+ }
+
+ // Enumerate the supported formats.
+ // TODO: Find out why this starts/stops the camera...
+ std::vector<VideoFormat> supported;
+ WebRtc_UWord32 num_caps = info->NumberOfCapabilities(vcm_id);
+ for (WebRtc_UWord8 i = 0; i < num_caps; ++i) {
+ webrtc::VideoCaptureCapability cap;
+ if (info->GetCapability(vcm_id, i, cap) != -1) {
+ VideoFormat format;
+ if (CapabilityToFormat(cap, &format)) {
+ supported.push_back(format);
+ } else {
+ LOG(LS_WARNING) << "Ignoring unsupported WebRTC capture format "
+ << cap.rawType;
+ }
+ }
+ }
+ factory_->DestroyDeviceInfo(info);
+ if (supported.empty()) {
+ LOG(LS_ERROR) << "Failed to find usable formats for id: " << device.id;
+ return false;
+ }
+
+ module_ = factory_->Create(0, vcm_id);
+ if (!module_) {
+ LOG(LS_ERROR) << "Failed to create capturer for id: " << device.id;
+ return false;
+ }
+
+ // It is safe to change member attributes now.
+ module_->AddRef();
+ SetId(device.id);
+ SetSupportedFormats(supported);
+ return true;
+}
+
+bool WebRtcVideoCapturer::Init(webrtc::VideoCaptureModule* module) {
+ if (module_) {
+ LOG(LS_ERROR) << "The capturer is already initialized";
+ return false;
+ }
+ if (!module) {
+ LOG(LS_ERROR) << "Invalid VCM supplied";
+ return false;
+ }
+ // TODO: Set id and formats.
+ (module_ = module)->AddRef();
+ return true;
+}
+
+bool WebRtcVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired,
+ VideoFormat* best_format) {
+ if (!best_format) {
+ return false;
+ }
+
+ if (!VideoCapturer::GetBestCaptureFormat(desired, best_format)) {
+ // If the vcm has a list of the supported format, but didn't find the
+ // best match, then we should return fail.
+ if (GetSupportedFormats()) {
+ return false;
+ }
+
+ // We maybe using a manually injected VCM which doesn't support enum.
+ // Use the desired format as the best format.
+ best_format->width = desired.width;
+ best_format->height = desired.height;
+ best_format->fourcc = FOURCC_I420;
+ best_format->interval = desired.interval;
+ LOG(LS_INFO) << "Failed to find best capture format,"
+ << " fall back to the requested format "
+ << best_format->ToString();
+ }
+ return true;
+}
+
+CaptureResult WebRtcVideoCapturer::Start(const VideoFormat& capture_format) {
+ if (!module_) {
+ LOG(LS_ERROR) << "The capturer has not been initialized";
+ return CR_NO_DEVICE;
+ }
+
+ if (IsRunning()) {
+ LOG(LS_ERROR) << "The capturer is already running";
+ return CR_FAILURE;
+ }
+
+ SetCaptureFormat(&capture_format);
+
+ webrtc::VideoCaptureCapability cap;
+ if (!FormatToCapability(capture_format, &cap)) {
+ LOG(LS_ERROR) << "Invalid capture format specified";
+ return CR_FAILURE;
+ }
+
+ std::string camera_id(GetId());
+ uint32 start = talk_base::Time();
+ if (module_->RegisterCaptureDataCallback(*this) != 0 ||
+ module_->StartCapture(cap) != 0) {
+ LOG(LS_ERROR) << "Camera '" << camera_id << "' failed to start";
+ return CR_FAILURE;
+ }
+
+ LOG(LS_INFO) << "Camera '" << camera_id << "' started with format "
+ << capture_format.ToString() << ", elapsed time "
+ << talk_base::TimeSince(start) << " ms";
+
+ captured_frames_ = 0;
+ talk_base::Thread::Current()->Post(this);
+ return CR_PENDING;
+}
+
+void WebRtcVideoCapturer::Stop() {
+ if (IsRunning()) {
+ talk_base::Thread::Current()->Clear(this);
+ module_->StopCapture();
+ module_->DeRegisterCaptureDataCallback();
+
+ // TODO: Determine if the VCM exposes any drop stats we can use.
+ double drop_ratio = 0.0;
+ std::string camera_id(GetId());
+ LOG(LS_INFO) << "Camera '" << camera_id << "' stopped after capturing "
+ << captured_frames_ << " frames and dropping "
+ << drop_ratio << "%";
+ }
+ SetCaptureFormat(NULL);
+}
+
+bool WebRtcVideoCapturer::IsRunning() {
+ return (module_ != NULL && module_->CaptureStarted());
+}
+
+bool WebRtcVideoCapturer::GetPreferredFourccs(
+ std::vector<uint32>* fourccs) {
+ if (!fourccs) {
+ return false;
+ }
+
+ fourccs->clear();
+ for (size_t i = 0; i < ARRAY_SIZE(kSupportedFourCCs); ++i) {
+ fourccs->push_back(kSupportedFourCCs[i].fourcc);
+ }
+ return true;
+}
+
+void WebRtcVideoCapturer::OnMessage(talk_base::Message* message) {
+ // TODO: Fire SignalCaptureEvent appropriately.
+ SignalStartResult(this, CR_SUCCESS);
+}
+
+void WebRtcVideoCapturer::OnIncomingCapturedFrame(const WebRtc_Word32 id,
+ webrtc::VideoFrame& sample, webrtc::VideoCodecType codec_type) {
+ ASSERT(IsRunning());
+ ASSERT(codec_type == webrtc::kVideoCodecUnknown);
+
+ ++captured_frames_;
+ // Log the size and pixel aspect ratio of the first captured frame.
+ if (1 == captured_frames_) {
+ LOG(LS_INFO) << "Captured frame size "
+ << sample.Width() << "x" << sample.Height()
+ << ". Expected format " << GetCaptureFormat()->ToString();
+ }
+
+ // Signal down stream components on captured frame.
+ WebRtcCapturedFrame frame(sample);
+ SignalFrameCaptured(this, &frame);
+}
+
+void WebRtcVideoCapturer::OnCaptureDelayChanged(
+ const WebRtc_Word32 id, const WebRtc_Word32 delay) {
+ LOG(LS_INFO) << "Capture delay changed to " << delay << " ms";
+}
+
+// WebRtcCapturedFrame
+WebRtcCapturedFrame::WebRtcCapturedFrame(const webrtc::VideoFrame& sample) {
+ width = sample.Width();
+ height = sample.Height();
+ fourcc = FOURCC_I420;
+ pixel_width = 1;
+ pixel_height = 1;
+ // convert units from VideoFrame RenderTimeMs
+ // to CapturedFrame (nanoseconds)
+ elapsed_time = sample.RenderTimeMs() * talk_base::kNumNanosecsPerMillisec;
+ time_stamp = elapsed_time;
+ data_size = sample.Length();
+ data = const_cast<WebRtc_UWord8*>(sample.Buffer());
+}
+
+} // namespace cricket
+
+#endif // HAVE_WEBRTC_VIDEO
diff --git a/talk/session/phone/webrtcvideocapturer.h b/talk/session/phone/webrtcvideocapturer.h
new file mode 100644
index 0000000..a9cc3e8
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer.h
@@ -0,0 +1,102 @@
+// libjingle
+// Copyright 2004 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
+#define TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
+
+#ifdef HAVE_WEBRTC_VIDEO
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagehandler.h"
+#include "talk/session/phone/videocapturer.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_capture/main/interface/video_capture.h"
+#else
+#include "third_party/webrtc/files/include/video_capture.h"
+#endif
+
+namespace cricket {
+
+// Factory to allow injection of a VCM impl into WebRtcVideoCapturer.
+// DeviceInfos do not have a Release() and therefore need an explicit Destroy().
+class WebRtcVcmFactoryInterface {
+ public:
+ virtual ~WebRtcVcmFactoryInterface() {}
+ virtual webrtc::VideoCaptureModule* Create(
+ int id, const WebRtc_UWord8* device) = 0;
+ virtual webrtc::VideoCaptureModule::DeviceInfo* CreateDeviceInfo(int id) = 0;
+ virtual void DestroyDeviceInfo(
+ webrtc::VideoCaptureModule::DeviceInfo* info) = 0;
+};
+
+// WebRTC-based implementation of VideoCapturer.
+class WebRtcVideoCapturer : public VideoCapturer,
+ public talk_base::MessageHandler,
+ public webrtc::VideoCaptureDataCallback {
+ public:
+ WebRtcVideoCapturer();
+ explicit WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory);
+ virtual ~WebRtcVideoCapturer();
+
+ bool Init(const Device& device);
+ bool Init(webrtc::VideoCaptureModule* module);
+
+ // Override virtual methods of the parent class VideoCapturer.
+ virtual bool GetBestCaptureFormat(const VideoFormat& desired,
+ VideoFormat* best_format);
+ virtual CaptureResult Start(const VideoFormat& capture_format);
+ virtual void Stop();
+ virtual bool IsRunning();
+
+ protected:
+ // Override virtual methods of the parent class VideoCapturer.
+ virtual bool GetPreferredFourccs(std::vector<uint32>* fourccs);
+
+ private:
+ // Callback for our started event.
+ virtual void OnMessage(talk_base::Message* message);
+ // Callback when a frame is captured by camera.
+ virtual void OnIncomingCapturedFrame(const WebRtc_Word32 id,
+ webrtc::VideoFrame& frame,
+ webrtc::VideoCodecType type);
+ virtual void OnCaptureDelayChanged(const WebRtc_Word32 id,
+ const WebRtc_Word32 delay);
+
+ talk_base::scoped_ptr<WebRtcVcmFactoryInterface> factory_;
+ webrtc::VideoCaptureModule* module_;
+ int captured_frames_;
+};
+
+struct WebRtcCapturedFrame : public CapturedFrame {
+ public:
+ explicit WebRtcCapturedFrame(const webrtc::VideoFrame& frame);
+};
+
+} // namespace cricket
+
+#endif // HAVE_WEBRTC_VIDEO
+#endif // TALK_SESSION_PHONE_WEBRTCVIDEOCAPTURER_H_
diff --git a/talk/session/phone/webrtcvideocapturer_unittest.cc b/talk/session/phone/webrtcvideocapturer_unittest.cc
new file mode 100644
index 0000000..c883844
--- /dev/null
+++ b/talk/session/phone/webrtcvideocapturer_unittest.cc
@@ -0,0 +1,145 @@
+// libjingle
+// Copyright 2004--2011 Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. The name of the author may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdio.h>
+#include <vector>
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/fakewebrtcvcmfactory.h"
+#include "talk/session/phone/testutils.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
+
+using cricket::VideoFormat;
+
+static const std::string kTestDeviceName = "JuberTech FakeCam Q123";
+static const std::string kTestDeviceId = "foo://bar/baz";
+const VideoFormat kDefaultVideoFormat =
+ VideoFormat(640, 400, VideoFormat::FpsToInterval(30), cricket::FOURCC_ANY);
+
+class WebRtcVideoCapturerTest : public testing::Test {
+ public:
+ WebRtcVideoCapturerTest()
+ : factory_(new FakeWebRtcVcmFactory),
+ capturer_(new cricket::WebRtcVideoCapturer(factory_)),
+ listener_(capturer_.get()) {
+ factory_->device_info.AddDevice(kTestDeviceName, kTestDeviceId);
+ // add a VGA/I420 capability
+ webrtc::VideoCaptureCapability vga;
+ vga.width = 640;
+ vga.height = 480;
+ vga.maxFPS = 30;
+ vga.rawType = webrtc::kVideoI420;
+ factory_->device_info.AddCapability(kTestDeviceId, vga);
+ }
+
+ protected:
+ FakeWebRtcVcmFactory* factory_; // owned by capturer_
+ talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> capturer_;
+ cricket::VideoCapturerListener listener_;
+};
+
+TEST_F(WebRtcVideoCapturerTest, TestNotOpened) {
+ EXPECT_EQ("", capturer_->GetId());
+ EXPECT_EQ(NULL, capturer_->GetSupportedFormats());
+ EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+ EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestBadInit) {
+ EXPECT_FALSE(capturer_->Init(cricket::Device("bad-name", "bad-id")));
+ EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestInit) {
+ EXPECT_TRUE(capturer_->Init(cricket::Device(kTestDeviceName, kTestDeviceId)));
+ EXPECT_EQ(kTestDeviceId, capturer_->GetId());
+ EXPECT_TRUE(NULL != capturer_->GetSupportedFormats());
+ ASSERT_EQ(1U, capturer_->GetSupportedFormats()->size());
+ EXPECT_EQ(640, (*capturer_->GetSupportedFormats())[0].width);
+ EXPECT_EQ(480, (*capturer_->GetSupportedFormats())[0].height);
+ EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL); // not started yet
+ EXPECT_FALSE(capturer_->IsRunning());
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestInitVcm) {
+ EXPECT_TRUE(capturer_->Init(factory_->Create(0,
+ reinterpret_cast<const WebRtc_UWord8*>(kTestDeviceId.c_str()))));
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCapture) {
+ EXPECT_TRUE(capturer_->Init(cricket::Device(kTestDeviceName, kTestDeviceId)));
+ cricket::VideoFormat format(
+ capturer_->GetSupportedFormats()->at(0));
+ EXPECT_EQ(cricket::CR_PENDING, capturer_->Start(format));
+ EXPECT_TRUE(capturer_->IsRunning());
+ ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+ EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+ EXPECT_EQ_WAIT(cricket::CR_SUCCESS, listener_.start_result(), 1000);
+ EXPECT_TRUE(factory_->modules[0]->SendFrame(640, 480));
+ EXPECT_TRUE_WAIT(listener_.frame_count() > 0, 5000);
+ EXPECT_EQ(capturer_->GetCaptureFormat()->fourcc, listener_.frame_fourcc());
+ EXPECT_EQ(640, listener_.frame_width());
+ EXPECT_EQ(480, listener_.frame_height());
+ EXPECT_EQ(cricket::CR_FAILURE, capturer_->Start(format));
+ capturer_->Stop();
+ EXPECT_FALSE(capturer_->IsRunning());
+ EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCaptureVcm) {
+ EXPECT_TRUE(capturer_->Init(factory_->Create(0,
+ reinterpret_cast<const WebRtc_UWord8*>(kTestDeviceId.c_str()))));
+ EXPECT_FALSE(capturer_->GetSupportedFormats());
+ VideoFormat format;
+ EXPECT_TRUE(capturer_->GetBestCaptureFormat(kDefaultVideoFormat, &format));
+ EXPECT_EQ(kDefaultVideoFormat.width, format.width);
+ EXPECT_EQ(kDefaultVideoFormat.height, format.height);
+ EXPECT_EQ(kDefaultVideoFormat.interval, format.interval);
+ EXPECT_EQ(cricket::FOURCC_I420, format.fourcc);
+ EXPECT_EQ(cricket::CR_PENDING, capturer_->Start(format));
+ EXPECT_TRUE(capturer_->IsRunning());
+ ASSERT_TRUE(capturer_->GetCaptureFormat() != NULL);
+ EXPECT_EQ(format, *capturer_->GetCaptureFormat());
+ EXPECT_EQ_WAIT(cricket::CR_SUCCESS, listener_.start_result(), 1000);
+ EXPECT_TRUE(factory_->modules[0]->SendFrame(640, 480));
+ EXPECT_TRUE_WAIT(listener_.frame_count() > 0, 5000);
+ EXPECT_EQ(capturer_->GetCaptureFormat()->fourcc, listener_.frame_fourcc());
+ EXPECT_EQ(640, listener_.frame_width());
+ EXPECT_EQ(480, listener_.frame_height());
+ EXPECT_EQ(cricket::CR_FAILURE, capturer_->Start(format));
+ capturer_->Stop();
+ EXPECT_FALSE(capturer_->IsRunning());
+ EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+}
+
+TEST_F(WebRtcVideoCapturerTest, TestCaptureWithoutInit) {
+ cricket::VideoFormat format;
+ EXPECT_EQ(cricket::CR_NO_DEVICE, capturer_->Start(format));
+ EXPECT_TRUE(capturer_->GetCaptureFormat() == NULL);
+ EXPECT_FALSE(capturer_->IsRunning());
+}
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index d0d71ad..07ab5e6 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -25,18 +25,26 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
#ifdef HAVE_WEBRTC_VIDEO
#include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/base/basictypes.h"
#include "talk/base/common.h"
#include "talk/base/buffer.h"
#include "talk/base/byteorder.h"
#include "talk/base/logging.h"
#include "talk/base/stringutils.h"
+#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/streamparams.h"
#include "talk/session/phone/videorenderer.h"
#include "talk/session/phone/webrtcpassthroughrender.h"
#include "talk/session/phone/webrtcvoiceengine.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
#include "talk/session/phone/webrtcvideoframe.h"
#include "talk/session/phone/webrtcvie.h"
#include "talk/session/phone/webrtcvoe.h"
@@ -44,32 +52,75 @@
namespace cricket {
static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+
+static const int kMinVideoBitrate = 100;
static const int kStartVideoBitrate = 300;
-static const int kMaxVideoBitrate = 1000;
+static const int kMaxVideoBitrate = 2000;
+static const int kConferenceModeMaxVideoBitrate = 500;
+
+static const int kVideoMtu = 1200;
+
static const int kVideoRtpBufferSize = 65536;
+static const char kVp8PayloadName[] = "VP8";
+static const char kRedPayloadName[] = "red";
+static const char kFecPayloadName[] = "ulpfec";
+
+static const int kDefaultNumberOfTemporalLayers = 3;
+
+static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
+ const char* delim = "\r\n";
+ for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
+ LOG_V(sev) << tok;
+ }
+}
+
class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
public:
explicit WebRtcRenderAdapter(VideoRenderer* renderer)
- : renderer_(renderer) {
+ : renderer_(renderer), width_(0), height_(0) {
+ }
+ virtual ~WebRtcRenderAdapter() {
}
+ void SetRenderer(VideoRenderer* renderer) {
+ talk_base::CritScope cs(&crit_);
+ renderer_ = renderer;
+ }
+ // Implementation of webrtc::ExternalRenderer.
virtual int FrameSizeChange(unsigned int width, unsigned int height,
unsigned int /*number_of_streams*/) {
- if (renderer_ == NULL)
+ talk_base::CritScope cs(&crit_);
+ if (renderer_ == NULL) {
return 0;
+ }
+ LOG(LS_INFO) << "WebRtcRenderAdapter frame size changed to: "
+ << width << "x" << height;
width_ = width;
height_ = height;
return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
}
-
virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
unsigned int time_stamp) {
- if (renderer_ == NULL)
+ talk_base::CritScope cs(&crit_);
+ frame_rate_tracker_.Update(1);
+ if (renderer_ == NULL) {
return 0;
+ }
WebRtcVideoFrame video_frame;
+ // Convert 90K timestamp to ns timestamp.
+ int64 time_stamp_in_ns = (time_stamp / 90) *
+ talk_base::kNumNanosecsPerMillisec;
video_frame.Attach(buffer, buffer_size, width_, height_,
- 1, 1, 0, time_stamp, 0);
+ 1, 1, 0, time_stamp_in_ns, 0);
+
+
+ // Sanity check on decoded frame size.
+ if (buffer_size != static_cast<int>(VideoFrame::SizeOf(width_, height_))) {
+ LOG(LS_WARNING) << "WebRtcRenderAdapter received a strange frame size: "
+ << buffer_size;
+ }
+
int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
uint8* buffer_temp;
size_t buffer_size_temp;
@@ -77,71 +128,221 @@
return ret;
}
- virtual ~WebRtcRenderAdapter() {}
+ unsigned int width() {
+ talk_base::CritScope cs(&crit_);
+ return width_;
+ }
+ unsigned int height() {
+ talk_base::CritScope cs(&crit_);
+ return height_;
+ }
+ int framerate() {
+ talk_base::CritScope cs(&crit_);
+ return frame_rate_tracker_.units_second();
+ }
+ VideoRenderer* renderer() {
+ talk_base::CritScope cs(&crit_);
+ return renderer_;
+ }
private:
+ talk_base::CriticalSection crit_;
VideoRenderer* renderer_;
unsigned int width_;
unsigned int height_;
+ talk_base::RateTracker frame_rate_tracker_;
+};
+
+class WebRtcDecoderObserver : public webrtc::ViEDecoderObserver {
+ public:
+ explicit WebRtcDecoderObserver(int video_channel)
+ : video_channel_(video_channel),
+ framerate_(0),
+ bitrate_(0),
+ firs_requested_(0) {
+ }
+
+ // virtual functions from VieDecoderObserver.
+ virtual void IncomingCodecChanged(const int videoChannel,
+ const webrtc::VideoCodec& videoCodec) {}
+ virtual void IncomingRate(const int videoChannel,
+ const unsigned int framerate,
+ const unsigned int bitrate) {
+ ASSERT(video_channel_ == videoChannel);
+ framerate_ = framerate;
+ bitrate_ = bitrate;
+ }
+ virtual void RequestNewKeyFrame(const int videoChannel) {
+ ASSERT(video_channel_ == videoChannel);
+ ++firs_requested_;
+ }
+
+ int framerate() const { return framerate_; }
+ int bitrate() const { return bitrate_; }
+ int firs_requested() const { return firs_requested_; }
+
+ private:
+ int video_channel_;
+ int framerate_;
+ int bitrate_;
+ int firs_requested_;
+};
+
+class WebRtcEncoderObserver : public webrtc::ViEEncoderObserver {
+ public:
+ explicit WebRtcEncoderObserver(int video_channel)
+ : video_channel_(video_channel),
+ framerate_(0),
+ bitrate_(0) {
+ }
+
+ // virtual functions from VieEncoderObserver.
+ virtual void OutgoingRate(const int videoChannel,
+ const unsigned int framerate,
+ const unsigned int bitrate) {
+ ASSERT(video_channel_ == videoChannel);
+ framerate_ = framerate;
+ bitrate_ = bitrate;
+ }
+
+ int framerate() const { return framerate_; }
+ int bitrate() const { return bitrate_; }
+
+ private:
+ int video_channel_;
+ int framerate_;
+ int bitrate_;
+};
+
+class WebRtcLocalStreamInfo {
+ public:
+ int width() {
+ talk_base::CritScope cs(&crit_);
+ return width_;
+ }
+ int height() {
+ talk_base::CritScope cs(&crit_);
+ return height_;
+ }
+ int framerate() {
+ talk_base::CritScope cs(&crit_);
+ return rate_tracker_.units_second();
+ }
+
+ void UpdateFrame(int width, int height) {
+ talk_base::CritScope cs(&crit_);
+ width_ = width;
+ height_ = height;
+ rate_tracker_.Update(1);
+ }
+
+ private:
+ talk_base::CriticalSection crit_;
+ unsigned int width_;
+ unsigned int height_;
+ talk_base::RateTracker rate_tracker_;
+};
+
+// WebRtcVideoChannelInfo is a container class with members such as renderer
+// and a decoder observer that is used by receive channels.
+// It must exist as long as the receive channel is connected to renderer or a
+// decoder observer in this class and methods in the class should only be called
+// from the worker thread.
+class WebRtcVideoChannelInfo {
+ public:
+ explicit WebRtcVideoChannelInfo(int channel_id)
+ : channel_id_(channel_id),
+ render_adapter_(NULL),
+ decoder_observer_(channel_id) {
+ }
+ int channel_id() { return channel_id_; }
+ void SetRenderer(VideoRenderer* renderer) {
+ render_adapter_.SetRenderer(renderer);
+ }
+ WebRtcRenderAdapter* render_adapter() { return &render_adapter_; }
+ WebRtcDecoderObserver* decoder_observer() { return &decoder_observer_; }
+
+ private:
+ int channel_id_; // Webrtc video channel number.
+ // Renderer for this channel.
+ WebRtcRenderAdapter render_adapter_;
+ WebRtcDecoderObserver decoder_observer_;
};
const WebRtcVideoEngine::VideoCodecPref
WebRtcVideoEngine::kVideoCodecPrefs[] = {
- {"VP8", 120, 0},
+ {kVp8PayloadName, 100, 0},
+ {kRedPayloadName, 101, 1},
+ {kFecPayloadName, 102, 2},
};
+static const int64 kNsPerFrame = 33333333; // 30fps
+
// The formats are sorted by the descending order of width. We use the order to
// find the next format for CPU and bandwidth adaptation.
-const VideoFormat WebRtcVideoEngine::kVideoFormats[] = {
- // TODO: Understand why we have problem with 16:9 formats.
- VideoFormat(1280, 800, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(1280, 720, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(960, 600, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(960, 540, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(640, 400, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(640, 360, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(480, 300, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(480, 270, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(320, 180, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(240, 150, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(240, 135, VideoFormat::FpsToInterval(30), FOURCC_ANY),
- VideoFormat(160, 100, VideoFormat::FpsToInterval(30), FOURCC_ANY),
-//VideoFormat(160, 90, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+const VideoFormatPod WebRtcVideoEngine::kVideoFormats[] = {
+ {1280, 800, kNsPerFrame, FOURCC_ANY},
+ {1280, 720, kNsPerFrame, FOURCC_ANY},
+ {960, 600, kNsPerFrame, FOURCC_ANY},
+ {960, 540, kNsPerFrame, FOURCC_ANY},
+ {640, 400, kNsPerFrame, FOURCC_ANY},
+ {640, 360, kNsPerFrame, FOURCC_ANY},
+ {640, 480, kNsPerFrame, FOURCC_ANY},
+ {480, 300, kNsPerFrame, FOURCC_ANY},
+ {480, 270, kNsPerFrame, FOURCC_ANY},
+ {480, 360, kNsPerFrame, FOURCC_ANY},
+ {320, 200, kNsPerFrame, FOURCC_ANY},
+ {320, 180, kNsPerFrame, FOURCC_ANY},
+ {320, 240, kNsPerFrame, FOURCC_ANY},
+ {240, 150, kNsPerFrame, FOURCC_ANY},
+ {240, 135, kNsPerFrame, FOURCC_ANY},
+ {240, 180, kNsPerFrame, FOURCC_ANY},
+ {160, 100, kNsPerFrame, FOURCC_ANY},
+ {160, 90, kNsPerFrame, FOURCC_ANY},
+ {160, 120, kNsPerFrame, FOURCC_ANY},
};
-// TODO: Understand why 640x400 is not working.
-const VideoFormat WebRtcVideoEngine::kDefaultVideoFormat =
- VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY);
+const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
+ {640, 400, kNsPerFrame, FOURCC_ANY};
-WebRtcVideoEngine::WebRtcVideoEngine()
- : vie_wrapper_(new ViEWrapper()),
- voice_engine_(NULL) {
- Construct();
+WebRtcVideoEngine::WebRtcVideoEngine() {
+ Construct(new ViEWrapper(), new ViETraceWrapper(), NULL);
}
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
- ViEWrapper* vie_wrapper)
- : vie_wrapper_(vie_wrapper),
- voice_engine_(voice_engine) {
- Construct();
+ ViEWrapper* vie_wrapper) {
+ Construct(vie_wrapper, new ViETraceWrapper(), voice_engine);
}
-void WebRtcVideoEngine::Construct() {
- initialized_ = false;
- capture_id_ = -1;
- capture_module_ = NULL;
- external_capture_ = false;
- log_level_ = kDefaultLogSeverity;
- capture_started_ = false;
- render_module_.reset(new WebRtcPassthroughRender());
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ ViEWrapper* vie_wrapper,
+ ViETraceWrapper* tracing) {
+ Construct(vie_wrapper, tracing, voice_engine);
+}
- ApplyLogging();
- if (vie_wrapper_->engine()->SetTraceCallback(this) != 0) {
+void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper,
+ ViETraceWrapper* tracing,
+ WebRtcVoiceEngine* voice_engine) {
+ LOG(LS_INFO) << "WebRtcVideoEngine::WebRtcVideoEngine";
+ vie_wrapper_.reset(vie_wrapper);
+ vie_wrapper_base_initialized_ = false;
+ tracing_.reset(tracing);
+ voice_engine_ = voice_engine;
+ initialized_ = false;
+ log_level_ = kDefaultLogSeverity;
+ render_module_.reset(new WebRtcPassthroughRender());
+ local_renderer_w_ = local_renderer_h_ = 0;
+ local_renderer_ = NULL;
+ owns_capturer_ = false;
+ video_capturer_ = NULL;
+ capture_started_ = false;
+
+ ApplyLogging("");
+ if (tracing_->SetTraceCallback(this) != 0) {
LOG_RTCERR1(SetTraceCallback, this);
}
- // Set default quality levels for our supported codecs. We override them here
+ // Set default quality levels for our supported codecs. We override them here
// if we know your cpu performance is low, and they can be updated explicitly
// by calling SetDefaultCodec. For example by a flute preference setting, or
// by the server with a jec in response to our reported system info.
@@ -149,20 +350,22 @@
kVideoCodecPrefs[0].name,
kDefaultVideoFormat.width,
kDefaultVideoFormat.height,
- kDefaultVideoFormat.framerate(), 0);
+ VideoFormat::IntervalToFps(kDefaultVideoFormat.interval),
+ 0);
if (!SetDefaultCodec(max_codec)) {
LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
}
}
WebRtcVideoEngine::~WebRtcVideoEngine() {
- LOG(LS_INFO) << " WebRtcVideoEngine::~WebRtcVideoEngine";
- vie_wrapper_->engine()->SetTraceCallback(NULL);
- Terminate();
- vie_wrapper_.reset();
- if (capture_module_) {
- capture_module_->Release();
+ ClearCapturer();
+ LOG(LS_INFO) << "WebRtcVideoEngine::~WebRtcVideoEngine";
+ if (initialized_) {
+ Terminate();
}
+ tracing_->SetTraceCallback(NULL);
+ // Test to see if the media processor was deregistered properly.
+ ASSERT(SignalMediaFrame.is_empty());
}
bool WebRtcVideoEngine::Init() {
@@ -180,11 +383,26 @@
bool WebRtcVideoEngine::InitVideoEngine() {
LOG(LS_INFO) << "WebRtcVideoEngine::InitVideoEngine";
- if (vie_wrapper_->base()->Init() != 0) {
- LOG_RTCERR0(Init);
+ // Init WebRTC VideoEngine.
+ if (!vie_wrapper_base_initialized_) {
+ if (vie_wrapper_->base()->Init() != 0) {
+ LOG_RTCERR0(Init);
+ return false;
+ }
+ vie_wrapper_base_initialized_ = true;
+ }
+
+ // Log the VoiceEngine version info.
+ char buffer[1024] = "";
+ if (vie_wrapper_->base()->GetVersion(buffer) != 0) {
+ LOG_RTCERR0(GetVersion);
return false;
}
+ LOG(LS_INFO) << "WebRtc VideoEngine Version:";
+ LogMultiline(talk_base::LS_INFO, buffer);
+
+ // Hook up to VoiceEngine for sync purposes, if supplied.
if (!voice_engine_) {
LOG(LS_WARNING) << "NULL voice engine";
} else if ((vie_wrapper_->base()->SetVoiceEngine(
@@ -193,69 +411,460 @@
return false;
}
+ // Register for callbacks from the engine.
if ((vie_wrapper_->base()->RegisterObserver(*this)) != 0) {
LOG_RTCERR0(RegisterObserver);
return false;
}
+ // Register our custom render module.
if (vie_wrapper_->render()->RegisterVideoRenderModule(
- *render_module_.get()) != 0) {
+ *render_module_.get()) != 0) {
LOG_RTCERR0(RegisterVideoRenderModule);
return false;
}
- std::sort(video_codecs_.begin(), video_codecs_.end(),
- &VideoCodec::Preferable);
-
initialized_ = true;
return true;
}
-void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
- LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
+void WebRtcVideoEngine::Terminate() {
+ LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
+ initialized_ = false;
+ SetCapture(false);
+
+ if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
+ *render_module_.get()) != 0) {
+ LOG_RTCERR0(DeRegisterVideoRenderModule);
+ }
+
+ if (vie_wrapper_->base()->DeregisterObserver() != 0) {
+ LOG_RTCERR0(DeregisterObserver);
+ }
+
+ if (vie_wrapper_->base()->SetVoiceEngine(NULL) != 0) {
+ LOG_RTCERR0(SetVoiceEngine);
+ }
}
-// Ignore spammy trace messages, mostly from the stats API when we haven't
-// gotten RTCP info yet from the remote side.
-static bool ShouldIgnoreTrace(const std::string& trace) {
- static const char* kTracesToIgnore[] = {
- "\tfailed to GetReportBlockInformation",
- NULL
- };
- for (const char* const* p = kTracesToIgnore; *p; ++p) {
- if (trace.find(*p) == 0) {
+int WebRtcVideoEngine::GetCapabilities() {
+ return VIDEO_RECV | VIDEO_SEND;
+}
+
+bool WebRtcVideoEngine::SetOptions(int options) {
+ return true;
+}
+
+bool WebRtcVideoEngine::SetDefaultEncoderConfig(
+ const VideoEncoderConfig& config) {
+ return SetDefaultCodec(config.max_codec);
+}
+
+// SetDefaultCodec may be called while the capturer is running. For example, a
+// test call is started in a page with QVGA default codec, and then a real call
+// is started in another page with VGA default codec. This is the corner case
+// and happens only when a session is started. We ignore this case currently.
+bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
+ if (!RebuildCodecList(codec)) {
+ LOG(LS_WARNING) << "Failed to RebuildCodecList";
+ return false;
+ }
+
+ default_codec_format_ = VideoFormat(
+ video_codecs_[0].width,
+ video_codecs_[0].height,
+ VideoFormat::FpsToInterval(video_codecs_[0].framerate),
+ FOURCC_ANY);
+ return true;
+}
+
+WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
+ VoiceMediaChannel* voice_channel) {
+ WebRtcVideoMediaChannel* channel =
+ new WebRtcVideoMediaChannel(this, voice_channel);
+ if (!channel->Init()) {
+ delete channel;
+ channel = NULL;
+ }
+ return channel;
+}
+
+bool WebRtcVideoEngine::SetCaptureDevice(const Device* device) {
+ const bool owns_capturer = true;
+ if (!device) {
+ if (!SetCapturer(NULL, owns_capturer)) {
+ return false;
+ }
+ LOG(LS_INFO) << "Camera set to NULL";
+ return true;
+ }
+ // No-op if the device hasn't changed.
+ if ((video_capturer_ != NULL) && video_capturer_->GetId() == device->id) {
+ return true;
+ }
+ // Create a new capturer for the specified device.
+ VideoCapturer* capturer = CreateVideoCapturer(*device);
+ if (!capturer) {
+ LOG(LS_ERROR) << "Failed to create camera '" << device->name << "', id='"
+ << device->id << "'";
+ return false;
+ }
+ if (!SetCapturer(capturer, owns_capturer)) {
+ return false;
+ }
+ LOG(LS_INFO) << "Camera set to '" << device->name << "', id='"
+ << device->id << "'";
+ return true;
+}
+
+bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
+ const bool owns_capturer = true;
+ if (!vcm) {
+ if (!SetCapturer(NULL, owns_capturer)) {
+ return false;
+ }
+ LOG(LS_INFO) << "Camera set to NULL";
+ return true;
+ }
+ // Create a new capturer for the specified device.
+ WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
+ if (!capturer->Init(vcm)) {
+ LOG(LS_ERROR) << "Failed to create camera from VCM";
+ delete capturer;
+ return false;
+ }
+ if (!SetCapturer(capturer, owns_capturer)) {
+ return false;
+ }
+ LOG(LS_INFO) << "Camera created with VCM";
+ CaptureResult ret = SetCapture(true);
+ if (ret != cricket::CR_SUCCESS && ret != cricket::CR_PENDING) {
+ LOG(LS_ERROR) << "Failed to start camera.";
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoEngine::SetVideoCapturer(VideoCapturer* capturer,
+ uint32 /*ssrc*/) {
+ const bool owns_capturer = false;
+ return SetCapturer(capturer, owns_capturer);
+}
+
+bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
+ local_renderer_w_ = local_renderer_h_ = 0;
+ local_renderer_ = renderer;
+ return true;
+}
+
+CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
+ bool old_capture = capture_started_;
+ capture_started_ = capture;
+ CaptureResult res = UpdateCapturingState();
+ if (res != CR_SUCCESS && res != CR_PENDING) {
+ capture_started_ = old_capture;
+ }
+ return res;
+}
+
+VideoCapturer* WebRtcVideoEngine::CreateVideoCapturer(const Device& device) {
+ WebRtcVideoCapturer* capturer = new WebRtcVideoCapturer;
+ if (!capturer->Init(device)) {
+ delete capturer;
+ return NULL;
+ }
+ return capturer;
+}
+
+CaptureResult WebRtcVideoEngine::UpdateCapturingState() {
+ CaptureResult result = CR_SUCCESS;
+
+ bool capture = capture_started_;
+ if (!IsCapturing() && capture) { // Start capturing.
+ if (video_capturer_ == NULL) {
+ return CR_NO_DEVICE;
+ }
+
+ VideoFormat capture_format;
+ if (!video_capturer_->GetBestCaptureFormat(default_codec_format_,
+ &capture_format)) {
+ LOG(LS_WARNING) << "Unsupported format:"
+ << " width=" << default_codec_format_.width
+ << " height=" << default_codec_format_.height
+ << ". Supported formats are:";
+ const std::vector<VideoFormat>* formats =
+ video_capturer_->GetSupportedFormats();
+ if (formats) {
+ for (std::vector<VideoFormat>::const_iterator i = formats->begin();
+ i != formats->end(); ++i) {
+ const VideoFormat& format = *i;
+ LOG(LS_WARNING) << " " << GetFourccName(format.fourcc) << ":"
+ << format.width << "x" << format.height << "x"
+ << format.framerate();
+ }
+ }
+ return CR_FAILURE;
+ }
+
+ // Start the video capturer.
+ result = video_capturer_->Start(capture_format);
+ if (CR_SUCCESS != result && CR_PENDING != result) {
+ LOG(LS_ERROR) << "Failed to start the video capturer";
+ return result;
+ }
+ } else if (IsCapturing() && !capture) { // Stop capturing.
+ video_capturer_->Stop();
+ }
+
+ return result;
+}
+
+bool WebRtcVideoEngine::IsCapturing() const {
+ return (video_capturer_ != NULL) && video_capturer_->IsRunning();
+}
+
+void WebRtcVideoEngine::OnFrameCaptured(VideoCapturer* capturer,
+ const CapturedFrame* frame) {
+ // Force 16:10 for now. We'll be smarter with the capture refactor.
+ int cropped_height = frame->width * default_codec_format_.height
+ / default_codec_format_.width;
+ if (cropped_height > frame->height) {
+ // TODO: Once we support horizontal cropping, add cropped_width.
+ cropped_height = frame->height;
+ }
+
+ // This CapturedFrame* will already be in I420. In the future, when
+ // WebRtcVideoFrame has support for independent planes, we can just attach
+ // to it and update the pointers when cropping.
+ WebRtcVideoFrame i420_frame;
+ if (!i420_frame.Init(frame, frame->width, cropped_height)) {
+ LOG(LS_ERROR) << "Couldn't convert to I420! "
+ << frame->width << " x " << cropped_height;
+ return;
+ }
+
+ // TODO: This is the trigger point for Tx video processing.
+ // Once the capturer refactoring is done, we will move this into the
+ // capturer...it's not there right now because that image is in not in the
+ // I420 color space.
+ // The clients that subscribe will obtain meta info from the frame.
+ // When this trigger is switched over to capturer, need to pass in the real
+ // ssrc.
+ {
+ talk_base::CritScope cs(&signal_media_critical_);
+ SignalMediaFrame(kDummyVideoSsrc, &i420_frame);
+ }
+
+ // Send I420 frame to the local renderer.
+ if (local_renderer_) {
+ if (local_renderer_w_ != static_cast<int>(i420_frame.GetWidth()) ||
+ local_renderer_h_ != static_cast<int>(i420_frame.GetHeight())) {
+ local_renderer_->SetSize(local_renderer_w_ = i420_frame.GetWidth(),
+ local_renderer_h_ = i420_frame.GetHeight(), 0);
+ }
+ local_renderer_->RenderFrame(&i420_frame);
+ }
+
+ // Send I420 frame to the registered senders.
+ talk_base::CritScope cs(&channels_crit_);
+ for (VideoChannels::iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ if ((*it)->sending()) (*it)->SendFrame(0, &i420_frame);
+ }
+}
+
+const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
+ return video_codecs_;
+}
+
+void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
+ // if min_sev == -1, we keep the current log level.
+ if (min_sev >= 0) {
+ log_level_ = min_sev;
+ }
+ ApplyLogging(filter);
+}
+
+int WebRtcVideoEngine::GetLastEngineError() {
+ return vie_wrapper_->error();
+}
+
+// Checks to see whether we comprehend and could receive a particular codec
+bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
+ for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
+ const VideoFormat fmt(kVideoFormats[i]);
+ if ((in.width == 0 && in.height == 0) ||
+ (fmt.width == in.width && fmt.height == in.height)) {
+ for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
+ VideoCodec codec(kVideoCodecPrefs[j].payload_type,
+ kVideoCodecPrefs[j].name, 0, 0, 0, 0);
+ if (codec.Matches(in)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// Given the requested codec, returns true if we can send that codec type and
+// updates out with the best quality we could send for that codec. If current is
+// not empty, we constrain out so that its aspect ratio matches current's.
+bool WebRtcVideoEngine::CanSendCodec(const VideoCodec& requested,
+ const VideoCodec& current,
+ VideoCodec* out) {
+ if (!out) {
+ return false;
+ }
+
+ std::vector<VideoCodec>::const_iterator local_max;
+ for (local_max = video_codecs_.begin();
+ local_max < video_codecs_.end();
+ ++local_max) {
+ // First match codecs by payload type
+ if (!requested.Matches(local_max->id, local_max->name)) {
+ continue;
+ }
+
+ out->id = requested.id;
+ out->name = requested.name;
+ out->preference = requested.preference;
+ out->framerate = talk_base::_min(requested.framerate, local_max->framerate);
+ out->width = 0;
+ out->height = 0;
+
+ if (0 == requested.width && 0 == requested.height) {
+ // Special case with resolution 0. The channel should not send frames.
+ return true;
+ } else if (0 == requested.width || 0 == requested.height) {
+ // 0xn and nx0 are invalid resolutions.
+ return false;
+ }
+
+ // Pick the best quality that is within their and our bounds and has the
+ // correct aspect ratio.
+ for (int j = 0; j < ARRAY_SIZE(kVideoFormats); ++j) {
+ const VideoFormat format(kVideoFormats[j]);
+
+ // Skip any format that is larger than the local or remote maximums, or
+ // smaller than the current best match
+ if (format.width > requested.width || format.height > requested.height ||
+ format.width > local_max->width ||
+ (format.width < out->width && format.height < out->height)) {
+ continue;
+ }
+
+ bool better = false;
+
+ // Check any further constraints on this prospective format
+ if (!out->width || !out->height) {
+ // If we don't have any matches yet, this is the best so far.
+ better = true;
+ } else if (current.width && current.height) {
+ // current is set so format must match its ratio exactly.
+ better =
+ (format.width * current.height == format.height * current.width);
+ } else {
+ // Prefer closer aspect ratios i.e
+ // format.aspect - requested.aspect < out.aspect - requested.aspect
+ better = abs(format.width * requested.height * out->height -
+ requested.width * format.height * out->height) <
+ abs(out->width * format.height * requested.height -
+ requested.width * format.height * out->height);
+ }
+
+ if (better) {
+ out->width = format.width;
+ out->height = format.height;
+ }
+ }
+ if (out->width > 0) {
return true;
}
}
return false;
}
-void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
- const char* trace, const int length) {
- talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
- if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
- sev = talk_base::LS_ERROR;
- else if (level == webrtc::kTraceWarning)
- sev = talk_base::LS_WARNING;
- else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
- sev = talk_base::LS_INFO;
-
- if (sev >= log_level_) {
- // Skip past boilerplate prefix text
- if (length < 72) {
- std::string msg(trace, length);
- LOG(LS_ERROR) << "Malformed webrtc log message: ";
- LOG_V(sev) << msg;
- } else {
- std::string msg(trace + 71, length - 72);
- if (!ShouldIgnoreTrace(msg)) {
- LOG_V(sev) << "WebRtc ViE:" << msg;
- }
- }
- }
+void WebRtcVideoEngine::ConvertToCricketVideoCodec(
+ const webrtc::VideoCodec& in_codec, VideoCodec* out_codec) {
+ out_codec->id = in_codec.plType;
+ out_codec->name = in_codec.plName;
+ out_codec->width = in_codec.width;
+ out_codec->height = in_codec.height;
+ out_codec->framerate = in_codec.maxFramerate;
}
-void WebRtcVideoEngine::ApplyLogging() {
+bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
+ const VideoCodec& in_codec, webrtc::VideoCodec* out_codec) {
+ bool found = false;
+ int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ if (vie_wrapper_->codec()->GetCodec(i, *out_codec) == 0 &&
+ _stricmp(in_codec.name.c_str(), out_codec->plName) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ LOG(LS_ERROR) << "invalid codec type";
+ return false;
+ }
+
+ if (in_codec.id != 0)
+ out_codec->plType = in_codec.id;
+
+ if (in_codec.width != 0)
+ out_codec->width = in_codec.width;
+
+ if (in_codec.height != 0)
+ out_codec->height = in_codec.height;
+
+ if (in_codec.framerate != 0)
+ out_codec->maxFramerate = in_codec.framerate;
+
+ // Init the codec with the default bandwidth options.
+ out_codec->minBitrate = kMinVideoBitrate;
+ out_codec->startBitrate = kStartVideoBitrate;
+ out_codec->maxBitrate = kMaxVideoBitrate;
+
+ return true;
+}
+
+void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
+ talk_base::CritScope cs(&channels_crit_);
+ channels_.push_back(channel);
+}
+
+void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
+ talk_base::CritScope cs(&channels_crit_);
+ channels_.erase(std::remove(channels_.begin(), channels_.end(), channel),
+ channels_.end());
+}
+
+bool WebRtcVideoEngine::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) {
+ if (initialized_) {
+ LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init";
+ return false;
+ }
+ voice_engine_ = voice_engine;
+ return true;
+}
+
+bool WebRtcVideoEngine::EnableTimedRender() {
+ if (initialized_) {
+ LOG(LS_WARNING) << "EnableTimedRender can not be called after Init";
+ return false;
+ }
+ render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL,
+ false, webrtc::kRenderExternal));
+ return true;
+}
+
+// See https://sites.google.com/a/google.com/wavelet/
+// Home/Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters
+// for all supported command line setttings.
+void WebRtcVideoEngine::ApplyLogging(const std::string& log_filter) {
int filter = 0;
switch (log_level_) {
case talk_base::LS_VERBOSE: filter |= webrtc::kTraceAll;
@@ -264,6 +873,19 @@
case talk_base::LS_ERROR: filter |=
webrtc::kTraceError | webrtc::kTraceCritical;
}
+ tracing_->SetTraceFilter(filter);
+
+ // Set WebRTC trace file.
+ std::vector<std::string> opts;
+ talk_base::tokenize(log_filter, ' ', '"', '"', &opts);
+ std::vector<std::string>::iterator tracefile =
+ std::find(opts.begin(), opts.end(), "tracefile");
+ if (tracefile != opts.end() && ++tracefile != opts.end()) {
+ // Write WebRTC debug output (at same loglevel) to file
+ if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
+ LOG_RTCERR1(SetTraceFile, *tracefile);
+ }
+ }
}
// Rebuilds the codec list to be only those that are less intensive
@@ -290,324 +912,101 @@
return true;
}
-void WebRtcVideoEngine::Terminate() {
- LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
- initialized_ = false;
- SetCapture(false);
- if (local_renderer_.get()) {
- // If the renderer already set, stop it first
- if (vie_wrapper_->render()->StopRender(capture_id_) != 0)
- LOG_RTCERR1(StopRender, capture_id_);
- }
-
- if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
- *render_module_.get()) != 0)
- LOG_RTCERR0(DeRegisterVideoRenderModule);
-
- if ((vie_wrapper_->base()->DeregisterObserver()) != 0)
- LOG_RTCERR0(DeregisterObserver);
-
- if ((vie_wrapper_->base()->SetVoiceEngine(NULL)) != 0)
- LOG_RTCERR0(SetVoiceEngine);
-
- if (vie_wrapper_->engine()->SetTraceCallback(NULL) != 0)
- LOG_RTCERR0(SetTraceCallback);
-}
-
-int WebRtcVideoEngine::GetCapabilities() {
- return VIDEO_RECV | VIDEO_SEND;
-}
-
-bool WebRtcVideoEngine::SetOptions(int options) {
- return true;
-}
-
-bool WebRtcVideoEngine::ReleaseCaptureDevice() {
- if (capture_id_ != -1) {
- // Stop capture
- SetCapture(false);
- // DisconnectCaptureDevice
- WebRtcVideoMediaChannel* channel;
- for (VideoChannels::const_iterator it = channels_.begin();
- it != channels_.end(); ++it) {
- ASSERT(*it != NULL);
- channel = *it;
- // Ignore the return value here as the channel may not have connected to
- // the capturer yet.
- vie_wrapper_->capture()->DisconnectCaptureDevice(
- channel->video_channel());
- channel->set_connected(false);
- }
- // ReleaseCaptureDevice
- vie_wrapper_->capture()->ReleaseCaptureDevice(capture_id_);
- capture_id_ = -1;
- }
-
- return true;
-}
-
-bool WebRtcVideoEngine::SetCaptureDevice(const Device* cam) {
- ASSERT(cam != NULL);
-
- ReleaseCaptureDevice();
-
- webrtc::ViECapture* vie_capture = vie_wrapper_->capture();
-
- // There's an external VCM
- if (capture_module_) {
- if (vie_capture->AllocateCaptureDevice(*capture_module_, capture_id_) != 0)
- ASSERT(capture_id_ == -1);
- } else if (!external_capture_) {
- char device_name[256], device_id[256];
- bool found = false;
- for (int i = 0; i < vie_capture->NumberOfCaptureDevices(); ++i) {
- if (vie_capture->GetCaptureDevice(i, device_name, ARRAY_SIZE(device_name),
- device_id,
- ARRAY_SIZE(device_id)) == 0) {
- // TODO: We should only compare the device_id here,
- // however the devicemanager and webrtc use different format for th v4l2
- // device id. So here we also compare the device_name for now.
- // For example "usb-0000:00:1d.7-6" vs "/dev/video0".
- if (cam->name.compare(device_name) == 0 ||
- cam->id.compare(device_id) == 0) {
- LOG(INFO) << "Found video capture device: " << device_name;
- found = true;
- break;
- }
- }
- }
- if (!found) {
+bool WebRtcVideoEngine::SetCapturer(VideoCapturer* capturer,
+ bool own_capturer) {
+ if (capturer == NULL) {
+ // Stop capturing before clearing the capturer.
+ if (SetCapture(false) != CR_SUCCESS) {
+ LOG(LS_WARNING) << "Camera failed to stop";
return false;
}
- if (vie_capture->AllocateCaptureDevice(device_id, strlen(device_id),
- capture_id_) != 0) {
- ASSERT(capture_id_ == -1);
- }
+ ClearCapturer();
+ return true;
}
-
- if (capture_id_ != -1) {
- // Connect to all the channels if there is any.
- WebRtcVideoMediaChannel* channel;
- for (VideoChannels::const_iterator it = channels_.begin();
- it != channels_.end(); ++it) {
- ASSERT(*it != NULL);
- channel = *it;
-
- // No channel should have been connected yet.
- // In case of switching device, all channel connections should have been
- // disconnected in ReleaseCaptureDevice() first.
- ASSERT(!channel->connected());
-
- if (vie_capture->ConnectCaptureDevice(capture_id_,
- channel->video_channel()) == 0) {
- channel->set_connected(true);
- } else {
- LOG(LS_WARNING) << "SetCaptureDevice failed to ConnectCaptureDevice.";
- }
- }
- SetCapture(true);
- }
-
- return (capture_id_ != -1);
-}
-
-bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
- ReleaseCaptureDevice();
- if (capture_module_) {
- capture_module_->Release();
- capture_module_ = NULL;
- }
-
- if (vcm) {
- capture_module_ = vcm;
- capture_module_->AddRef();
- external_capture_ = true;
- } else {
- external_capture_ = false;
- }
-
- return true;
-}
-
-bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
- if (local_renderer_.get()) {
- // If the renderer already set, stop and remove it first
- if (vie_wrapper_->render()->StopRender(capture_id_) != 0) {
- LOG_RTCERR1(StopRender, capture_id_);
- }
- if (vie_wrapper_->render()->RemoveRenderer(capture_id_) != 0) {
- LOG_RTCERR1(RemoveRenderer, capture_id_);
- }
- }
- local_renderer_.reset(new WebRtcRenderAdapter(renderer));
-
- int ret;
- ret = vie_wrapper_->render()->AddRenderer(capture_id_,
- webrtc::kVideoI420,
- local_renderer_.get());
- if (ret != 0)
+ // Hook up signals and install the supplied capturer.
+ SignalCaptureResult.repeat(capturer->SignalStartResult);
+ capturer->SignalFrameCaptured.connect(this,
+ &WebRtcVideoEngine::OnFrameCaptured);
+ ClearCapturer();
+ video_capturer_ = capturer;
+ owns_capturer_ = own_capturer;
+ // Possibly restart the capturer if it is supposed to be running.
+ CaptureResult result = UpdateCapturingState();
+ if (result != CR_SUCCESS && result != CR_PENDING) {
+ LOG(LS_WARNING) << "Camera failed to restart";
return false;
- ret = vie_wrapper_->render()->StartRender(capture_id_);
- return (ret == 0);
-}
-
-CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
- if ((capture_started_ != capture) && (capture_id_ != -1)) {
- int ret;
- if (capture)
- ret = vie_wrapper_->capture()->StartCapture(capture_id_);
- else
- ret = vie_wrapper_->capture()->StopCapture(capture_id_);
- if (ret != 0)
- return CR_NO_DEVICE;
- capture_started_ = capture;
}
- return CR_SUCCESS;
-}
-
-const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
- return video_codecs_;
-}
-
-void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
- log_level_ = min_sev;
- ApplyLogging();
-}
-
-int WebRtcVideoEngine::GetLastEngineError() {
- return vie_wrapper_->error();
-}
-
-bool WebRtcVideoEngine::SetDefaultEncoderConfig(
- const VideoEncoderConfig& config) {
- default_encoder_config_ = config;
return true;
}
-WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
- VoiceMediaChannel* voice_channel) {
- WebRtcVideoMediaChannel* channel =
- new WebRtcVideoMediaChannel(this, voice_channel);
- if (channel) {
- if (!channel->Init()) {
- delete channel;
- channel = NULL;
- }
- }
- return channel;
+void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
+ LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
}
-// Checks to see whether we comprehend and could receive a particular codec
-bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
- for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
- const VideoFormat& fmt = kVideoFormats[i];
- if ((in.width == 0 && in.height == 0) ||
- (fmt.width == in.width && fmt.height == in.height)) {
- for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
- VideoCodec codec(kVideoCodecPrefs[j].payload_type,
- kVideoCodecPrefs[j].name, 0, 0, 0, 0);
- if (codec.Matches(in)) {
- return true;
- }
- }
+// Ignore spammy trace messages, mostly from the stats API when we haven't
+// gotten RTCP info yet from the remote side.
+bool WebRtcVideoEngine::ShouldIgnoreTrace(const std::string& trace) {
+ static const char* const kTracesToIgnore[] = {
+ NULL
+ };
+ for (const char* const* p = kTracesToIgnore; *p; ++p) {
+ if (trace.find(*p) == 0) {
+ return true;
}
}
return false;
}
-// SetDefaultCodec may be called while the capturer is running. For example, a
-// test call is started in a page with QVGA default codec, and then a real call
-// is started in another page with VGA default codec. This is the corner case
-// and happens only when a session is started. We ignore this case currently.
-bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
- if (!RebuildCodecList(codec)) {
- LOG(LS_WARNING) << "Failed to RebuildCodecList";
- return false;
- }
- return true;
+int WebRtcVideoEngine::GetNumOfChannels() {
+ talk_base::CritScope cs(&channels_crit_);
+ return channels_.size();
}
-void WebRtcVideoEngine::ConvertToCricketVideoCodec(
- const webrtc::VideoCodec& in_codec, VideoCodec& out_codec) {
- out_codec.id = in_codec.plType;
- out_codec.name = in_codec.plName;
- out_codec.width = in_codec.width;
- out_codec.height = in_codec.height;
- out_codec.framerate = in_codec.maxFramerate;
-}
+void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
+ const char* trace, const int length) {
+ talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
+ if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
+ sev = talk_base::LS_ERROR;
+ else if (level == webrtc::kTraceWarning)
+ sev = talk_base::LS_WARNING;
+ else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
+ sev = talk_base::LS_INFO;
-bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
- const VideoCodec& in_codec, webrtc::VideoCodec& out_codec) {
- bool found = false;
- int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
- for (int i = 0; i < ncodecs; ++i) {
- if ((vie_wrapper_->codec()->GetCodec(i, out_codec) == 0) &&
- (strncmp(out_codec.plName,
- in_codec.name.c_str(),
- webrtc::kPayloadNameSize - 1) == 0)) {
- found = true;
- break;
+ if (sev >= log_level_) {
+ // Skip past boilerplate prefix text
+ if (length < 72) {
+ std::string msg(trace, length);
+ LOG(LS_ERROR) << "Malformed webrtc log message: ";
+ LOG_V(sev) << msg;
+ } else {
+ std::string msg(trace + 71, length - 72);
+ if (!ShouldIgnoreTrace(msg) &&
+ (!voice_engine_ || !voice_engine_->ShouldIgnoreTrace(msg))) {
+ LOG_V(sev) << "WebRtc:" << msg;
+ }
}
}
+}
- if (!found) {
- LOG(LS_ERROR) << "invalid codec type";
- return false;
- }
-
- if (in_codec.id != 0)
- out_codec.plType = in_codec.id;
-
- if (in_codec.width != 0)
- out_codec.width = in_codec.width;
-
- if (in_codec.height != 0)
- out_codec.height = in_codec.height;
-
- if (in_codec.framerate != 0)
- out_codec.maxFramerate = in_codec.framerate;
-
- out_codec.maxBitrate = kMaxVideoBitrate;
- out_codec.startBitrate = kStartVideoBitrate;
- out_codec.minBitrate = kStartVideoBitrate;
-
+bool WebRtcVideoEngine::RegisterProcessor(
+ VideoProcessor* video_processor) {
+ talk_base::CritScope cs(&signal_media_critical_);
+ SignalMediaFrame.connect(video_processor,
+ &VideoProcessor::OnFrame);
+ return true;
+}
+bool WebRtcVideoEngine::UnregisterProcessor(
+ VideoProcessor* video_processor) {
+ talk_base::CritScope cs(&signal_media_critical_);
+ SignalMediaFrame.disconnect(video_processor);
return true;
}
-int WebRtcVideoEngine::GetLastVideoEngineError() {
- return vie_wrapper_->base()->LastError();
-}
-
-void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
- channels_.push_back(channel);
-}
-
-void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
- VideoChannels::iterator i = std::find(channels_.begin(),
- channels_.end(),
- channel);
- if (i != channels_.end()) {
- channels_.erase(i);
+void WebRtcVideoEngine::ClearCapturer() {
+ if (owns_capturer_) {
+ delete video_capturer_;
}
-}
-
-bool WebRtcVideoEngine::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) {
- if (initialized_) {
- LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init.";
- return false;
- }
- voice_engine_ = voice_engine;
- return true;
-}
-
-bool WebRtcVideoEngine::EnableTimedRender() {
- if (initialized_) {
- LOG(LS_WARNING) << "EnableTimedRender can not be called after Init.";
- return false;
- }
- render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL,
- false, webrtc::kRenderExternal));
- return true;
+ video_capturer_ = NULL;
}
// WebRtcVideoMediaChannel
@@ -617,296 +1016,608 @@
: engine_(engine),
voice_channel_(channel),
vie_channel_(-1),
+ vie_capture_(-1),
+ external_capture_(NULL),
sending_(false),
- connected_(false),
render_started_(false),
- send_codec_(NULL) {
+ muted_(false),
+ local_ssrc_(0),
+ first_receive_ssrc_(0),
+ send_min_bitrate_(kMinVideoBitrate),
+ send_start_bitrate_(kStartVideoBitrate),
+ send_max_bitrate_(kMaxVideoBitrate),
+ local_stream_info_(new WebRtcLocalStreamInfo()),
+ channel_options_(0) {
engine->RegisterChannel(this);
}
bool WebRtcVideoMediaChannel::Init() {
- bool ret = true;
- if (engine_->video_engine()->base()->CreateChannel(vie_channel_) != 0) {
+ if (engine_->vie()->base()->CreateChannel(vie_channel_) != 0) {
LOG_RTCERR1(CreateChannel, vie_channel_);
return false;
}
+ if (!ConfigureChannel(vie_channel_)) {
+ engine_->vie()->base()->DeleteChannel(vie_channel_);
+ vie_channel_ = -1;
+ return false;
+ }
+
+ if (!ConfigureReceiving(vie_channel_, 0)) {
+ engine_->vie()->base()->DeleteChannel(vie_channel_);
+ vie_channel_ = -1;
+ return false;
+ }
LOG(LS_INFO) << "WebRtcVideoMediaChannel::Init "
- << "video_channel " << vie_channel_ << " created";
+ << "vie_channel " << vie_channel_ << " created";
- // connect audio channel
- if (voice_channel_) {
- WebRtcVoiceMediaChannel* channel =
- static_cast<WebRtcVoiceMediaChannel*> (voice_channel_);
- if (engine_->video_engine()->base()->ConnectAudioChannel(
- vie_channel_, channel->voe_channel()) != 0) {
- LOG(LS_WARNING) << "ViE ConnectAudioChannel failed"
- << "A/V not synchronized";
- // Don't set ret to false;
- }
+ // Register external capture.
+ if (engine()->vie()->capture()->AllocateExternalCaptureDevice(
+ vie_capture_, external_capture_) != 0) {
+ LOG_RTCERR0(AllocateExternalCaptureDevice);
+ return false;
}
- // Register external transport
- if (engine_->video_engine()->network()->RegisterSendTransport(
- vie_channel_, *this) != 0) {
- ret = false;
- } else {
- EnableRtcp();
- EnablePLI();
+ // Connect external capture.
+ if (engine()->vie()->capture()->ConnectCaptureDevice(
+ vie_capture_, vie_channel_) != 0) {
+ LOG_RTCERR2(ConnectCaptureDevice, vie_capture_, vie_channel_);
+ return false;
}
- return ret;
+
+ // Register encoder observer for outgoing framerate and bitrate.
+ encoder_observer_.reset(new WebRtcEncoderObserver(vie_channel_));
+ if (engine()->vie()->codec()->RegisterEncoderObserver(
+ vie_channel_, *encoder_observer_) != 0) {
+ LOG_RTCERR1(RegisterEncoderObserver, encoder_observer_.get());
+ return false;
+ }
+
+ return true;
}
WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
- // Stop and remote renderer
- SetRender(false);
- if (engine()->video_engine()->render()->RemoveRenderer(vie_channel_)
- == -1) {
- LOG_RTCERR1(RemoveRenderer, vie_channel_);
+ if (vie_channel_ != -1) {
+ // Stop sending.
+ SetSend(false);
+ if (engine()->vie()->codec()->DeregisterEncoderObserver(
+ vie_channel_) != 0) {
+ LOG_RTCERR1(DeregisterEncoderObserver, vie_channel_);
+ }
+
+ // Stop the renderer.
+ SetRender(false);
+
+ // Destroy the external capture interface.
+ if (vie_capture_ != -1) {
+ if (engine()->vie()->capture()->DisconnectCaptureDevice(
+ vie_channel_) != 0) {
+ LOG_RTCERR1(DisconnectCaptureDevice, vie_channel_);
+ }
+ if (engine()->vie()->capture()->ReleaseCaptureDevice(
+ vie_capture_) != 0) {
+ LOG_RTCERR1(ReleaseCaptureDevice, vie_capture_);
+ }
+ }
+
+ // Remove all receive streams and the default channel.
+ while (!mux_channels_.empty()) {
+ RemoveRecvStream(mux_channels_.begin()->first);
+ }
}
- // DeRegister external transport
- if (engine()->video_engine()->network()->DeregisterSendTransport(
- vie_channel_) == -1) {
- LOG_RTCERR1(DeregisterSendTransport, vie_channel_);
- }
-
- // Unregister RtcChannel with the engine.
+ // Unregister the channel from the engine.
engine()->UnregisterChannel(this);
-
- // Delete VideoChannel
- if (engine()->video_engine()->base()->DeleteChannel(vie_channel_) == -1) {
- LOG_RTCERR1(DeleteChannel, vie_channel_);
- }
}
bool WebRtcVideoMediaChannel::SetRecvCodecs(
const std::vector<VideoCodec>& codecs) {
- bool ret = true;
+ receive_codecs_.clear();
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
iter != codecs.end(); ++iter) {
if (engine()->FindCodec(*iter)) {
webrtc::VideoCodec wcodec;
- if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec)) {
- if (engine()->video_engine()->codec()->SetReceiveCodec(
- vie_channel_, wcodec) != 0) {
- LOG_RTCERR2(SetReceiveCodec, vie_channel_, wcodec.plName);
- ret = false;
- }
+ if (engine()->ConvertFromCricketVideoCodec(*iter, &wcodec)) {
+ receive_codecs_.push_back(wcodec);
}
} else {
- LOG(LS_INFO) << "Unknown codec" << iter->name;
- ret = false;
+ LOG(LS_INFO) << "Unknown codec " << iter->name;
+ return false;
}
}
- // make channel ready to receive packets
- if (ret) {
- if (engine()->video_engine()->base()->StartReceive(vie_channel_) != 0) {
- LOG_RTCERR1(StartReceive, vie_channel_);
- ret = false;
- }
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ if (!SetReceiveCodecs(it->second->channel_id()))
+ return false;
}
- return ret;
+ return true;
}
bool WebRtcVideoMediaChannel::SetSendCodecs(
const std::vector<VideoCodec>& codecs) {
- if (sending_) {
- LOG(LS_ERROR) << "channel is alredy sending";
- return false;
- }
-
- // match with local video codec list
+ // Match with local video codec list.
std::vector<webrtc::VideoCodec> send_codecs;
+ int red_type = -1, fec_type = -1;
+ VideoCodec checked_codec;
+ VideoCodec current; // defaults to 0x0
+ if (sending_) {
+ engine()->ConvertToCricketVideoCodec(*send_codec_, ¤t);
+ }
for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
iter != codecs.end(); ++iter) {
- if (engine()->FindCodec(*iter)) {
+ if (_stricmp(iter->name.c_str(), kRedPayloadName) == 0) {
+ red_type = iter->id;
+ } else if (_stricmp(iter->name.c_str(), kFecPayloadName) == 0) {
+ fec_type = iter->id;
+ } else if (engine()->CanSendCodec(*iter, current, &checked_codec)) {
webrtc::VideoCodec wcodec;
- if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec))
+ if (engine()->ConvertFromCricketVideoCodec(checked_codec, &wcodec)) {
send_codecs.push_back(wcodec);
+ }
+ } else {
+ LOG(LS_WARNING) << "Unknown codec " << iter->name;
}
}
- // if none matches, return with set
+ // Fail if we don't have a match.
if (send_codecs.empty()) {
- LOG(LS_ERROR) << "No matching codecs avilable";
+ LOG(LS_WARNING) << "No matching codecs avilable";
return false;
}
- // select the first matched codec
- const webrtc::VideoCodec& codec(send_codecs[0]);
- send_codec_.reset(new webrtc::VideoCodec(codec));
- if (engine()->video_engine()->codec()->SetSendCodec(
- vie_channel_, codec) != 0) {
- LOG_RTCERR2(SetSendCodec, vie_channel_, codec.plName);
+ // Configure video protection.
+ if (!SetNackFec(vie_channel_, red_type, fec_type)) {
return false;
}
+
+ // Select the first matched codec.
+ webrtc::VideoCodec& codec(send_codecs[0]);
+
+ // Set the default number of temporal layers for VP8.
+ if (webrtc::kVideoCodecVP8 == codec.codecType) {
+ codec.codecSpecific.VP8.numberOfTemporalLayers =
+ kDefaultNumberOfTemporalLayers;
+ }
+
+ if (!SetSendCodec(
+ codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_)) {
+ return false;
+ }
+
+ LOG(LS_INFO) << "Selected video codec " << send_codec_->plName << "/"
+ << send_codec_->width << "x" << send_codec_->height << "x"
+ << static_cast<int>(send_codec_->maxFramerate);
+ if (webrtc::kVideoCodecVP8 == codec.codecType) {
+ LOG(LS_INFO) << "VP8 number of layers: "
+ << static_cast<int>(
+ send_codec_->codecSpecific.VP8.numberOfTemporalLayers);
+ }
return true;
}
+bool WebRtcVideoMediaChannel::SetSendStreamFormat(uint32 ssrc,
+ const VideoFormat& format) {
+ // TODO: Handle multiple outgoing streams.
+ if (local_ssrc_ != ssrc) {
+ LOG(LS_ERROR) << "The specified ssrc " << ssrc
+ << " differs from the send ssrc " << local_ssrc_;
+ return false;
+ }
+
+ if (send_codec_.get() == NULL) {
+ LOG(LS_ERROR) << "The send codec has not been set yet.";
+ return false;
+ }
+
+ webrtc::VideoCodec codec = *send_codec_.get();
+ codec.width = format.width;
+ codec.height = format.height;
+ codec.maxFramerate = VideoFormat::IntervalToFps(format.interval);
+
+ bool ret = SetSendCodec(
+ codec, send_min_bitrate_, send_start_bitrate_, send_max_bitrate_);
+ if (ret) {
+ LOG(LS_INFO) << "Update send codec resolution to "
+ << codec.width << "x" << codec.height << "x"
+ << static_cast<int>(codec.maxFramerate);
+ }
+ return ret;
+}
+
bool WebRtcVideoMediaChannel::SetRender(bool render) {
- if (render != render_started_) {
- int ret;
- if (render) {
- ret = engine()->video_engine()->render()->StartRender(vie_channel_);
- } else {
- ret = engine()->video_engine()->render()->StopRender(vie_channel_);
- }
- if (ret != 0) {
- return false;
- }
- render_started_ = render;
- }
- return true;
-}
-
-bool WebRtcVideoMediaChannel::SetSend(bool send) {
- if (send == sending()) {
+ if (render == render_started_) {
return true; // no action required
}
bool ret = true;
- if (send) { // enable
- if (engine()->video_engine()->base()->StartSend(vie_channel_) != 0) {
- LOG_RTCERR1(StartSend, vie_channel_);
- ret = false;
- }
-
- // If the channel has not been connected to the capturer yet,
- // connect it now.
- if (!connected()) {
- if (engine()->video_engine()->capture()->ConnectCaptureDevice(
- engine()->capture_id(), vie_channel_) != 0) {
- LOG_RTCERR2(ConnectCaptureDevice, engine()->capture_id(), vie_channel_);
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ if (render) {
+ if (engine()->vie()->render()->StartRender(
+ it->second->channel_id()) != 0) {
+ LOG_RTCERR1(StartRender, it->second->channel_id());
ret = false;
- } else {
- set_connected(true);
}
- }
- } else { // disable
- if (engine()->video_engine()->base()->StopSend(vie_channel_) != 0) {
- LOG_RTCERR1(StopSend, vie_channel_);
- ret = false;
+ } else {
+ if (engine()->vie()->render()->StopRender(
+ it->second->channel_id()) != 0) {
+ LOG_RTCERR1(StopRender, it->second->channel_id());
+ ret = false;
+ }
}
}
if (ret) {
- sending_ = send;
+ render_started_ = render;
}
return ret;
}
-bool WebRtcVideoMediaChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
- return false;
-}
-
-bool WebRtcVideoMediaChannel::RemoveStream(uint32 ssrc) {
- return false;
-}
-
-bool WebRtcVideoMediaChannel::SetRenderer(
- uint32 ssrc, VideoRenderer* renderer) {
- ASSERT(vie_channel_ != -1);
- if (ssrc != 0)
+bool WebRtcVideoMediaChannel::SetSend(bool send) {
+ if (local_ssrc_ == 0 && send) {
+ LOG(LS_ERROR) << "No stream added";
return false;
- if (remote_renderer_.get()) {
- // If the renderer already set, stop and remove it first
- if (engine_->video_engine()->render()->StopRender(vie_channel_) != 0) {
- LOG_RTCERR1(StopRender, vie_channel_);
+ }
+ if (send == sending()) {
+ return true; // No action required.
+ }
+
+ if (send) {
+ // We've been asked to start sending.
+ // SetSendCodecs must have been called already.
+ if (!send_codec_.get()) {
+ return false;
}
- if (engine_->video_engine()->render()->RemoveRenderer(vie_channel_) != 0) {
- LOG_RTCERR1(RemoveRenderer, vie_channel_);
+ // Start send now.
+ if (!StartSend()) {
+ return false;
+ }
+ } else {
+ // We've been asked to stop sending.
+ if (!StopSend()) {
+ return false;
}
}
- remote_renderer_.reset(new WebRtcRenderAdapter(renderer));
+ sending_ = send;
+
+ return true;
+}
- if (engine_->video_engine()->render()->AddRenderer(vie_channel_,
- webrtc::kVideoI420, remote_renderer_.get()) != 0) {
- LOG_RTCERR3(AddRenderer, vie_channel_, webrtc::kVideoI420,
- remote_renderer_.get());
- remote_renderer_.reset();
+int WebRtcVideoMediaChannel::GetChannelNum(uint32 ssrc) {
+ ChannelMap::iterator it = mux_channels_.find(ssrc);
+ return (it != mux_channels_.end()) ? it->second->channel_id() : -1;
+}
+
+bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) {
+ // TODO: Implement send media from multiple streams.
+ if (local_ssrc_ != 0) {
+ LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one sending channel";
+ return false;
+ }
+ if (sp.ssrcs.size() != 1) {
+ LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one sending SSRC per"
+ << " stream";
return false;
}
- if (engine_->video_engine()->render()->StartRender(vie_channel_) != 0) {
- LOG_RTCERR1(StartRender, vie_channel_);
+ if (engine()->vie()->rtp()->SetRTCPCName(vie_channel_,
+ sp.cname.c_str()) != 0) {
+ LOG_RTCERR2(SetRTCPCName, vie_channel_, sp.cname.c_str());
return false;
}
+ local_ssrc_ = sp.first_ssrc();
+ // Set the SSRC on the receive channels and this send channel.
+ // Receive channels have to have the same SSRC in order to send receiver
+ // reports with this SSRC.
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ WebRtcVideoChannelInfo* info = it->second;
+ int channel_id = info->channel_id();
+ if (engine()->vie()->rtp()->SetLocalSSRC(channel_id,
+ sp.first_ssrc()) != 0) {
+ LOG_RTCERR1(SetLocalSSRC, it->first);
+ return false;
+ }
+ }
+
+ if (sending_) {
+ return StartSend();
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::RemoveSendStream(uint32 ssrc) {
+ // TODO: Implement send media from multiple streams.
+ if (ssrc != local_ssrc_) {
+ return false;
+ }
+ if (sending_) {
+ StopSend();
+ }
+ local_ssrc_ = 0;
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) {
+ // TODO Remove this once BWE works properly across different send
+ // and receive channels.
+ // Reuse default channel for recv stream in 1:1 call.
+ if ((channel_options_ & OPT_CONFERENCE) == 0 && first_receive_ssrc_ == 0) {
+ LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+ << " reuse default channel #"
+ << vie_channel_;
+ first_receive_ssrc_ = sp.first_ssrc();
+ return true;
+ }
+
+ if (mux_channels_.find(sp.first_ssrc()) != mux_channels_.end()) {
+ LOG(LS_ERROR) << "Stream already exists";
+ return false;
+ }
+
+ // TODO: Implement recv media from multiple SSRCs per stream.
+ if (sp.ssrcs.size() != 1) {
+ LOG(LS_ERROR) << "WebRtcVideoMediaChannel supports one receiving SSRC per"
+ << " stream";
+ return false;
+ }
+
+ // Create a new channel for receiving video data.
+ // TODO: In order to support REMB we connect all receiving channels
+ // to our master send channel. This have to be done in a later cl when it have
+ // been properly implemented in webrtc.
+ int channel_id = -1;
+ if (engine_->vie()->base()->CreateChannel(channel_id) != 0) {
+ LOG_RTCERR1(CreateChannel, channel_id);
+ return false;
+ }
+
+ // Get the default renderer.
+ VideoRenderer* default_renderer = NULL;
+ if ((channel_options_ & OPT_CONFERENCE) != 0) {
+ if (mux_channels_.size() == 1 &&
+ mux_channels_.find(0) != mux_channels_.end()) {
+ GetRenderer(0, &default_renderer);
+ }
+ }
+
+ if (!ConfigureChannel(channel_id) ||
+ !ConfigureReceiving(channel_id, sp.first_ssrc())) {
+ engine_->vie()->base()->DeleteChannel(channel_id);
+ return false;
+ }
+
+ // The first recv stream reuses the default renderer (if a default renderer
+ // has been set).
+ if (default_renderer) {
+ SetRenderer(sp.first_ssrc(), default_renderer);
+ }
+
+ LOG(LS_INFO) << "New video stream " << sp.first_ssrc()
+ << " registered to VideoEngine channel #"
+ << channel_id;
+
return true;
}
+bool WebRtcVideoMediaChannel::RemoveRecvStream(uint32 ssrc) {
+ ChannelMap::iterator it = mux_channels_.find(ssrc);
+
+ if (it == mux_channels_.end()) {
+ // TODO: Remove this once BWE works properly across different send
+ // and receive channels.
+ // The default channel is reused for recv stream in 1:1 call.
+ if (first_receive_ssrc_ == ssrc) {
+ first_receive_ssrc_ = 0;
+ return true;
+ }
+ return false;
+ }
+ WebRtcVideoChannelInfo* info = it->second;
+ int channel_id = info->channel_id();
+ if (engine()->vie()->render()->RemoveRenderer(channel_id) != 0) {
+ LOG_RTCERR1(RemoveRenderer, channel_id);
+ }
+
+ if (engine()->vie()->network()->DeregisterSendTransport(channel_id) !=0) {
+ LOG_RTCERR1(DeRegisterSendTransport, channel_id);
+ }
+
+ if (engine()->vie()->codec()->DeregisterDecoderObserver(
+ channel_id) != 0) {
+ LOG_RTCERR1(DeregisterDecoderObserver, channel_id);
+ }
+
+ LOG(LS_INFO) << "Removing video stream " << ssrc
+ << " with VideoEngine channel #"
+ << channel_id;
+ if (engine()->vie()->base()->DeleteChannel(channel_id) == -1) {
+ LOG_RTCERR1(DeleteChannel, channel_id);
+ // Leak the WebRtcVideoChannelInfo owned by |it| but remove the channel from
+ // mux_channels_.
+ mux_channels_.erase(it);
+ return false;
+ }
+ // Delete the WebRtcVideoChannelInfo pointed to by it->second.
+ delete info;
+ mux_channels_.erase(it);
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::StartSend() {
+ if (engine()->vie()->base()->StartSend(vie_channel_) != 0) {
+ LOG_RTCERR1(StartSend, vie_channel_);
+ return false;
+ }
+
+ // TODO Change this once REMB supporting multiple sending channels.
+ // Send remb (2nd param) and use remb for BWE (3rd param).
+ engine_->vie()->rtp()->SetRembStatus(vie_channel_, true, true);
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::StopSend() {
+ if (engine()->vie()->base()->StopSend(vie_channel_) != 0) {
+ LOG_RTCERR1(StopSend, vie_channel_);
+ return false;
+ }
+
+ // TODO Change this once REMB supporting multiple sending channels.
+ // Don't send remb (2nd param) but use remb for BWE (3rd param).
+ engine_->vie()->rtp()->SetRembStatus(vie_channel_, false, true);
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRenderer(uint32 ssrc,
+ VideoRenderer* renderer) {
+ if (mux_channels_.find(ssrc) == mux_channels_.end()) {
+ // TODO: Remove this once BWE works properly across different send
+ // and receive channels.
+ // The default channel is reused for recv stream in 1:1 call.
+ if (first_receive_ssrc_ == ssrc &&
+ mux_channels_.find(0) != mux_channels_.end()) {
+ LOG(LS_INFO) << "SetRenderer " << ssrc
+ << " reuse default channel #"
+ << vie_channel_;
+ mux_channels_[0]->SetRenderer(renderer);
+ return true;
+ }
+ return false;
+ }
+
+ mux_channels_[ssrc]->SetRenderer(renderer);
+ return true;
+}
+
bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
- VideoSenderInfo sinfo;
- memset(&sinfo, 0, sizeof(sinfo));
-
- unsigned int ssrc;
- if (engine_->video_engine()->rtp()->GetLocalSSRC(vie_channel_,
- ssrc) != 0) {
- LOG_RTCERR2(GetLocalSSRC, vie_channel_, ssrc);
- return false;
- }
- sinfo.ssrc = ssrc;
-
- unsigned int cumulative_lost, extended_max, jitter;
- int rtt_ms;
- uint16 fraction_lost;
-
- if (engine_->video_engine()->rtp()->GetReceivedRTCPStatistics(vie_channel_,
- fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
- LOG_RTCERR6(GetReceivedRTCPStatistics, vie_channel_,
- fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
- return false;
- }
-
- sinfo.fraction_lost = fraction_lost;
- sinfo.packets_lost = cumulative_lost;
- sinfo.rtt_ms = rtt_ms;
-
+ // Get basic statistics.
unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
- if (engine_->video_engine()->rtp()->GetRTPStatistics(vie_channel_,
+ unsigned int ssrc;
+ if (engine_->vie()->rtp()->GetRTPStatistics(vie_channel_,
bytes_sent, packets_sent, bytes_recv, packets_recv) != 0) {
- LOG_RTCERR5(GetRTPStatistics, vie_channel_,
- bytes_sent, packets_sent, bytes_recv, packets_recv);
+ LOG_RTCERR1(GetRTPStatistics, vie_channel_);
return false;
}
- sinfo.packets_sent = packets_sent;
- sinfo.bytes_sent = bytes_sent;
- sinfo.packets_lost = -1;
- sinfo.packets_cached = -1;
- info->senders.push_back(sinfo);
+ // Get sender statistics and build VideoSenderInfo.
+ if (engine_->vie()->rtp()->GetLocalSSRC(vie_channel_, ssrc) == 0) {
+ VideoSenderInfo sinfo;
+ sinfo.ssrc = ssrc;
+ sinfo.codec_name = send_codec_.get() ? send_codec_->plName : "";
+ sinfo.bytes_sent = bytes_sent;
+ sinfo.packets_sent = packets_sent;
+ sinfo.packets_cached = -1;
+ sinfo.packets_lost = -1;
+ sinfo.fraction_lost = -1;
+ sinfo.firs_rcvd = -1;
+ sinfo.nacks_rcvd = -1;
+ sinfo.rtt_ms = -1;
+ sinfo.frame_width = local_stream_info_->width();
+ sinfo.frame_height = local_stream_info_->height();
+ sinfo.framerate_input = local_stream_info_->framerate();
+ sinfo.framerate_sent = encoder_observer_->framerate();
+ sinfo.nominal_bitrate = encoder_observer_->bitrate();
+ sinfo.preferred_bitrate = send_max_bitrate_;
- // build receiver info.
- // reusing the above local variables
- VideoReceiverInfo rinfo;
- memset(&rinfo, 0, sizeof(rinfo));
- if (engine_->video_engine()->rtp()->GetSentRTCPStatistics(vie_channel_,
- fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
- LOG_RTCERR6(GetSentRTCPStatistics, vie_channel_,
- fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
- return false;
+ // Get received RTCP statistics for the sender, if available.
+ // It's not a fatal error if we can't, since RTCP may not have arrived yet.
+ uint16 r_fraction_lost;
+ unsigned int r_cumulative_lost;
+ unsigned int r_extended_max;
+ unsigned int r_jitter;
+ int r_rtt_ms;
+ if (engine_->vie()->rtp()->GetSentRTCPStatistics(vie_channel_,
+ r_fraction_lost, r_cumulative_lost, r_extended_max,
+ r_jitter, r_rtt_ms) == 0) {
+ // Convert Q8 to float.
+ sinfo.packets_lost = r_cumulative_lost;
+ sinfo.fraction_lost = static_cast<float>(r_fraction_lost) / (1 << 8);
+ sinfo.rtt_ms = r_rtt_ms;
+ }
+ info->senders.push_back(sinfo);
+ } else {
+ LOG_RTCERR1(GetLocalSSRC, vie_channel_);
}
- rinfo.bytes_rcvd = bytes_recv;
- rinfo.packets_rcvd = packets_recv;
- rinfo.fraction_lost = fraction_lost;
- rinfo.packets_lost = cumulative_lost;
- if (engine_->video_engine()->rtp()->GetRemoteSSRC(vie_channel_,
- ssrc) != 0) {
- return false;
+ // Get the SSRC and stats for each receiver, based on our own calculations.
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ // Don't report receive statistics from the default channel if we have
+ // specified receive channels.
+ if (it->first == 0 && mux_channels_.size() > 1)
+ continue;
+ WebRtcVideoChannelInfo* channel = it->second;
+
+ // Get receiver statistics and build VideoReceiverInfo, if we have data.
+ if (engine_->vie()->rtp()->GetRemoteSSRC(channel->channel_id(), ssrc) != 0)
+ continue;
+
+ if (engine_->vie()->rtp()->GetRTPStatistics(
+ channel->channel_id(), bytes_sent, packets_sent, bytes_recv,
+ packets_recv) != 0) {
+ LOG_RTCERR1(GetRTPStatistics, channel->channel_id());
+ return false;
+ }
+ VideoReceiverInfo rinfo;
+ rinfo.ssrc = ssrc;
+ rinfo.bytes_rcvd = bytes_recv;
+ rinfo.packets_rcvd = packets_recv;
+ rinfo.packets_lost = -1;
+ rinfo.packets_concealed = -1;
+ rinfo.fraction_lost = -1; // from SentRTCP
+ rinfo.firs_sent = channel->decoder_observer()->firs_requested();
+ rinfo.nacks_sent = -1;
+ rinfo.frame_width = channel->render_adapter()->width();
+ rinfo.frame_height = channel->render_adapter()->height();
+ rinfo.framerate_rcvd = channel->decoder_observer()->framerate();
+ int fps = channel->render_adapter()->framerate();
+ rinfo.framerate_decoded = fps;
+ rinfo.framerate_output = fps;
+
+ // Get sent RTCP statistics.
+ uint16 s_fraction_lost;
+ unsigned int s_cumulative_lost;
+ unsigned int s_extended_max;
+ unsigned int s_jitter;
+ int s_rtt_ms;
+ if (engine_->vie()->rtp()->GetReceivedRTCPStatistics(channel->channel_id(),
+ s_fraction_lost, s_cumulative_lost, s_extended_max,
+ s_jitter, s_rtt_ms) == 0) {
+ // Convert Q8 to float.
+ rinfo.packets_lost = s_cumulative_lost;
+ rinfo.fraction_lost = static_cast<float>(s_fraction_lost) / (1 << 8);
+ }
+ info->receivers.push_back(rinfo);
}
- rinfo.ssrc = ssrc;
- // Get codec for wxh
- info->receivers.push_back(rinfo);
+ // Build BandwidthEstimationInfo.
+ // TODO: Fill in more BWE stats once we have them.
+ unsigned int total_bitrate_sent;
+ unsigned int video_bitrate_sent;
+ unsigned int fec_bitrate_sent;
+ unsigned int nack_bitrate_sent;
+ if (engine_->vie()->rtp()->GetBandwidthUsage(vie_channel_,
+ total_bitrate_sent, video_bitrate_sent,
+ fec_bitrate_sent, nack_bitrate_sent) == 0) {
+ BandwidthEstimationInfo bwe;
+ bwe.actual_enc_bitrate = video_bitrate_sent;
+ bwe.transmit_bitrate = total_bitrate_sent;
+ bwe.retransmit_bitrate = nack_bitrate_sent;
+ info->bw_estimations.push_back(bwe);
+ } else {
+ LOG_RTCERR1(GetBandwidthUsage, vie_channel_);
+ }
+
return true;
}
bool WebRtcVideoMediaChannel::SendIntraFrame() {
bool ret = true;
- if (engine()->video_engine()->codec()->SendKeyFrame(vie_channel_) != 0) {
+ if (engine()->vie()->codec()->SendKeyFrame(vie_channel_) != 0) {
LOG_RTCERR1(SendKeyFrame, vie_channel_);
ret = false;
}
@@ -921,59 +1632,86 @@
}
void WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
- engine()->video_engine()->network()->ReceivedRTPPacket(vie_channel_,
- packet->data(),
- packet->length());
+ // Pick which channel to send this packet to. If this packet doesn't match
+ // any multiplexed streams, just send it to the default channel. Otherwise,
+ // send it to the specific decoder instance for that stream.
+ uint32 ssrc = 0;
+ if (!GetRtpSsrc(packet->data(), packet->length(), &ssrc))
+ return;
+ int which_channel = GetChannelNum(ssrc);
+ if (which_channel == -1) {
+ which_channel = video_channel();
+ }
+
+ engine()->vie()->network()->ReceivedRTPPacket(which_channel,
+ packet->data(),
+ packet->length());
}
void WebRtcVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
- engine_->video_engine()->network()->ReceivedRTCPPacket(vie_channel_,
- packet->data(),
- packet->length());
-}
+// Sending channels need all RTCP packets with feedback information.
+// Even sender reports can contain attached report blocks.
+// Receiving channels need sender reports in order to create
+// correct receiver reports.
-void WebRtcVideoMediaChannel::SetSendSsrc(uint32 id) {
- if (!sending_) {
- if (engine()->video_engine()->rtp()->SetLocalSSRC(vie_channel_,
- id) != 0) {
- LOG_RTCERR1(SetLocalSSRC, vie_channel_);
+ uint32 ssrc = 0;
+ if (!GetRtcpSsrc(packet->data(), packet->length(), &ssrc)) {
+ LOG(LS_WARNING) << "Failed to parse SSRC from received RTCP packet";
+ return;
+ }
+ int type = 0;
+ if (!GetRtcpType(packet->data(), packet->length(), & type)) {
+ LOG(LS_WARNING) << "Failed to parse type from received RTCP packet";
+ return;
+ }
+
+ // If it is a sender report, find the channel that is listening.
+ if (type == kRtcpTypeSR) {
+ int which_channel = GetChannelNum(ssrc);
+ if (which_channel != -1 && which_channel != vie_channel_) {
+ engine_->vie()->network()->ReceivedRTCPPacket(which_channel,
+ packet->data(),
+ packet->length());
}
- } else {
- LOG(LS_ERROR) << "Channel already in send state";
}
-}
-
-bool WebRtcVideoMediaChannel::SetRtcpCName(const std::string& cname) {
- if (engine()->video_engine()->rtp()->SetRTCPCName(vie_channel_,
- cname.c_str()) != 0) {
- LOG_RTCERR2(SetRTCPCName, vie_channel_, cname.c_str());
- return false;
- }
- return true;
+ // The sending channel receives all RTCP packets.
+ engine_->vie()->network()->ReceivedRTCPPacket(vie_channel_,
+ packet->data(),
+ packet->length());
}
bool WebRtcVideoMediaChannel::Mute(bool on) {
- // stop send??
- return false;
+ muted_ = on;
+ return true;
}
bool WebRtcVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
LOG(LS_INFO) << "RtcVideoMediaChanne::SetSendBandwidth";
if (!send_codec_.get()) {
- LOG(LS_INFO) << "The send codec has not been set up yet.";
+ LOG(LS_INFO) << "The send codec has not been set up yet";
return true;
}
- if (!autobw) {
- send_codec_->startBitrate = bps;
- send_codec_->minBitrate = bps;
+ int min_bitrate;
+ int start_bitrate;
+ int max_bitrate;
+ if (autobw) {
+ // Use the default values for min bitrate.
+ min_bitrate = kMinVideoBitrate;
+ // Use the default value or the bps for the max
+ max_bitrate = (bps <= 0) ? send_max_bitrate_ : (bps / 1000);
+ // Maximum start bitrate can be kStartVideoBitrate.
+ start_bitrate = talk_base::_min(kStartVideoBitrate, max_bitrate);
+ } else {
+ // Use the default start or the bps as the target bitrate.
+ int target_bitrate = (bps <= 0) ? kStartVideoBitrate : (bps / 1000);
+ min_bitrate = target_bitrate;
+ start_bitrate = target_bitrate;
+ max_bitrate = target_bitrate;
}
- send_codec_->maxBitrate = bps;
- if (engine()->video_engine()->codec()->SetSendCodec(vie_channel_,
- *send_codec_.get()) != 0) {
- LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
+ if (!SetSendCodec(*send_codec_, min_bitrate, start_bitrate, max_bitrate)) {
return false;
}
@@ -981,6 +1719,33 @@
}
bool WebRtcVideoMediaChannel::SetOptions(int options) {
+ // Always accept options that are unchanged.
+ if (channel_options_ == options) {
+ return true;
+ }
+
+ // Reject new options if we're already sending.
+ if (sending()) {
+ return false;
+ }
+
+ // Save the options, to be interpreted where appropriate.
+ channel_options_ = options;
+
+ // Adjust send codec bitrate if needed.
+ int expected_bitrate = (0 != (channel_options_ & OPT_CONFERENCE)) ?
+ kConferenceModeMaxVideoBitrate : kMaxVideoBitrate;
+ if (NULL != send_codec_.get() && send_max_bitrate_ != expected_bitrate) {
+ // On success, SetSendCodec() will reset send_max_bitrate_ to
+ // expected_bitrate.
+ if (!SetSendCodec(*send_codec_,
+ send_min_bitrate_,
+ send_start_bitrate_,
+ expected_bitrate)) {
+ return false;
+ }
+ }
+
return true;
}
@@ -997,18 +1762,309 @@
}
}
-void WebRtcVideoMediaChannel::EnableRtcp() {
- engine()->video_engine()->rtp()->SetRTCPStatus(
- vie_channel_, webrtc::kRtcpCompound_RFC4585);
+bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc,
+ VideoRenderer** renderer) {
+ ChannelMap::const_iterator it = mux_channels_.find(ssrc);
+ if (it == mux_channels_.end()) {
+ if (first_receive_ssrc_ == ssrc &&
+ mux_channels_.find(0) != mux_channels_.end()) {
+ LOG(LS_INFO) << " GetRenderer " << ssrc
+ << " reuse default renderer #"
+ << vie_channel_;
+ *renderer = mux_channels_[0]->render_adapter()->renderer();
+ return true;
+ }
+ return false;
+ }
+
+ *renderer = it->second->render_adapter()->renderer();
+ return true;
}
-void WebRtcVideoMediaChannel::EnablePLI() {
- engine_->video_engine()->rtp()->SetKeyFrameRequestMethod(
- vie_channel_, webrtc::kViEKeyFrameRequestPliRtcp);
+// TODO: Add unittests to test this function.
+bool WebRtcVideoMediaChannel::SendFrame(uint32 ssrc, const VideoFrame* frame) {
+ if (ssrc != 0 || !sending() || !external_capture_) {
+ return false;
+ }
+
+ // Update local stream statistics.
+ local_stream_info_->UpdateFrame(frame->GetWidth(), frame->GetHeight());
+
+ // Checks if we need to reset vie send codec.
+ if (!MaybeResetVieSendCodec(frame->GetWidth(), frame->GetHeight(), NULL)) {
+ LOG(LS_ERROR) << "MaybeResetVieSendCodec failed with "
+ << frame->GetWidth() << "x" << frame->GetHeight();
+ return false;
+ }
+
+ // Blacken the frame if video is muted.
+ const VideoFrame* frame_out = frame;
+ talk_base::scoped_ptr<VideoFrame> black_frame;
+ if (muted_) {
+ black_frame.reset(frame->Copy());
+ black_frame->SetToBlack();
+ frame_out = black_frame.get();
+ }
+
+ webrtc::ViEVideoFrameI420 frame_i420;
+ // TODO: Update the webrtc::ViEVideoFrameI420
+ // to use const unsigned char*
+ frame_i420.y_plane = const_cast<unsigned char*>(frame_out->GetYPlane());
+ frame_i420.u_plane = const_cast<unsigned char*>(frame_out->GetUPlane());
+ frame_i420.v_plane = const_cast<unsigned char*>(frame_out->GetVPlane());
+ frame_i420.y_pitch = frame_out->GetYPitch();
+ frame_i420.u_pitch = frame_out->GetUPitch();
+ frame_i420.v_pitch = frame_out->GetVPitch();
+ frame_i420.width = frame_out->GetWidth();
+ frame_i420.height = frame_out->GetHeight();
+
+ // Convert from nanoseconds to milliseconds.
+ WebRtc_Word64 clocks = frame_out->GetTimeStamp() /
+ talk_base::kNumNanosecsPerMillisec;
+
+ return (external_capture_->IncomingFrameI420(frame_i420, clocks) == 0);
}
-void WebRtcVideoMediaChannel::EnableTMMBR() {
- engine_->video_engine()->rtp()->SetTMMBRStatus(vie_channel_, true);
+bool WebRtcVideoMediaChannel::ConfigureChannel(int channel_id) {
+ // Register external transport.
+ if (engine_->vie()->network()->RegisterSendTransport(
+ channel_id, *this) != 0) {
+ LOG_RTCERR1(RegisterSendTransport, channel_id);
+ return false;
+ }
+
+ // Set MTU.
+ if (engine_->vie()->network()->SetMTU(channel_id, kVideoMtu) != 0) {
+ LOG_RTCERR2(SetMTU, channel_id, kVideoMtu);
+ return false;
+ }
+ // Turn on RTCP and loss feedback reporting.
+ if (engine()->vie()->rtp()->SetRTCPStatus(
+ channel_id, webrtc::kRtcpCompound_RFC4585) != 0) {
+ LOG_RTCERR2(SetRTCPStatus, channel_id, webrtc::kRtcpCompound_RFC4585);
+ return false;
+ }
+ // Enable pli as key frame request method.
+ if (engine_->vie()->rtp()->SetKeyFrameRequestMethod(
+ channel_id, webrtc::kViEKeyFrameRequestPliRtcp) != 0) {
+ LOG_RTCERR2(SetKeyFrameRequestMethod,
+ channel_id, webrtc::kViEKeyFrameRequestPliRtcp);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id,
+ uint32 remote_ssrc) {
+ // Connect the voice channel, if there is one.
+ // TODO: The A/V is synched by the receiving channel. So we need to
+ // know the SSRC of the remote audio channel in order to fetch the correct
+ // webrtc VoiceEngine channel. For now- only sync the default channel used
+ // in 1-1 calls.
+ if (remote_ssrc == 0 && voice_channel_) {
+ WebRtcVoiceMediaChannel* voice_channel =
+ static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
+ if (engine_->vie()->base()->ConnectAudioChannel(
+ vie_channel_, voice_channel->voe_channel()) != 0) {
+ LOG_RTCERR2(ConnectAudioChannel, channel_id,
+ voice_channel->voe_channel());
+ LOG(LS_WARNING) << "A/V not synchronized";
+ // Not a fatal error.
+ }
+ }
+
+ talk_base::scoped_ptr<WebRtcVideoChannelInfo> channel_info(
+ new WebRtcVideoChannelInfo(channel_id));
+
+ // Install a render adapter.
+ if (engine_->vie()->render()->AddRenderer(channel_id,
+ webrtc::kVideoI420, channel_info->render_adapter()) != 0) {
+ LOG_RTCERR3(AddRenderer, channel_id, webrtc::kVideoI420,
+ channel_info->render_adapter());
+ return false;
+ }
+
+ // TODO Change this once REMB supporting multiple sending channels.
+ // Turn off remb sending (2nd param) and turn on remb reporting (3rd param)
+ // here.
+ // For sending channel, remb sending will be turned on after StartSending.
+ engine_->vie()->rtp()->SetRembStatus(channel_id, false, true);
+
+ if (remote_ssrc != 0) {
+ // Use the same SSRC as our default channel
+ // (so the RTCP reports are correct).
+ unsigned int send_ssrc = 0;
+ webrtc::ViERTP_RTCP* rtp = engine()->vie()->rtp();
+ if (rtp->GetLocalSSRC(vie_channel_, send_ssrc) == -1) {
+ LOG_RTCERR2(GetSendSSRC, channel_id, send_ssrc);
+ return false;
+ }
+ if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) {
+ LOG_RTCERR2(SetSendSSRC, channel_id, send_ssrc);
+ return false;
+ }
+ } // Else this is the the default channel and we don't change the SSRC.
+
+ // Disable color enhancement since it is a bit too aggressive.
+ if (engine()->vie()->image()->EnableColorEnhancement(channel_id,
+ false) != 0) {
+ LOG_RTCERR1(EnableColorEnhancement, channel_id);
+ return false;
+ }
+
+ if (!SetReceiveCodecs(channel_id)) {
+ return false;
+ }
+
+ if (render_started_) {
+ if (engine_->vie()->render()->StartRender(channel_id) != 0) {
+ LOG_RTCERR1(StartRender, channel_id);
+ return false;
+ }
+ }
+
+ // Register decoder observer for incoming framerate and bitrate.
+ if (engine()->vie()->codec()->RegisterDecoderObserver(
+ channel_id, *channel_info->decoder_observer()) != 0) {
+ LOG_RTCERR1(RegisterDecoderObserver, channel_info->decoder_observer());
+ return false;
+ }
+
+ mux_channels_[remote_ssrc] = channel_info.release();
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetNackFec(int channel_id,
+ int red_payload_type,
+ int fec_payload_type) {
+ // Enable hybrid NACK/FEC if negotiated and not in a conference, use only NACK
+ // otherwise.
+ bool enable = (red_payload_type != -1 && fec_payload_type != -1 &&
+ !(channel_options_ & OPT_CONFERENCE));
+ if (enable) {
+ if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
+ channel_id, enable, red_payload_type, fec_payload_type) != 0) {
+ LOG_RTCERR4(SetHybridNACKFECStatus,
+ channel_id, enable, red_payload_type, fec_payload_type);
+ return false;
+ }
+ LOG(LS_INFO) << "Hybrid NACK/FEC enabled for channel " << channel_id;
+ } else {
+ if (engine_->vie()->rtp()->SetNACKStatus(channel_id, true) != 0) {
+ LOG_RTCERR1(SetNACKStatus, channel_id);
+ return false;
+ }
+ LOG(LS_INFO) << "NACK enabled for channel " << channel_id;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSendCodec(const webrtc::VideoCodec& codec,
+ int min_bitrate,
+ int start_bitrate,
+ int max_bitrate) {
+ // Make a copy of the codec
+ webrtc::VideoCodec target_codec = codec;
+ target_codec.startBitrate = start_bitrate;
+ target_codec.minBitrate = min_bitrate;
+ target_codec.maxBitrate = max_bitrate;
+
+ if (engine()->vie()->codec()->SetSendCodec(vie_channel_, target_codec) != 0) {
+ LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
+ return false;
+ }
+
+ // Reset the send_codec_ only if SetSendCodec is success.
+ send_codec_.reset(new webrtc::VideoCodec(target_codec));
+ send_min_bitrate_ = min_bitrate;
+ send_start_bitrate_ = start_bitrate;
+ send_max_bitrate_ = max_bitrate;
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetReceiveCodecs(int channel_id) {
+ int red_type = -1;
+ int fec_type = -1;
+ for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
+ it != receive_codecs_.end(); ++it) {
+ if (it->codecType == webrtc::kVideoCodecRED) {
+ red_type = it->plType;
+ } else if (it->codecType == webrtc::kVideoCodecULPFEC) {
+ fec_type = it->plType;
+ }
+ if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) {
+ LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName);
+ return false;
+ }
+ }
+
+ // Enable video protection. For a sending channel, this will be taken care of
+ // in SetSendCodecs.
+ if (channel_id != vie_channel_) {
+ if (!SetNackFec(channel_id, red_type, fec_type)) {
+ return false;
+ }
+ }
+
+ // Start receiving packets if at least one receive codec has been set.
+ if (!receive_codecs_.empty()) {
+ if (engine()->vie()->base()->StartReceive(channel_id) != 0) {
+ LOG_RTCERR1(StartReceive, channel_id);
+ return false;
+ }
+ }
+ return true;
+}
+
+// If the new frame size is different from the send codec size we set on vie,
+// we need to reset the send codec on vie.
+// The new send codec size should not exceed send_codec_ which is controlled
+// only by the 'jec' logic.
+bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec(int new_width,
+ int new_height,
+ bool* reset) {
+ if (reset) {
+ *reset = false;
+ }
+
+ if (NULL == send_codec_.get()) {
+ return false;
+ }
+
+ // Vie send codec size should not exceed send_codec_.
+ int target_width = new_width;
+ int target_height = new_height;
+ if (new_width > send_codec_->width || new_height > send_codec_->height) {
+ target_width = send_codec_->width;
+ target_height = send_codec_->height;
+ }
+
+ // Get current vie codec.
+ webrtc::VideoCodec vie_codec;
+ if (engine()->vie()->codec()->GetSendCodec(vie_channel_, vie_codec) != 0) {
+ LOG_RTCERR1(GetSendCodec, vie_channel_);
+ return false;
+ }
+
+ // Only reset send codec when there is a size change.
+ if (target_width != vie_codec.width || target_height != vie_codec.height) {
+ // Set the new codec on vie.
+ vie_codec.width = target_width;
+ vie_codec.height = target_height;
+ if (engine()->vie()->codec()->SetSendCodec(vie_channel_, vie_codec) != 0) {
+ LOG_RTCERR1(SetSendCodec, vie_channel_);
+ return false;
+ }
+ if (reset) {
+ *reset = true;
+ }
+ LOG(LS_INFO) << "Reset vie send codec to: "
+ << vie_codec.width << "x" << vie_codec.height;
+ }
+
+ return true;
}
int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
@@ -1021,8 +2077,8 @@
}
int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
- const void* data,
- int len) {
+ const void* data,
+ int len) {
if (!network_interface_) {
return -1;
}
@@ -1033,4 +2089,3 @@
} // namespace cricket
#endif // HAVE_WEBRTC_VIDEO
-
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index ffb6e7d..8eb56da 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -28,6 +28,7 @@
#ifndef TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
#define TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
+#include <map>
#include <vector>
#include "talk/base/scoped_ptr.h"
@@ -36,7 +37,7 @@
#include "talk/session/phone/channel.h"
#include "talk/session/phone/webrtccommon.h"
#ifdef WEBRTC_RELATIVE_PATH
-#include "video_engine/main/interface/vie_base.h"
+#include "video_engine/include/vie_base.h"
#else
#include "third_party/webrtc/files/include/vie_base.h"
#endif // WEBRTC_RELATIVE_PATH
@@ -44,72 +45,104 @@
namespace webrtc {
class VideoCaptureModule;
class VideoRender;
+class ViEExternalCapture;
}
namespace cricket {
+struct CapturedFrame;
+class WebRtcVideoChannelInfo;
struct Device;
+class WebRtcLocalStreamInfo;
class VideoCapturer;
+class VideoFrame;
+class VideoProcessor;
class VideoRenderer;
+class ViETraceWrapper;
class ViEWrapper;
class VoiceMediaChannel;
class WebRtcRenderAdapter;
class WebRtcVideoMediaChannel;
class WebRtcVoiceEngine;
+class WebRtcDecoderObserver;
+class WebRtcEncoderObserver;
-class WebRtcVideoEngine : public webrtc::ViEBaseObserver,
+class WebRtcVideoEngine : public sigslot::has_slots<>,
+ public webrtc::ViEBaseObserver,
public webrtc::TraceCallback {
public:
// Creates the WebRtcVideoEngine with internal VideoCaptureModule.
WebRtcVideoEngine();
// For testing purposes. Allows the WebRtcVoiceEngine and
// ViEWrapper to be mocks.
- WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, ViEWrapper* vie_wrapper);
+ // TODO: Remove the 2-arg ctor once fake tracing is implemented.
+ WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ ViEWrapper* vie_wrapper);
+ WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ ViEWrapper* vie_wrapper,
+ ViETraceWrapper* tracing);
~WebRtcVideoEngine();
+ // Basic video engine implementation.
bool Init();
void Terminate();
- WebRtcVideoMediaChannel* CreateChannel(
- VoiceMediaChannel* voice_channel);
- bool FindCodec(const VideoCodec& in);
- bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
-
- void RegisterChannel(WebRtcVideoMediaChannel* channel);
- void UnregisterChannel(WebRtcVideoMediaChannel* channel);
-
- ViEWrapper* video_engine() { return vie_wrapper_.get(); }
- int GetLastVideoEngineError();
int GetCapabilities();
bool SetOptions(int options);
- bool SetCaptureDevice(const Device* device);
- bool SetCaptureModule(webrtc::VideoCaptureModule* vcm);
- int capture_id() const { return capture_id_; }
- bool SetLocalRenderer(VideoRenderer* renderer);
- CaptureResult SetCapture(bool capture);
+ bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
+
+ WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel);
+
const std::vector<VideoCodec>& codecs() const;
void SetLogging(int min_sev, const char* filter);
- int GetLastEngineError();
+ // Capture-related stuff. Will be removed with capture refactor.
+ bool SetCaptureDevice(const Device* device);
+ bool SetCaptureModule(webrtc::VideoCaptureModule* vcm);
+ // If capturer is NULL, unregisters the capturer and stops capturing.
+ // Otherwise sets the capturer and starts capturing.
+ bool SetVideoCapturer(VideoCapturer* capturer, uint32 /*ssrc*/);
+ bool SetLocalRenderer(VideoRenderer* renderer);
+ CaptureResult SetCapture(bool capture);
+ sigslot::repeater2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+ virtual VideoCapturer* CreateVideoCapturer(const Device& device);
+ CaptureResult UpdateCapturingState();
+ bool IsCapturing() const;
+ void OnFrameCaptured(VideoCapturer* capturer, const CapturedFrame* frame);
// Set the VoiceEngine for A/V sync. This can only be called before Init.
bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine);
-
// Enable the render module with timing control.
bool EnableTimedRender();
- VideoEncoderConfig& default_encoder_config() {
- return default_encoder_config_;
+ bool RegisterProcessor(VideoProcessor* video_processor);
+ bool UnregisterProcessor(VideoProcessor* video_processor);
+
+ // Functions called by WebRtcVideoMediaChannel.
+ ViEWrapper* vie() { return vie_wrapper_.get(); }
+ const VideoFormat& default_codec_format() const {
+ return default_codec_format_;
}
-
+ int GetLastEngineError();
+ bool FindCodec(const VideoCodec& in);
+ bool CanSendCodec(const VideoCodec& in, const VideoCodec& current,
+ VideoCodec* out);
+ void RegisterChannel(WebRtcVideoMediaChannel* channel);
+ void UnregisterChannel(WebRtcVideoMediaChannel* channel);
void ConvertToCricketVideoCodec(const webrtc::VideoCodec& in_codec,
- VideoCodec& out_codec);
-
+ VideoCodec* out_codec);
bool ConvertFromCricketVideoCodec(const VideoCodec& in_codec,
- webrtc::VideoCodec& out_codec);
+ webrtc::VideoCodec* out_codec);
+ // Check whether the supplied trace should be ignored.
+ bool ShouldIgnoreTrace(const std::string& trace);
+ int GetNumOfChannels();
- sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+ protected:
+ // When a video processor registers with the engine.
+ // SignalMediaFrame will be invoked for every video frame.
+ sigslot::signal2<uint32, VideoFrame*> SignalMediaFrame;
private:
+ typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
struct VideoCodecPref {
const char* name;
int payload_type;
@@ -117,35 +150,48 @@
};
static const VideoCodecPref kVideoCodecPrefs[];
- static const VideoFormat kVideoFormats[];
- static const VideoFormat kDefaultVideoFormat;
+ static const VideoFormatPod kVideoFormats[];
+ static const VideoFormatPod kDefaultVideoFormat;
- void Construct();
+ void Construct(ViEWrapper* vie_wrapper,
+ ViETraceWrapper* tracing,
+ WebRtcVoiceEngine* voice_engine);
bool SetDefaultCodec(const VideoCodec& codec);
bool RebuildCodecList(const VideoCodec& max_codec);
-
- void ApplyLogging();
+ void ApplyLogging(const std::string& log_filter);
bool InitVideoEngine();
- void PerformanceAlarm(const unsigned int cpu_load);
- bool ReleaseCaptureDevice();
+ bool SetCapturer(VideoCapturer* capturer, bool own_capturer);
+
+ // webrtc::ViEBaseObserver implementation.
+ virtual void PerformanceAlarm(const unsigned int cpu_load);
+ // webrtc::TraceCallback implementation.
virtual void Print(const webrtc::TraceLevel level, const char* trace_string,
const int length);
- typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
+ void ClearCapturer();
talk_base::scoped_ptr<ViEWrapper> vie_wrapper_;
- webrtc::VideoCaptureModule* capture_module_;
- bool external_capture_;
- int capture_id_;
- talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+ bool vie_wrapper_base_initialized_;
+ talk_base::scoped_ptr<ViETraceWrapper> tracing_;
WebRtcVoiceEngine* voice_engine_;
- std::vector<VideoCodec> video_codecs_;
- VideoChannels channels_;
int log_level_;
- VideoEncoderConfig default_encoder_config_;
- bool capture_started_;
- talk_base::scoped_ptr<WebRtcRenderAdapter> local_renderer_;
+ talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+ std::vector<VideoCodec> video_codecs_;
+ VideoFormat default_codec_format_;
bool initialized_;
+ talk_base::CriticalSection channels_crit_;
+ VideoChannels channels_;
+
+ bool owns_capturer_;
+ VideoCapturer* video_capturer_;
+ bool capture_started_;
+ int local_renderer_w_;
+ int local_renderer_h_;
+ VideoRenderer* local_renderer_;
+
+ // Critical section to protect the media processor register/unregister
+ // while processing a frame
+ talk_base::CriticalSection signal_media_critical_;
};
class WebRtcVideoMediaChannel : public VideoMediaChannel,
@@ -154,23 +200,37 @@
WebRtcVideoMediaChannel(
WebRtcVideoEngine* engine, VoiceMediaChannel* voice_channel);
~WebRtcVideoMediaChannel();
-
bool Init();
+
+ WebRtcVideoEngine* engine() { return engine_; }
+ VoiceMediaChannel* voice_channel() { return voice_channel_; }
+ int video_channel() const { return vie_channel_; }
+ bool sending() const { return sending_; }
+
+ // VideoMediaChannel implementation
virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs);
virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs);
+ virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format);
virtual bool SetRender(bool render);
virtual bool SetSend(bool send);
- virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
- virtual bool RemoveStream(uint32 ssrc);
+
+ virtual bool AddSendStream(const StreamParams& sp);
+ virtual bool RemoveSendStream(uint32 ssrc);
+ virtual bool AddRecvStream(const StreamParams& sp);
+ virtual bool RemoveRecvStream(uint32 ssrc);
virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
virtual bool GetStats(VideoMediaInfo* info);
+ virtual bool AddScreencast(uint32 ssrc, const ScreencastId& id) {
+ return false;
+ }
+ virtual bool RemoveScreencast(uint32 ssrc) {
+ return false;
+ }
virtual bool SendIntraFrame();
virtual bool RequestIntraFrame();
virtual void OnPacketReceived(talk_base::Buffer* packet);
virtual void OnRtcpReceived(talk_base::Buffer* packet);
- virtual void SetSendSsrc(uint32 id);
- virtual bool SetRtcpCName(const std::string& cname);
virtual bool Mute(bool on);
virtual bool SetRecvRtpHeaderExtensions(
const std::vector<RtpHeaderExtension>& extensions) {
@@ -184,12 +244,17 @@
virtual bool SetOptions(int options);
virtual void SetInterface(NetworkInterface* iface);
- WebRtcVideoEngine* engine() const { return engine_; }
- VoiceMediaChannel* voice_channel() const { return voice_channel_; }
- int video_channel() const { return vie_channel_; }
- bool sending() const { return sending_; }
- void set_connected(bool connected) { connected_ = connected; }
- bool connected() const { return connected_; }
+ // Public functions for use by tests and other specialized code.
+ uint32 send_ssrc() const { return 0; }
+ bool GetRenderer(uint32 ssrc, VideoRenderer** renderer);
+ bool SendFrame(uint32 ssrc, const VideoFrame* frame);
+
+ // Thunk functions for use with HybridVideoEngine
+ void OnLocalFrame(VideoCapturer* capturer, const VideoFrame* frame) {
+ SendFrame(0, frame);
+ }
+ void OnLocalFrameFormat(VideoCapturer* capturer, const VideoFormat* format) {
+ }
protected:
int GetLastEngineError() { return engine()->GetLastEngineError(); }
@@ -197,20 +262,55 @@
virtual int SendRTCPPacket(int channel, const void* data, int len);
private:
- void EnableRtcp();
- void EnablePLI();
- void EnableTMMBR();
+ typedef std::map<uint32, WebRtcVideoChannelInfo*> ChannelMap;
+
+ // Creates and initializes a WebRtc video channel.
+ bool ConfigureChannel(int channel_id);
+ bool ConfigureReceiving(int channel_id, uint32 remote_ssrc);
+ bool SetNackFec(int channel_id, int red_payload_type, int fec_payload_type);
+ bool SetSendCodec(const webrtc::VideoCodec& codec,
+ int min_bitrate,
+ int start_bitrate,
+ int max_bitrate);
+ // Prepares the channel with channel id |channel_id| to receive all codecs in
+ // |receive_codecs_| and start receive packets.
+ bool SetReceiveCodecs(int channel_id);
+ // Returns the channel number that receives the stream with SSRC |ssrc|.
+ int GetChannelNum(uint32 ssrc);
+ // Given captured video frame size, checks if we need to reset vie send codec.
+ // |reset| is set to whether resetting has happened on vie or not.
+ // Returns false on error.
+ bool MaybeResetVieSendCodec(int new_width, int new_height, bool* reset);
+ // Call Webrtc function to start sending media on |vie_channel_|.
+ // Does not affect |sending_|.
+ bool StartSend();
+ // Call Webrtc function to stop sending media on |vie_channel_|.
+ // Does not affect |sending_|.
+ bool StopSend();
WebRtcVideoEngine* engine_;
VoiceMediaChannel* voice_channel_;
int vie_channel_;
+ int vie_capture_;
+ webrtc::ViEExternalCapture* external_capture_;
bool sending_;
- // connected to the capture device or not.
- bool connected_;
bool render_started_;
+ bool muted_; // Flag to tell if we need to mute video.
+ // Our local SSRC. Currently only one send stream is supported.
+ uint32 local_ssrc_;
+ uint32 first_receive_ssrc_;
+ int send_min_bitrate_;
+ int send_start_bitrate_;
+ int send_max_bitrate_;
talk_base::scoped_ptr<webrtc::VideoCodec> send_codec_;
- talk_base::scoped_ptr<WebRtcRenderAdapter> remote_renderer_;
+ std::vector<webrtc::VideoCodec> receive_codecs_;
+ talk_base::scoped_ptr<WebRtcEncoderObserver> encoder_observer_;
+ talk_base::scoped_ptr<WebRtcLocalStreamInfo> local_stream_info_;
+ int channel_options_;
+
+ ChannelMap mux_channels_; // Contains all receive channels.
};
+
} // namespace cricket
#endif // TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
diff --git a/talk/session/phone/webrtcvideoengine_unittest.cc b/talk/session/phone/webrtcvideoengine_unittest.cc
index ee42729..b0bf813 100644
--- a/talk/session/phone/webrtcvideoengine_unittest.cc
+++ b/talk/session/phone/webrtcvideoengine_unittest.cc
@@ -1,93 +1,77 @@
-// Copyright 2008 Google Inc. All Rights Reserved.
-//
-// Author: Ronghua Wu (ronghuawu@google.com)
-// Zhurun Zhang (zhurunz@google.com)
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
-#include "talk/base/byteorder.h"
#include "talk/base/gunit.h"
-#include "talk/session/phone/channel.h"
-#include "talk/session/phone/fakemediaengine.h"
-#include "talk/session/phone/fakertp.h"
-#include "talk/session/phone/fakesession.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/fakemediaprocessor.h"
+#include "talk/session/phone/fakewebrtcvideocapturemodule.h"
#include "talk/session/phone/fakewebrtcvideoengine.h"
#include "talk/session/phone/fakewebrtcvoiceengine.h"
+#include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/videoengine_unittest.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
#include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/session/phone/webrtcvideoframe.h"
#include "talk/session/phone/webrtcvoiceengine.h"
// Tests for the WebRtcVideoEngine/VideoChannel code.
-static const cricket::VideoCodec kVP8Codec(104, "VP8", 320, 200, 30, 0);
+static const cricket::VideoCodec kVP8Codec(100, "VP8", 640, 400, 30, 0);
+static const cricket::VideoCodec kRedCodec(101, "red", 0, 0, 0, 0);
+static const cricket::VideoCodec kUlpFecCodec(102, "ulpfec", 0, 0, 0, 0);
static const cricket::VideoCodec* const kVideoCodecs[] = {
&kVP8Codec,
+ &kRedCodec,
+ &kUlpFecCodec
};
+static const unsigned int kMinBandwidthKbps = 100;
+static const unsigned int kStartBandwidthKbps = 300;
+static const unsigned int kMaxBandwidthKbps = 2000;
+static const unsigned int kConferenceModeMaxBandwidthKbps = 500;
+
class FakeViEWrapper : public cricket::ViEWrapper {
public:
explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine)
- : cricket::ViEWrapper(engine, engine, engine, engine,
- engine, engine, engine) {
+ : cricket::ViEWrapper(engine, // base
+ engine, // codec
+ engine, // capture
+ engine, // network
+ engine, // render
+ engine, // rtp
+ engine) { // image
}
};
-class FakeNetworkInterface : public cricket::MediaChannel::NetworkInterface {
+// Test fixture to test WebRtcVideoEngine with a fake webrtc::VideoEngine.
+// Useful for testing failure paths.
+class WebRtcVideoEngineTestFake : public testing::Test {
public:
- FakeNetworkInterface()
- : recv_buffer_size_(0),
- send_buffer_size_(0) {
- }
- virtual bool SendPacket(talk_base::Buffer* packet) {
- return true;
- }
- virtual bool SendRtcp(talk_base::Buffer* packet) {
- return true;
- }
- virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
- int option) {
- if (type == ST_RTP) {
- if (opt == talk_base::Socket::OPT_RCVBUF)
- recv_buffer_size_ = option;
- else if (opt == talk_base::Socket::OPT_SNDBUF)
- send_buffer_size_ = option;
- }
- return 0;
- }
- virtual ~FakeNetworkInterface() {}
- int recv_buffer_size_;
- int send_buffer_size_;
-};
-
-class WebRtcVideoEngineTest : public testing::Test {
- public:
- class ChannelErrorListener : public sigslot::has_slots<> {
- public:
- explicit ChannelErrorListener(cricket::WebRtcVideoMediaChannel* channel)
- : ssrc_(0), error_(cricket::WebRtcVideoMediaChannel::ERROR_NONE) {
- ASSERT(channel != NULL);
- channel->SignalMediaError.connect(
- this, &ChannelErrorListener::OnVideoChannelError);
- }
- void OnVideoChannelError(uint32 ssrc,
- cricket::WebRtcVideoMediaChannel::Error error) {
- ssrc_ = ssrc;
- error_ = error;
- }
- void Reset() {
- ssrc_ = 0;
- error_ = cricket::WebRtcVideoMediaChannel::ERROR_NONE;
- }
- uint32 ssrc() const {
- return ssrc_;
- }
- cricket::WebRtcVideoMediaChannel::Error error() const {
- return error_;
- }
-
- private:
- uint32 ssrc_;
- cricket::WebRtcVideoMediaChannel::Error error_;
- };
-
- WebRtcVideoEngineTest()
+ WebRtcVideoEngineTestFake()
: vie_(kVideoCodecs, ARRAY_SIZE(kVideoCodecs)),
engine_(NULL, // cricket::WebRtcVoiceEngine
new FakeViEWrapper(&vie_)),
@@ -102,9 +86,19 @@
}
return result;
}
- void DeliverPacket(const void* data, int len) {
- talk_base::Buffer packet(data, len);
- channel_->OnPacketReceived(&packet);
+ bool SendI420Frame(int width, int height) {
+ if (NULL == channel_) {
+ return false;
+ }
+ cricket::WebRtcVideoFrame frame;
+ size_t size = width * height * 3 / 2; // I420
+ talk_base::scoped_ptr<uint8> pixel(new uint8[size]);
+ if (!frame.Init(cricket::FOURCC_I420,
+ width, height, width, height,
+ pixel.get(), size, 1, 1, 0, 0, 0)) {
+ return false;
+ }
+ return channel_->SendFrame(0, &frame);
}
virtual void TearDown() {
delete channel_;
@@ -118,40 +112,580 @@
cricket::WebRtcVoiceMediaChannel* voice_channel_;
};
+// Test fixtures to test WebRtcVideoEngine with a real webrtc::VideoEngine.
+class WebRtcVideoEngineTest
+ : public VideoEngineTest<cricket::WebRtcVideoEngine> {
+ protected:
+ typedef VideoEngineTest<cricket::WebRtcVideoEngine> Base;
+};
+class WebRtcVideoMediaChannelTest
+ : public VideoMediaChannelTest<
+ cricket::WebRtcVideoEngine, cricket::WebRtcVideoMediaChannel> {
+ protected:
+ typedef VideoMediaChannelTest<cricket::WebRtcVideoEngine,
+ cricket::WebRtcVideoMediaChannel> Base;
+ virtual cricket::VideoCodec DefaultCodec() { return kVP8Codec; }
+ virtual void SetUp() {
+ Base::SetUp();
+ // Need to start the capturer to allow us to pump in frames.
+ engine_.SetCapture(true);
+ }
+ virtual void TearDown() {
+ engine_.SetCapture(false);
+ Base::TearDown();
+ }
+};
+
+/////////////////////////
+// Tests with fake ViE //
+/////////////////////////
+
// Tests that our stub library "works".
-TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
+TEST_F(WebRtcVideoEngineTestFake, StartupShutdown) {
EXPECT_FALSE(vie_.IsInited());
EXPECT_TRUE(engine_.Init());
EXPECT_TRUE(vie_.IsInited());
engine_.Terminate();
- // TODO: what to expect after Terminate
- // EXPECT_FALSE(vie_.IsInited());
}
// Tests that we can create and destroy a channel.
-TEST_F(WebRtcVideoEngineTest, CreateChannel) {
+TEST_F(WebRtcVideoEngineTestFake, CreateChannel) {
EXPECT_TRUE(engine_.Init());
channel_ = engine_.CreateChannel(voice_channel_);
EXPECT_TRUE(channel_ != NULL);
+ EXPECT_EQ(1, engine_.GetNumOfChannels());
+ delete channel_;
+ channel_ = NULL;
+ EXPECT_EQ(0, engine_.GetNumOfChannels());
}
// Tests that we properly handle failures in CreateChannel.
-TEST_F(WebRtcVideoEngineTest, CreateChannelFail) {
+TEST_F(WebRtcVideoEngineTestFake, CreateChannelFail) {
vie_.set_fail_create_channel(true);
EXPECT_TRUE(engine_.Init());
channel_ = engine_.CreateChannel(voice_channel_);
EXPECT_TRUE(channel_ == NULL);
}
-// Tests that we can find codecs by name or id
+// Tests that we properly handle failures in AllocateExternalCaptureDevice.
+TEST_F(WebRtcVideoEngineTestFake, AllocateExternalCaptureDeviceFail) {
+ vie_.set_fail_alloc_capturer(true);
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel(voice_channel_);
+ EXPECT_TRUE(channel_ == NULL);
+}
+
+// Test that we apply our default codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_EQ(kVP8Codec.width, gcodec.width);
+ EXPECT_EQ(kVP8Codec.height, gcodec.height);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+ EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+ EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+ // TODO: Check RTCP, PLI, TMMBR.
+}
+
+// Test that we constrain send codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, ConstrainSendCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Set max settings of 640x400x30.
+ EXPECT_TRUE(engine_.SetDefaultEncoderConfig(
+ cricket::VideoEncoderConfig(kVP8Codec)));
+
+ // Send codec format bigger than max setting.
+ cricket::VideoCodec codec(kVP8Codec);
+ codec.width = 1280;
+ codec.height = 800;
+ codec.framerate = 60;
+ std::vector<cricket::VideoCodec> codec_list;
+ codec_list.push_back(codec);
+
+ // Set send codec and verify codec has been constrained.
+ EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_EQ(kVP8Codec.width, gcodec.width);
+ EXPECT_EQ(kVP8Codec.height, gcodec.height);
+ EXPECT_EQ(kVP8Codec.framerate, gcodec.maxFramerate);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+}
+
+// Test that SetSendCodecs rejects bad format.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsRejectBadFormat) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Set w = 0.
+ cricket::VideoCodec codec(kVP8Codec);
+ codec.width = 0;
+ std::vector<cricket::VideoCodec> codec_list;
+ codec_list.push_back(codec);
+
+ // Verify SetSendCodecs failed and send codec is not changed on engine.
+ EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.plType);
+
+ // Set h = 0.
+ codec_list[0].width = 640;
+ codec_list[0].height = 0;
+
+ // Verify SetSendCodecs failed and send codec is not changed on engine.
+ EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.plType);
+}
+
+// Test that SetSendCodecs rejects bad codec.
+TEST_F(WebRtcVideoEngineTestFake, SetSendCodecsRejectBadCodec) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Set bad codec name.
+ cricket::VideoCodec codec(kVP8Codec);
+ codec.name = "bad";
+ std::vector<cricket::VideoCodec> codec_list;
+ codec_list.push_back(codec);
+
+ // Verify SetSendCodecs failed and send codec is not changed on engine.
+ EXPECT_FALSE(channel_->SetSendCodecs(codec_list));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.plType);
+}
+
+// Test that vie send codec is reset on new video frame size.
+TEST_F(WebRtcVideoEngineTestFake, ResetVieSendCodecOnNewFrameSize) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Set send codec.
+ std::vector<cricket::VideoCodec> codec_list;
+ codec_list.push_back(kVP8Codec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codec_list));
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(123)));
+ EXPECT_TRUE(channel_->SetSend(true));
+
+ // Capture a smaller frame and verify vie send codec has been reset to
+ // the new size.
+ SendI420Frame(kVP8Codec.width / 2, kVP8Codec.height / 2);
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.width / 2, gcodec.width);
+ EXPECT_EQ(kVP8Codec.height / 2, gcodec.height);
+
+ // Capture a frame bigger than send_codec_ and verify vie send codec has been
+ // reset (and clipped) to send_codec_.
+ SendI420Frame(kVP8Codec.width * 2, kVP8Codec.height * 2);
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.width, gcodec.width);
+ EXPECT_EQ(kVP8Codec.height, gcodec.height);
+}
+
+// Test that we set our inbound codecs properly.
+TEST_F(WebRtcVideoEngineTestFake, SetRecvCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+ webrtc::VideoCodec wcodec;
+ EXPECT_TRUE(engine_.ConvertFromCricketVideoCodec(kVP8Codec, &wcodec));
+ EXPECT_TRUE(vie_.ReceiveCodecRegistered(channel_num, wcodec));
+}
+
+// Test that channel connects and disconnects external capturer correctly.
+TEST_F(WebRtcVideoEngineTestFake, HasExternalCapturer) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ EXPECT_EQ(1, vie_.GetNumCapturers());
+ int capture_id = vie_.GetCaptureId(channel_num);
+ EXPECT_EQ(channel_num, vie_.GetCaptureChannelId(capture_id));
+
+ // Delete the channel should disconnect the capturer.
+ delete channel_;
+ channel_ = NULL;
+ EXPECT_EQ(0, vie_.GetNumCapturers());
+}
+
+// Test that channel adds and removes renderer correctly.
+TEST_F(WebRtcVideoEngineTestFake, HasRenderer) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ EXPECT_TRUE(vie_.GetHasRenderer(channel_num));
+ EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+}
+
+// Test that rtcp is enabled on the channel.
+TEST_F(WebRtcVideoEngineTestFake, RtcpEnabled) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_EQ(webrtc::kRtcpCompound_RFC4585, vie_.GetRtcpStatus(channel_num));
+}
+
+// Test that key frame request method is set on the channel.
+TEST_F(WebRtcVideoEngineTestFake, KeyFrameRequestEnabled) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_EQ(webrtc::kViEKeyFrameRequestPliRtcp,
+ vie_.GetKeyFrameRequestMethod(channel_num));
+}
+
+// Test that remb is enabled on the default channel.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabled) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test that remb is enabled on a receive channel but it uses the default
+// channel for sending remb packets.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabledOnReceiveChannels) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ int new_channel_num = vie_.GetLastChannel();
+ EXPECT_NE(channel_num, new_channel_num);
+
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+ EXPECT_TRUE(vie_.GetRembStatus(new_channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(new_channel_num));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, AddRecvStream1On1) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_EQ(channel_num, vie_.GetLastChannel());
+}
+
+// Test that AddRecvStream doesn't change remb for 1:1 call.
+TEST_F(WebRtcVideoEngineTestFake, NoRembChangeAfterAddRecvStream) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test remb sending is on after StartSending and off after StopSending.
+TEST_F(WebRtcVideoEngineTestFake, RembOnOff) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Verify remb sending is off before StartSending.
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+
+ // Verify remb sending is on after StartSending.
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
+
+ // Verify remb sending is off after StopSending.
+ EXPECT_TRUE(channel_->SetSend(false));
+ EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+ EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test that nack is enabled on the channel if we don't offer red/fec.
+TEST_F(WebRtcVideoEngineTestFake, NackEnabled) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+ codecs.resize(1); // toss out red and ulpfec
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that we enable hybrid NACK FEC mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFec) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+ EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that we enable hybrid NACK FEC mode when calling SetSendCodecs and
+// SetReceiveCodecs in reversed order.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecReversedOrder) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+ EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+ EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInterop) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+ std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+ // Only add VP8 as send codec.
+ send_codecs.resize(1);
+ EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+ EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+ EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+ EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec. Call order reversed compared to
+// VideoProtectionInterop.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInteropReversed) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+ std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+ // Only add VP8 as send codec.
+ send_codecs.resize(1);
+ EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+ EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+ EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+ EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that NACK, not hybrid mode, is enabled in conference mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecConference) {
+ EXPECT_TRUE(SetupEngine());
+ // Setup the send channel.
+ int send_channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_FALSE(vie_.GetHybridNackFecStatus(send_channel_num));
+ EXPECT_TRUE(vie_.GetNackStatus(send_channel_num));
+ // Add a receive stream.
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ int receive_channel_num = vie_.GetLastChannel();
+ EXPECT_FALSE(vie_.GetHybridNackFecStatus(receive_channel_num));
+ EXPECT_TRUE(vie_.GetNackStatus(receive_channel_num));
+}
+
+// Test that we can create a channel and start/stop rendering out on it.
+TEST_F(WebRtcVideoEngineTestFake, SetRender) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Verify we can start/stop/start/stop rendering.
+ EXPECT_TRUE(channel_->SetRender(true));
+ EXPECT_TRUE(vie_.GetRenderStarted(channel_num));
+ EXPECT_TRUE(channel_->SetRender(false));
+ EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+ EXPECT_TRUE(channel_->SetRender(true));
+ EXPECT_TRUE(vie_.GetRenderStarted(channel_num));
+ EXPECT_TRUE(channel_->SetRender(false));
+ EXPECT_FALSE(vie_.GetRenderStarted(channel_num));
+}
+
+// Test that we can create a channel and start/stop sending out on it.
+TEST_F(WebRtcVideoEngineTestFake, SetSend) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ // Set send codecs on the channel.
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(123)));
+
+ // Verify we can start/stop/start/stop sending.
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(vie_.GetSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(false));
+ EXPECT_FALSE(vie_.GetSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(true));
+ EXPECT_TRUE(vie_.GetSend(channel_num));
+ EXPECT_TRUE(channel_->SetSend(false));
+ EXPECT_FALSE(vie_.GetSend(channel_num));
+}
+
+// Test that we set bandwidth properly when using full auto bandwidth mode.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthAuto) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSendBandwidth(true, cricket::kAutoBandwidth));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+}
+
+// Test that we set bandwidth properly when using auto with upper bound.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthAutoCapped) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSendBandwidth(true, 768000));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(768U, gcodec.maxBitrate);
+}
+
+// Test that we set bandwidth properly when using a fixed bandwidth.
+TEST_F(WebRtcVideoEngineTestFake, SetBandwidthFixed) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+ EXPECT_TRUE(channel_->SetSendBandwidth(false, 768000));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(768U, gcodec.minBitrate);
+ EXPECT_EQ(768U, gcodec.startBitrate);
+ EXPECT_EQ(768U, gcodec.maxBitrate);
+}
+
+// Test SetSendSsrc.
+TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+
+ cricket::StreamParams stream;
+ stream.ssrcs.push_back(1234);
+ stream.cname = "cname";
+ channel_->AddSendStream(stream);
+
+ unsigned int ssrc = 0;
+ EXPECT_EQ(0, vie_.GetLocalSSRC(channel_num, ssrc));
+ EXPECT_EQ(1234U, ssrc);
+
+ char rtcp_cname[256];
+ EXPECT_EQ(0, vie_.GetRTCPCName(channel_num, rtcp_cname));
+ EXPECT_STREQ("cname", rtcp_cname);
+}
+
+// Test that the local SSRC is the same on sending and receiving channels if the
+// receive channel is created before the send channel.
+TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) {
+ EXPECT_TRUE(SetupEngine());
+
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ int receive_channel_num = vie_.GetLastChannel();
+ cricket::StreamParams stream = cricket::StreamParams::CreateLegacy(1234);
+ EXPECT_TRUE(channel_->AddSendStream(stream));
+ int send_channel_num = vie_.GetLastChannel();
+ unsigned int ssrc = 0;
+ EXPECT_EQ(0, vie_.GetLocalSSRC(send_channel_num, ssrc));
+ EXPECT_EQ(1234U, ssrc);
+ ssrc = 0;
+ EXPECT_EQ(0, vie_.GetLocalSSRC(receive_channel_num, ssrc));
+ EXPECT_EQ(1234U, ssrc);
+}
+
+// Test SetOptions with OPT_CONFERENCE flag.
+TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithConferenceMode) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+
+ // Verify default send codec and bitrate.
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+
+ // Set options with OPT_CONFERENCE flag.
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+ // Verify channel now has new max bitrate.
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(kConferenceModeMaxBandwidthKbps, gcodec.maxBitrate);
+
+ // Set options back to zero.
+ EXPECT_TRUE(channel_->SetOptions(0));
+
+ // Verify channel now has default max bitrate again.
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
+ EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
+ EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
+}
+
+/////////////////////////
+// Tests with real ViE //
+/////////////////////////
+
+// Tests that we can find codecs by name or id.
TEST_F(WebRtcVideoEngineTest, FindCodec) {
// We should not need to init engine in order to get codecs.
const std::vector<cricket::VideoCodec>& c = engine_.codecs();
- EXPECT_EQ(1U, c.size());
+ EXPECT_EQ(3U, c.size());
cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0);
EXPECT_TRUE(engine_.FindCodec(vp8));
+ cricket::VideoCodec vp8_ci(104, "vp8", 320, 200, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(vp8));
+
cricket::VideoCodec vp8_diff_fr_diff_pref(104, "VP8", 320, 200, 50, 50);
EXPECT_TRUE(engine_.FindCodec(vp8_diff_fr_diff_pref));
@@ -167,59 +701,251 @@
// Test that FindCodec can handle the case when width/height is 0.
cricket::VideoCodec vp8_zero_res(104, "VP8", 0, 0, 30, 0);
EXPECT_TRUE(engine_.FindCodec(vp8_zero_res));
+
+ cricket::VideoCodec red(101, "RED", 0, 0, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(red));
+
+ cricket::VideoCodec red_ci(101, "red", 0, 0, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(red));
+
+ cricket::VideoCodec fec(102, "ULPFEC", 0, 0, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(fec));
+
+ cricket::VideoCodec fec_ci(102, "ulpfec", 0, 0, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(fec));
}
-// Test that we set our inbound codecs properly
-TEST_F(WebRtcVideoEngineTest, SetRecvCodecs) {
- EXPECT_TRUE(SetupEngine());
+TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
+ EXPECT_TRUE(engine_.Init());
+ engine_.Terminate();
+}
+
+TEST_PRE_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainNewCodec)
+TEST_POST_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainNewCodec)
+
+TEST_PRE_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainRunningCodec)
+TEST_POST_VIDEOENGINE_INIT(WebRtcVideoEngineTest, ConstrainRunningCodec)
+
+// TODO: Figure out why ViE is munging the COM refcount.
+#ifdef WIN32
+TEST_F(WebRtcVideoEngineTest, DISABLED_CheckCoInitialize) {
+ Base::CheckCoInitialize();
+}
+#endif
+
+TEST_F(WebRtcVideoEngineTest, CreateChannel) {
+ EXPECT_TRUE(engine_.Init());
+ cricket::VideoMediaChannel* channel = engine_.CreateChannel(NULL);
+ EXPECT_TRUE(channel != NULL);
+ delete channel;
+}
+
+TEST_F(WebRtcVideoEngineTest, SetCaptureDevice) {
+ cricket::Device device;
+ EXPECT_TRUE(engine_.Init());
+
+ EXPECT_TRUE(engine_.SetCaptureDevice(&device));
+ EXPECT_FALSE(engine_.IsCapturing());
+ // FakeVideoCapturer returns CR_SUCCESS.
+ EXPECT_EQ(cricket::CR_SUCCESS, engine_.SetCapture(true));
+ EXPECT_TRUE(engine_.IsCapturing());
+
+ EXPECT_TRUE(engine_.SetCaptureDevice(NULL));
+ EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, SetCaptureModule) {
+ // Use 123 to verify there's no assumption to the module id
+ FakeWebRtcVideoCaptureModule* vcm =
+ new FakeWebRtcVideoCaptureModule(NULL, 123);
+ EXPECT_TRUE(engine_.Init());
+ // The ownership of the vcm is transferred to the engine.
+ // Technically we should call vcm->AddRef since we are using the vcm below,
+ // however the FakeWebRtcVideoCaptureModule didn't implemented the refcount.
+ // So for testing, this should be fine.
+ // The SetCaptureModule call always starts the capture.
+ EXPECT_TRUE(engine_.SetCaptureModule(vcm));
+ EXPECT_TRUE(engine_.IsCapturing());
+ EXPECT_EQ(engine_.default_codec_format().width, vcm->cap().width);
+ EXPECT_EQ(engine_.default_codec_format().height, vcm->cap().height);
+ EXPECT_EQ(cricket::VideoFormat::IntervalToFps(
+ engine_.default_codec_format().interval),
+ vcm->cap().maxFPS);
+ EXPECT_EQ(webrtc::kVideoI420, vcm->cap().rawType);
+ EXPECT_EQ(webrtc::kVideoCodecUnknown, vcm->cap().codecType);
+
+ EXPECT_TRUE(engine_.SetCaptureModule(NULL));
+ EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, SetVideoCapturer) {
+ // Use 123 to verify there's no assumption to the module id
+ FakeWebRtcVideoCaptureModule* vcm =
+ new FakeWebRtcVideoCaptureModule(NULL, 123);
+ talk_base::scoped_ptr<cricket::WebRtcVideoCapturer> capturer(
+ new cricket::WebRtcVideoCapturer);
+ EXPECT_TRUE(capturer->Init(vcm));
+ EXPECT_TRUE(engine_.Init());
+ const uint32 ssrc_dummy = 0;
+ EXPECT_TRUE(engine_.SetVideoCapturer(capturer.get(), ssrc_dummy));
+ EXPECT_FALSE(engine_.IsCapturing());
+ EXPECT_EQ(cricket::CR_PENDING, engine_.SetCapture(true));
+ EXPECT_TRUE(engine_.IsCapturing());
+
+ EXPECT_EQ(engine_.default_codec_format().width, vcm->cap().width);
+ EXPECT_EQ(engine_.default_codec_format().height, vcm->cap().height);
+ EXPECT_EQ(cricket::VideoFormat::IntervalToFps(
+ engine_.default_codec_format().interval),
+ vcm->cap().maxFPS);
+ EXPECT_EQ(webrtc::kVideoI420, vcm->cap().rawType);
+ EXPECT_EQ(webrtc::kVideoCodecUnknown, vcm->cap().codecType);
+
+ EXPECT_TRUE(engine_.SetVideoCapturer(NULL, ssrc_dummy));
+ EXPECT_FALSE(engine_.IsCapturing());
+}
+
+TEST_F(WebRtcVideoEngineTest, TestRegisterVideoProcessor) {
+ cricket::FakeMediaProcessor vp;
+ EXPECT_TRUE(engine_.Init());
+
+ EXPECT_TRUE(engine_.RegisterProcessor(&vp));
+ engine_.TriggerMediaFrame(0, NULL);
+ EXPECT_EQ(1, vp.video_frame_count());
+
+ EXPECT_TRUE(engine_.UnregisterProcessor(&vp));
+ engine_.TriggerMediaFrame(0, NULL);
+ EXPECT_EQ(1, vp.video_frame_count());
+
+ engine_.Terminate();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecs) {
std::vector<cricket::VideoCodec> codecs;
codecs.push_back(kVP8Codec);
EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
}
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsWrongPayloadType) {
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ codecs[0].id = 99;
+ EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsUnsupportedCodec) {
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ codecs.push_back(cricket::VideoCodec(101, "VP1", 640, 400, 30, 0));
+ EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
-// Test that we apply codecs properly.
-TEST_F(WebRtcVideoEngineTest, SetSendCodecs) {
- EXPECT_TRUE(SetupEngine());
- int channel_num = vie_.GetLastChannel();
+TEST_F(WebRtcVideoMediaChannelTest, SetSend) {
+ Base::SetSend();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendWithoutCodecs) {
+ Base::SetSendWithoutCodecs();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSetsTransportBufferSizes) {
+ Base::SetSendSetsTransportBufferSizes();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveVp8Vga) {
+ SendAndReceive(cricket::VideoCodec(100, "VP8", 640, 400, 30, 0));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveVp8Qvga) {
+ SendAndReceive(cricket::VideoCodec(100, "VP8", 320, 200, 30, 0));
+}
+TEST_F(WebRtcVideoMediaChannelTest, SendAndReceiveH264SvcQqvga) {
+ SendAndReceive(cricket::VideoCodec(100, "VP8", 160, 100, 30, 0));
+}
+// TODO: Figure out why this test doesn't work.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SendManyResizeOnce) {
+ SendManyResizeOnce();
+}
+
+// TODO: Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStats) {
+ Base::GetStats();
+}
+
+// TODO: Fix this test to tolerate missing stats.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleRecvStreams) {
+ Base::GetStatsMultipleRecvStreams();
+}
+// TODO: Restore this test once we support multiple send streams.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_GetStatsMultipleSendStreams) {
+ Base::GetStatsMultipleSendStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetSendBandwidth) {
+ Base::SetSendBandwidth();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSsrc) {
+ Base::SetSendSsrc();
+}
+TEST_F(WebRtcVideoMediaChannelTest, SetSendSsrcAfterSetCodecs) {
+ Base::SetSendSsrcAfterSetCodecs();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetRenderer) {
+ Base::SetRenderer();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreams) {
+ Base::AddRemoveRecvStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveRecvStreamsNoConference) {
+ Base::AddRemoveRecvStreamsNoConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, AddRemoveSendStreams) {
+ Base::AddRemoveSendStreams();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SimulateConference) {
+ Base::SimulateConference();
+}
+
+TEST_F(WebRtcVideoMediaChannelTest, SetOptionsFailsWhenSending) {
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+ // Verify SetOptions returns true on a different options.
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CPU_ADAPTATION));
+
+ // Set send codecs on the channel and start sending.
std::vector<cricket::VideoCodec> codecs;
codecs.push_back(kVP8Codec);
EXPECT_TRUE(channel_->SetSendCodecs(codecs));
- webrtc::VideoCodec gcodec;
- EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
- EXPECT_EQ(kVP8Codec.id, gcodec.plType);
- EXPECT_EQ(kVP8Codec.width, gcodec.width);
- EXPECT_EQ(kVP8Codec.height, gcodec.height);
- EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+ EXPECT_TRUE(channel_->SetSend(true));
+
+ // Verify SetOptions returns false if channel is already sending.
+ EXPECT_FALSE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+ // Verify SetOptions returns true with the old options.
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CPU_ADAPTATION));
}
-// Tests that the rtp buffer is set properly after the SetInterface call.
-TEST_F(WebRtcVideoEngineTest, SetRtpBufferSize) {
- EXPECT_TRUE(SetupEngine());
- const int kExpectedVideoRtpBufferSize = 65536;
- FakeNetworkInterface network_interface;
- channel_->SetInterface(&network_interface);
- EXPECT_EQ(kExpectedVideoRtpBufferSize, network_interface.recv_buffer_size_);
- EXPECT_EQ(kExpectedVideoRtpBufferSize, network_interface.send_buffer_size_);
+// TODO: Investigate why this test is flaky.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptResolution16x10) {
+ Base::AdaptResolution16x10();
}
-
-// TODO: add tests for below interfaces
-// bool SetOptions(int options);
-// bool SetCaptureDevice(const Device* device);
-// bool SetLocalRenderer(VideoRenderer* renderer);
-// CaptureResult SetCapture(bool capture);
-// virtual bool SetRender(bool render);
-// virtual bool SetSend(bool send);
-// virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
-// virtual bool RemoveStream(uint32 ssrc);
-// virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
-// virtual bool GetStats(VideoMediaInfo* info);
-// virtual bool SendIntraFrame();
-// virtual bool RequestIntraFrame();
-// virtual void OnPacketReceived(talk_base::Buffer* packet);
-// virtual void OnRtcpReceived(talk_base::Buffer* packet);
-// virtual void SetSendSsrc(uint32 id);
-// virtual bool SetRtcpCName(const std::string& cname);
-// virtual bool Mute(bool on);
-// virtual bool SetSendBandwidth(bool autobw, int bps);
-// virtual bool SetOptions(int options);
-
+// TODO: Investigate why this test is flaky.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptResolution4x3) {
+ Base::AdaptResolution4x3();
+}
+// TODO: Restore this test once we support sending 0 fps.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptDropAllFrames) {
+ Base::AdaptDropAllFrames();
+}
+// TODO: Understand why we get decode errors on this test.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_AdaptFramerate) {
+ Base::AdaptFramerate();
+}
+// TODO: Understand why format is set but the encoded frames do not
+// change.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_SetSendStreamFormat) {
+ Base::SetSendStreamFormat();
+}
+// TODO: Understand why we receive a not-quite-black frame.
+TEST_F(WebRtcVideoMediaChannelTest, DISABLED_Mute) {
+ Base::Mute();
+}
diff --git a/talk/session/phone/webrtcvideoframe.cc b/talk/session/phone/webrtcvideoframe.cc
index 8d2d6ca..5eb849d 100644
--- a/talk/session/phone/webrtcvideoframe.cc
+++ b/talk/session/phone/webrtcvideoframe.cc
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -27,17 +27,20 @@
#include "talk/session/phone/webrtcvideoframe.h"
+#include "libyuv/convert.h"
+#include "libyuv/planar_functions.h"
#include "talk/base/logging.h"
#include "talk/session/phone/videocapturer.h"
#include "talk/session/phone/videocommon.h"
-#ifdef WEBRTC_RELATIVE_PATH
-#include "common_video/vplib/main/interface/vplib.h"
-#else
-#include "third_party/webrtc/files/include/vplib.h"
-#endif
namespace cricket {
+static const int kWatermarkWidth = 8;
+static const int kWatermarkHeight = 8;
+static const int kWatermarkOffsetFromLeft = 8;
+static const int kWatermarkOffsetFromBottom = 8;
+static const unsigned char kWatermarkMaxYValue = 64;
+
WebRtcVideoFrame::WebRtcVideoFrame() {
}
@@ -49,36 +52,22 @@
size_t pixel_width, size_t pixel_height,
int64 elapsed_time, int64 time_stamp,
int rotation) {
- // WebRtcVideoFrame currently doesn't support color conversion or rotation.
- if (format != FOURCC_I420 || dw != w || dh != h || rotation != 0) {
- return false;
- }
-
- uint8* buffer = new uint8[sample_size];
- memcpy(buffer, sample, sample_size);
- Attach(buffer, sample_size, w, h, pixel_width, pixel_height,
- elapsed_time, time_stamp, rotation);
- return true;
+ return Reset(format, w, h, dw, dh, sample, sample_size,
+ pixel_width, pixel_height, elapsed_time, time_stamp, rotation);
}
bool WebRtcVideoFrame::Init(const CapturedFrame* frame, int dw, int dh) {
- return Init(frame->fourcc, frame->width, frame->height, dw, dh,
- static_cast<uint8*>(frame->data), frame->data_size,
- frame->pixel_width, frame->pixel_height,
- frame->elapsed_time, frame->time_stamp, frame->rotation);
+ return Reset(frame->fourcc, frame->width, frame->height, dw, dh,
+ static_cast<uint8*>(frame->data), frame->data_size,
+ frame->pixel_width, frame->pixel_height,
+ frame->elapsed_time, frame->time_stamp, frame->rotation);
}
bool WebRtcVideoFrame::InitToBlack(int w, int h,
size_t pixel_width, size_t pixel_height,
int64 elapsed_time, int64 time_stamp) {
- size_t buffer_size = VideoFrame::SizeOf(w, h);
- uint8* buffer = new uint8[buffer_size];
- Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
- elapsed_time, time_stamp, 0);
- memset(GetYPlane(), 16, w * h);
- memset(GetUPlane(), 128, w * h / 4);
- memset(GetVPlane(), 128, w * h / 4);
- return true;
+ InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
+ return SetToBlack();
}
void WebRtcVideoFrame::Attach(uint8* buffer, size_t buffer_size, int w, int h,
@@ -95,7 +84,7 @@
pixel_width_ = pixel_width;
pixel_height_ = pixel_height;
elapsed_time_ = elapsed_time;
- video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+ time_stamp_ = time_stamp;
rotation_ = rotation;
}
@@ -123,15 +112,18 @@
const uint8* WebRtcVideoFrame::GetUPlane() const {
WebRtc_UWord8* buffer = video_frame_.Buffer();
- if (buffer)
+ if (buffer) {
buffer += (video_frame_.Width() * video_frame_.Height());
+ }
return buffer;
}
const uint8* WebRtcVideoFrame::GetVPlane() const {
WebRtc_UWord8* buffer = video_frame_.Buffer();
- if (buffer)
- buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+ if (buffer) {
+ int uv_size = GetChromaSize();
+ buffer += video_frame_.Width() * video_frame_.Height() + uv_size;
+ }
return buffer;
}
@@ -142,15 +134,18 @@
uint8* WebRtcVideoFrame::GetUPlane() {
WebRtc_UWord8* buffer = video_frame_.Buffer();
- if (buffer)
+ if (buffer) {
buffer += (video_frame_.Width() * video_frame_.Height());
+ }
return buffer;
}
uint8* WebRtcVideoFrame::GetVPlane() {
WebRtc_UWord8* buffer = video_frame_.Buffer();
- if (buffer)
- buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+ if (buffer) {
+ int uv_size = GetChromaSize();
+ buffer += video_frame_.Width() * video_frame_.Height() + uv_size;
+ }
return buffer;
}
@@ -166,7 +161,7 @@
copy->Attach(new_buffer, new_buffer_size,
video_frame_.Width(), video_frame_.Height(),
pixel_width_, pixel_height_,
- elapsed_time_, video_frame_.TimeStamp(), rotation_);
+ elapsed_time_, time_stamp_, rotation_);
return copy;
}
@@ -188,91 +183,126 @@
return needed;
}
+// TODO: Refactor into base class and share with lmi
size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc,
uint8* buffer,
size_t size,
- size_t pitch_rgb) const {
+ int stride_rgb) const {
if (!video_frame_.Buffer()) {
return 0;
}
-
size_t width = video_frame_.Width();
size_t height = video_frame_.Height();
- // See http://www.virtualdub.org/blog/pivot/entry.php?id=190 for a good
- // explanation of pitch and why this is the amount of space we need.
- size_t needed = pitch_rgb * (height - 1) + 4 * width;
-
- if (needed > size) {
+ size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height;
+ if (size < needed) {
LOG(LS_WARNING) << "RGB buffer is not large enough";
- return 0;
+ return needed;
}
- webrtc::VideoType to_type = webrtc::kUnknown;
- switch (to_fourcc) {
- case FOURCC_ARGB:
- to_type = webrtc::kARGB;
- break;
- default:
- LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
- return 0;
- }
-
- if (to_type != webrtc::kUnknown) {
- webrtc::ConvertFromI420(to_type, video_frame_.Buffer(),
- width, height, buffer);
- }
-
- return needed;
-}
-
-void WebRtcVideoFrame::StretchToPlanes(
- uint8* y, uint8* u, uint8* v,
- int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v,
- size_t width, size_t height, bool interpolate, bool crop) const {
- // TODO: Implement StretchToPlanes
-}
-
-size_t WebRtcVideoFrame::StretchToBuffer(size_t w, size_t h,
- uint8* buffer, size_t size,
- bool interpolate,
- bool crop) const {
- if (!video_frame_.Buffer()) {
- return 0;
- }
-
- size_t needed = video_frame_.Length();
-
- if (needed <= size) {
- uint8* bufy = buffer;
- uint8* bufu = bufy + w * h;
- uint8* bufv = bufu + ((w + 1) >> 1) * ((h + 1) >> 1);
- StretchToPlanes(bufy, bufu, bufv, w, (w + 1) >> 1, (w + 1) >> 1, w, h,
- interpolate, crop);
+ if (libyuv::ConvertFromI420(GetYPlane(), GetYPitch(),
+ GetUPlane(), GetUPitch(),
+ GetVPlane(), GetVPitch(),
+ buffer, stride_rgb,
+ width, height,
+ to_fourcc)) {
+ LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
+ return 0; // 0 indicates error
}
return needed;
}
-void WebRtcVideoFrame::StretchToFrame(VideoFrame* target,
- bool interpolate, bool crop) const {
- if (!target) return;
+// Add a square watermark near the left-low corner. clamp Y.
+// Returns false on error.
+bool WebRtcVideoFrame::AddWatermark() {
+ size_t w = GetWidth();
+ size_t h = GetHeight();
- StretchToPlanes(target->GetYPlane(),
- target->GetUPlane(),
- target->GetVPlane(),
- target->GetYPitch(),
- target->GetUPitch(),
- target->GetVPitch(),
- target->GetWidth(),
- target->GetHeight(),
- interpolate, crop);
- target->SetElapsedTime(GetElapsedTime());
- target->SetTimeStamp(GetTimeStamp());
+ if (w < kWatermarkWidth + kWatermarkOffsetFromLeft ||
+ h < kWatermarkHeight + kWatermarkOffsetFromBottom) {
+ return false;
+ }
+
+ uint8* buffer = GetYPlane();
+ for (size_t x = kWatermarkOffsetFromLeft;
+ x < kWatermarkOffsetFromLeft + kWatermarkWidth; ++x) {
+ for (size_t y = h - kWatermarkOffsetFromBottom - kWatermarkHeight;
+ y < h - kWatermarkOffsetFromBottom; ++y) {
+ buffer[y * w + x] = talk_base::_min(buffer[y * w + x],
+ kWatermarkMaxYValue);
+ }
+ }
+ return true;
}
-VideoFrame* WebRtcVideoFrame::Stretch(size_t w, size_t h,
- bool interpolate, bool crop) const {
- // TODO: implement
- return NULL;
+bool WebRtcVideoFrame::Reset(uint32 format, int w, int h, int dw, int dh,
+ uint8* sample, size_t sample_size,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time, int64 time_stamp,
+ int rotation) {
+ // WebRtcVideoFrame currently doesn't support color conversion or rotation.
+ // TODO: Add horizontal cropping support.
+ if (format != FOURCC_I420 || dw != w || dh < 0 || dh > abs(h) ||
+ rotation != 0) {
+ return false;
+ }
+
+ // Discard the existing buffer.
+ uint8* old_buffer;
+ size_t old_buffer_size;
+ Detach(&old_buffer, &old_buffer_size);
+ delete[] old_buffer;
+
+ // Set up a new buffer.
+ size_t desired_size = SizeOf(dw, dh);
+ uint8* buffer = new uint8[desired_size];
+ Attach(buffer, desired_size, dw, dh, pixel_width, pixel_height,
+ elapsed_time, time_stamp, rotation);
+
+ if (dh == h) {
+ // Uncropped
+ memcpy(buffer, sample, desired_size);
+ } else {
+ // Cropped
+ // TODO: use I420Copy which supports horizontal crop and vertical
+ // flip.
+ int horiz_crop = ((w - dw) / 2) & ~1;
+ int vert_crop = ((abs(h) - dh) / 2) & ~1;
+ int y_crop_offset = w * vert_crop + horiz_crop;
+ int halfwidth = (w + 1) / 2;
+ int halfheight = (h + 1) / 2;
+ int uv_size = GetChromaSize();
+ int uv_crop_offset = (halfwidth * vert_crop + horiz_crop) / 2;
+ uint8* src_y = sample + y_crop_offset;
+ uint8* src_u = sample + w * h + uv_crop_offset;
+ uint8* src_v = sample + w * h + halfwidth * halfheight + uv_crop_offset;
+ memcpy(GetYPlane(), src_y, dw * dh);
+ memcpy(GetUPlane(), src_u, uv_size);
+ memcpy(GetVPlane(), src_v, uv_size);
+ }
+
+ return true;
+}
+
+VideoFrame* WebRtcVideoFrame::CreateEmptyFrame(int w, int h,
+ size_t pixel_width,
+ size_t pixel_height,
+ int64 elapsed_time,
+ int64 time_stamp) const {
+ WebRtcVideoFrame* frame = new WebRtcVideoFrame();
+ frame->InitToEmptyBuffer(w, h, pixel_width, pixel_height,
+ elapsed_time, time_stamp);
+ return frame;
+}
+
+void WebRtcVideoFrame::InitToEmptyBuffer(int w, int h,
+ size_t pixel_width,
+ size_t pixel_height,
+ int64 elapsed_time,
+ int64 time_stamp) {
+ size_t buffer_size = VideoFrame::SizeOf(w, h);
+ uint8* buffer = new uint8[buffer_size];
+ Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
+ elapsed_time, time_stamp, 0);
}
} // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe.h b/talk/session/phone/webrtcvideoframe.h
index 3431969..e041003 100644
--- a/talk/session/phone/webrtcvideoframe.h
+++ b/talk/session/phone/webrtcvideoframe.h
@@ -1,6 +1,6 @@
/*
* libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2011 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -46,6 +46,10 @@
WebRtcVideoFrame();
~WebRtcVideoFrame();
+ // Creates a frame from a raw sample with FourCC "format" and size "w" x "h".
+ // "h" can be negative indicating a vertically flipped image.
+ // "dh" is destination height if cropping is desired and is always positive.
+ // Returns "true" if successful.
bool Init(uint32 format, int w, int h, int dw, int dh,
uint8* sample, size_t sample_size,
size_t pixel_width, size_t pixel_height,
@@ -60,8 +64,14 @@
size_t pixel_width, size_t pixel_height,
int64 elapsed_time, int64 time_stamp, int rotation);
void Detach(uint8** buffer, size_t* buffer_size);
+ bool AddWatermark();
+ webrtc::VideoFrame* frame() { return &video_frame_; }
- bool HasImage() const { return video_frame_.Buffer() != NULL; }
+ // From base class VideoFrame.
+ virtual bool Reset(uint32 format, int w, int h, int dw, int dh,
+ uint8* sample, size_t sample_size,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time, int64 time_stamp, int rotation);
virtual size_t GetWidth() const;
virtual size_t GetHeight() const;
@@ -72,18 +82,18 @@
virtual uint8* GetUPlane();
virtual uint8* GetVPlane();
virtual int32 GetYPitch() const { return video_frame_.Width(); }
- virtual int32 GetUPitch() const { return video_frame_.Width() / 2; }
- virtual int32 GetVPitch() const { return video_frame_.Width() / 2; }
+ virtual int32 GetUPitch() const { return (video_frame_.Width() + 1) / 2; }
+ virtual int32 GetVPitch() const { return (video_frame_.Width() + 1) / 2; }
virtual size_t GetPixelWidth() const { return pixel_width_; }
virtual size_t GetPixelHeight() const { return pixel_height_; }
virtual int64 GetElapsedTime() const { return elapsed_time_; }
- virtual int64 GetTimeStamp() const { return video_frame_.TimeStamp(); }
+ virtual int64 GetTimeStamp() const { return time_stamp_; }
virtual void SetElapsedTime(int64 elapsed_time) {
elapsed_time_ = elapsed_time;
}
virtual void SetTimeStamp(int64 time_stamp) {
- video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+ time_stamp_ = time_stamp;
}
virtual int GetRotation() const { return rotation_; }
@@ -92,23 +102,22 @@
virtual bool MakeExclusive();
virtual size_t CopyToBuffer(uint8* buffer, size_t size) const;
virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
- size_t size, size_t pitch_rgb) const;
- virtual void StretchToPlanes(uint8* y, uint8* u, uint8* v,
- int32 pitchY, int32 pitchU, int32 pitchV,
- size_t width, size_t height,
- bool interpolate, bool crop) const;
- virtual size_t StretchToBuffer(size_t w, size_t h, uint8* buffer, size_t size,
- bool interpolate, bool crop) const;
- virtual void StretchToFrame(VideoFrame* target, bool interpolate,
- bool crop) const;
- virtual VideoFrame* Stretch(size_t w, size_t h, bool interpolate,
- bool crop) const;
+ size_t size, int stride_rgb) const;
private:
+ virtual VideoFrame* CreateEmptyFrame(int w, int h,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time,
+ int64 time_stamp) const;
+ void InitToEmptyBuffer(int w, int h,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time, int64 time_stamp);
+
webrtc::VideoFrame video_frame_;
size_t pixel_width_;
size_t pixel_height_;
int64 elapsed_time_;
+ int64 time_stamp_;
int rotation_;
};
} // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe_unittest.cc b/talk/session/phone/webrtcvideoframe_unittest.cc
index cd1f033..16e5e16 100644
--- a/talk/session/phone/webrtcvideoframe_unittest.cc
+++ b/talk/session/phone/webrtcvideoframe_unittest.cc
@@ -1,439 +1,201 @@
-// Copyright 2008 Google Inc. All Rights Reserved,
-//
-// Author: Justin Uberti (juberti@google.com)
-// Frank Barchard (fbarchard@google.com)
-#include <string>
+/*
+ * libjingle
+ * Copyright 2011 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
#include "talk/base/flags.h"
-#include "talk/base/gunit.h"
-#include "talk/base/pathutils.h"
-#include "talk/base/scoped_ptr.h"
-#include "talk/base/stream.h"
-#include "talk/base/stringutils.h"
-#include "talk/session/phone/formatconversion.h"
+#include "talk/session/phone/videoframe_unittest.h"
#include "talk/session/phone/webrtcvideoframe.h"
-#include "talk/session/phone/testutils.h"
-#include "talk/session/phone/videocommon.h"
-enum {
- ROTATION_0 = 0,
- ROTATION_90 = 90,
- ROTATION_180 = 180,
- ROTATION_270 = 270
-};
+extern int FLAG_yuvconverter_repeat; // from lmivideoframe_unittest.cc
-using cricket::WebRtcVideoFrame;
-using cricket::FOURCC_I420;
-
-static const int kWidth = 1280;
-static const int kHeight = 720;
-static const int kAlignment = 16;
-static const std::string kImageFilename = "faces.1280x720_P420.yuv";
-
-class WebRtcVideoFrameTest : public testing::Test {
- protected:
- virtual void SetUp() {
- // TODO: Fix (add a new flag) or remove repeat_.
- repeat_ = 1;
- }
-
+class WebRtcVideoFrameTest : public VideoFrameTest<cricket::WebRtcVideoFrame> {
public:
- // Load a video frame from disk or a buffer.
- bool LoadFrame(const std::string& filename, uint32 format,
- int32 width, int32 height, WebRtcVideoFrame* frame,
- int rotation) {
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(filename));
- return LoadFrame(ms.get(), format, width, height, frame, rotation);
+ WebRtcVideoFrameTest() {
+ repeat_ = FLAG_yuvconverter_repeat;
}
-
- bool LoadFrame(talk_base::MemoryStream* ms, uint32 format,
- int32 width, int32 height, WebRtcVideoFrame* frame,
- int rotation) {
- if (!ms) {
- return false;
- }
- size_t data_size;
- bool ret = ms->GetSize(&data_size);
- EXPECT_TRUE(ret);
- if (ret) {
- ret = LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()), data_size,
- format, width, height, frame, rotation);
- }
- return ret;
- }
-
- bool LoadFrame(uint8* sample, size_t sample_size, uint32 format,
- int32 width, int32 height, WebRtcVideoFrame* frame,
- int rotation) {
- for (int i = 0; i < repeat_; ++i) {
- if (!frame->Init(format, width, height, width, height,
- sample, sample_size, 1, 1, 0, 0, 0)) {
- return false;
- }
- }
- return true;
- }
-
- talk_base::MemoryStream* LoadSample(const std::string& filename) {
- talk_base::Pathname path(cricket::GetTestFilePath(filename));
- talk_base::scoped_ptr<talk_base::FileStream> fs(
- talk_base::Filesystem::OpenFile(path, "rb"));
- if (!fs.get()) {
- return NULL;
- }
-
- char buf[4096];
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(
- new talk_base::MemoryStream());
- talk_base::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get());
- if (res != talk_base::SR_SUCCESS) {
- return NULL;
- }
-
- return ms.release();
- }
-
- // Write an I420 frame out to disk.
- bool DumpFrame(const std::string& prefix,
- const WebRtcVideoFrame& frame) {
- char filename[256];
- talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
- prefix.c_str(), frame.GetWidth(), frame.GetHeight());
- size_t out_size = cricket::VideoFrame::SizeOf(frame.GetWidth(),
- frame.GetHeight());
- talk_base::scoped_array<uint8> out(new uint8[out_size]);
- frame.CopyToBuffer(out.get(), out_size);
- return DumpSample(filename, out.get(), out_size);
- }
-
- bool DumpSample(const std::string& filename, const void* buffer, int size) {
- talk_base::Pathname path(filename);
- talk_base::scoped_ptr<talk_base::FileStream> fs(
- talk_base::Filesystem::OpenFile(path, "wb"));
- if (!fs.get()) {
- return false;
- }
-
- return (fs->Write(buffer, size, NULL, NULL) == talk_base::SR_SUCCESS);
- }
-
- // Create a test image for YUV 420 formats with 12 bits per pixel.
- talk_base::MemoryStream* CreateYuv420Sample(uint32 width, uint32 height) {
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(
- new talk_base::MemoryStream);
- if (!ms->ReserveSize(width * height * 12 / 8)) {
- return NULL;
- }
-
- for (uint32 i = 0; i < width * height * 12 / 8; ++i) {
- char value = ((i / 63) & 1) ? 192 : 64;
- ms->Write(&value, sizeof(value), NULL, NULL);
- }
- return ms.release();
- }
-
- talk_base::MemoryStream* CreateRgbSample(uint32 fourcc,
- uint32 width, uint32 height) {
- int r_pos, g_pos, b_pos, bytes;
- if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
- return NULL;
- }
-
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(
- new talk_base::MemoryStream);
- if (!ms->ReserveSize(width * height * bytes)) {
- return NULL;
- }
-
- for (uint32 y = 0; y < height; ++y) {
- for (uint32 x = 0; x < width; ++x) {
- uint8 rgb[4] = { 255, 255, 255, 255 };
- rgb[r_pos] = ((x / 63) & 1) ? 224 : 32;
- rgb[g_pos] = (x % 63 + y % 63) + 96;
- rgb[b_pos] = ((y / 63) & 1) ? 224 : 32;
- ms->Write(rgb, bytes, NULL, NULL);
- }
- }
- return ms.release();
- }
-
- // Convert RGB to 420.
- // A negative height inverts the image.
- bool ConvertRgb(const talk_base::MemoryStream* ms,
- uint32 fourcc, int32 width, int32 height,
- WebRtcVideoFrame* frame) {
- int r_pos, g_pos, b_pos, bytes;
- if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
- return false;
- }
- int stride = width * bytes;
- const uint8* start = reinterpret_cast<const uint8*>(ms->GetBuffer());
- if (height < 0) {
- height = -height;
- start = start + stride * (height - 1);
- stride = -stride;
- }
- frame->InitToBlack(width, height, 1, 1, 0, 0);
- for (int32 y = 0; y < height; y += 2) {
- for (int32 x = 0; x < width; x += 2) {
- const uint8* rgb[4];
- uint8 yuv[4][3];
- rgb[0] = start + y * stride + x * bytes;
- rgb[1] = rgb[0] + bytes;
- rgb[2] = rgb[0] + stride;
- rgb[3] = rgb[2] + bytes;
- for (size_t i = 0; i < 4; ++i) {
- ConvertRgbPixel(rgb[i][r_pos], rgb[i][g_pos], rgb[i][b_pos],
- &yuv[i][0], &yuv[i][1], &yuv[i][2]);
- }
- frame->GetYPlane()[width * y + x] = yuv[0][0];
- frame->GetYPlane()[width * y + x + 1] = yuv[1][0];
- frame->GetYPlane()[width * (y + 1) + x] = yuv[2][0];
- frame->GetYPlane()[width * (y + 1) + x + 1] = yuv[3][0];
- frame->GetUPlane()[width / 2 * (y / 2) + x / 2] =
- (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4;
- frame->GetVPlane()[width / 2 * (y / 2) + x / 2] =
- (yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2] + 2) / 4;
- }
- }
- return true;
- }
-
- // Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia.
- void ConvertRgbPixel(uint8 r, uint8 g, uint8 b,
- uint8* y, uint8* u, uint8* v) {
- *y = static_cast<int>(.257 * r + .504 * g + .098 * b) + 16;
- *u = static_cast<int>(-.148 * r - .291 * g + .439 * b) + 128;
- *v = static_cast<int>(.439 * r - .368 * g - .071 * b) + 128;
- }
-
- bool GetRgbPacking(uint32 fourcc,
- int* r_pos, int* g_pos, int* b_pos, int* bytes) {
- if (fourcc == cricket::FOURCC_RAW) {
- *r_pos = 0;
- *g_pos = 1;
- *b_pos = 2;
- *bytes = 3; // RGB in memory
- } else if (fourcc == cricket::FOURCC_24BG) {
- *r_pos = 2;
- *g_pos = 1;
- *b_pos = 0;
- *bytes = 3; // BGR in memory
- } else if (fourcc == cricket::FOURCC_ABGR) {
- *r_pos = 0;
- *g_pos = 1;
- *b_pos = 2;
- *bytes = 4; // RGBA in memory
- } else if (fourcc == cricket::FOURCC_BGRA) {
- *r_pos = 1;
- *g_pos = 2;
- *b_pos = 3;
- *bytes = 4; // ARGB in memory
- } else if (fourcc == cricket::FOURCC_ARGB) {
- *r_pos = 2;
- *g_pos = 1;
- *b_pos = 0;
- *bytes = 4; // BGRA in memory
- } else {
- return false;
- }
- return true;
- }
-
- // Comparison functions for testing.
- static bool IsNull(const WebRtcVideoFrame& frame) {
- return !frame.HasImage();
- }
-
- static bool IsSize(const WebRtcVideoFrame& frame,
- uint32 width, uint32 height) {
- return frame.HasImage() &&
- frame.GetYPitch() >= static_cast<int32>(width) &&
- frame.GetUPitch() >= static_cast<int32>(width) / 2 &&
- frame.GetVPitch() >= static_cast<int32>(width) / 2 &&
- frame.GetWidth() == width && frame.GetHeight() == height;
- }
-
- static bool IsPlaneEqual(const std::string& name,
- const uint8* plane1, uint32 pitch1,
- const uint8* plane2, uint32 pitch2,
- uint32 width, uint32 height,
- int max_error) {
- const uint8* r1 = plane1;
- const uint8* r2 = plane2;
- for (uint32 y = 0; y < height; ++y) {
- for (uint32 x = 0; x < width; ++x) {
- if (abs(static_cast<int>(r1[x] - r2[x])) > max_error) {
- LOG(LS_INFO) << "IsPlaneEqual(" << name << "): pixel["
- << x << "," << y << "] differs: "
- << static_cast<int>(r1[x]) << " vs "
- << static_cast<int>(r2[x]);
- return false;
- }
- }
- r1 += pitch1;
- r2 += pitch2;
- }
- return true;
- }
-
- static bool IsFrameContiguous(const WebRtcVideoFrame& frame) {
- int width = frame.GetWidth();
- int height = frame.GetHeight();
- const uint8* y = frame.GetYPlane();
- const uint8* u = frame.GetUPlane();
- const uint8* v = frame.GetVPlane();
- int size = width * height * 3 / 2;
- bool u_near = (u - y) < size;
- bool v_near = (v - y) < size;
- return u_near && v_near;
- }
-
- static bool IsEqual(const WebRtcVideoFrame& frame,
- size_t width, size_t height,
- size_t pixel_width, size_t pixel_height,
- int64 elapsed_time, int64 time_stamp,
- const uint8* y, uint32 ypitch,
- const uint8* u, uint32 upitch,
- const uint8* v, uint32 vpitch,
- int max_error) {
- if (!IsFrameContiguous(frame)) {
- LOG(LS_INFO) << "lmi frame is not contiguous";
- }
- return IsSize(frame, width, height) &&
- frame.GetPixelWidth() == pixel_width &&
- frame.GetPixelHeight() == pixel_height &&
- frame.GetElapsedTime() == elapsed_time &&
- frame.GetTimeStamp() == time_stamp &&
- IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch,
- width, height, max_error) &&
- IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch,
- width / 2, height / 2, max_error) &&
- IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch,
- width / 2, height / 2, max_error);
- }
-
- static bool IsEqual(const WebRtcVideoFrame& frame1,
- const WebRtcVideoFrame& frame2,
- int max_error) {
- return IsEqual(frame1, frame2.GetWidth(), frame2.GetHeight(),
- frame2.GetPixelWidth(), frame2.GetPixelHeight(),
- frame2.GetElapsedTime(), frame2.GetTimeStamp(),
- frame2.GetYPlane(), frame2.GetYPitch(),
- frame2.GetUPlane(), frame2.GetUPitch(),
- frame2.GetVPlane(), frame2.GetVPitch(),
- max_error);
- }
-
- protected:
- int repeat_;
};
-TEST_F(WebRtcVideoFrameTest, ConvertToARGBBuffer) {
- size_t out_size = kWidth * kHeight * 4;
- talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
- uint8 *out = ALIGNP(outbuf.get(), kAlignment);
- WebRtcVideoFrame frame;
- ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
- &frame, ROTATION_0));
-
- // TODO: Add test to convert these back to I420, to ensure the
- // conversion is done correctly.
- for (int i = 0; i < repeat_; ++i) {
- EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
- out,
- out_size, kWidth * 4));
- }
+#define TEST_WEBRTCVIDEOFRAME(X) TEST_F(WebRtcVideoFrameTest, X) { \
+ VideoFrameTest<cricket::WebRtcVideoFrame>::X(); \
}
-// Test basic contruction of an image from an I420 buffer.
-TEST_F(WebRtcVideoFrameTest, InitI420) {
- WebRtcVideoFrame frame;
- EXPECT_TRUE(IsNull(frame));
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
- ASSERT_TRUE(ms.get() != NULL);
- size_t data_size;
- ASSERT_TRUE(ms->GetSize(&data_size));
- uint8* buf = reinterpret_cast<uint8*>(ms->GetBuffer());
- EXPECT_TRUE(LoadFrame(buf, data_size, FOURCC_I420,
- kWidth, kHeight, &frame, ROTATION_0));
+TEST_WEBRTCVIDEOFRAME(ConstructI420)
+TEST_WEBRTCVIDEOFRAME(ConstructI4201Pixel)
+// TODO: WebRtcVideoFrame does not support horizontal crop.
+// Re-evaluate once it supports 3 independent planes, since we might want to
+// just Init normally and then crop by adjusting pointers.
+// TEST_WEBRTCVIDEOFRAME(ConstructI420CropHorizontal)
+TEST_WEBRTCVIDEOFRAME(ConstructI420CropVertical)
+// TODO: WebRtcVideoFrame is not currently refcounted.
+// TEST_WEBRTCVIDEOFRAME(ConstructCopy)
+// TEST_WEBRTCVIDEOFRAME(ConstructCopyIsRef)
+TEST_WEBRTCVIDEOFRAME(ConstructBlack)
+// TODO: Implement Jpeg
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400)
+// TODO: WebRtcVideoFrame does not support odd sizes.
+// Re-evaluate once WebRTC switches to libyuv
+// TEST_WEBRTCVIDEOFRAME(ConstructYuy2AllSizes)
+TEST_WEBRTCVIDEOFRAME(Reset)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerBGGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGRBGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerGBRGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBayerRGGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToI400BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromABGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB1555BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGB4444BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromARGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBGRABufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRAWBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB24BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromRGB565BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerBGGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGRBGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerGBRGBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromBayerRGGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromI400BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromYUY2BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertFromUYVYBufferInverted)
+//TEST_WEBRTCVIDEOFRAME(ConvertToI422Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGRBG)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGBRG)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerBGGR)
+TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerRGGB)
+TEST_WEBRTCVIDEOFRAME(CopyToBuffer)
+TEST_WEBRTCVIDEOFRAME(CopyToBuffer1Pixel)
+//TEST_WEBRTCVIDEOFRAME(ConstructARGBBlackWhitePixel)
- const uint8* y = reinterpret_cast<uint8*>(ms->GetBuffer());
- const uint8* u = y + kWidth * kHeight;
- const uint8* v = u + kWidth * kHeight / 4;
- EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
- y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+TEST_WEBRTCVIDEOFRAME(StretchToFrame)
+TEST_WEBRTCVIDEOFRAME(Copy)
+// TODO: WebRtcVideoFrame is not currently refcounted.
+// TEST_WEBRTCVIDEOFRAME(CopyIsRef)
+TEST_WEBRTCVIDEOFRAME(MakeExclusive)
+
+// These functions test implementation-specific details.
+TEST_F(WebRtcVideoFrameTest, AttachAndRelease) {
+ cricket::WebRtcVideoFrame frame1, frame2;
+ ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+ const int64 time_stamp = 0x7FFFFFFFFFFFFFF0LL;
+ frame1.SetTimeStamp(time_stamp);
+ EXPECT_EQ(time_stamp, frame1.GetTimeStamp());
+ frame2.Attach(frame1.frame()->Buffer(), frame1.frame()->Size(),
+ kWidth, kHeight, 1, 1,
+ frame1.GetElapsedTime(), frame1.GetTimeStamp(), 0);
+ EXPECT_TRUE(IsEqual(frame1, frame2, 0));
+ uint8* buffer;
+ size_t size;
+ frame2.Detach(&buffer, &size);
+ EXPECT_EQ(frame1.frame()->Buffer(), buffer);
+ EXPECT_EQ(frame1.frame()->Size(), size);
+ EXPECT_TRUE(IsNull(frame2));
+ EXPECT_TRUE(IsSize(frame1, kWidth, kHeight));
}
-// Test constructing an image from a I420 buffer
-TEST_F(WebRtcVideoFrameTest, ConstructI420) {
- WebRtcVideoFrame frame;
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(
- CreateYuv420Sample(kWidth, kHeight));
- EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420,
- kWidth, kHeight, &frame, ROTATION_0));
-
- const uint8* y = reinterpret_cast<uint8*>(ms.get()->GetBuffer());
- const uint8* u = y + kWidth * kHeight;
- const uint8* v = u + kWidth * kHeight / 4;
- EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
- y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+TEST_F(WebRtcVideoFrameTest, Transfer) {
+ cricket::WebRtcVideoFrame frame1, frame2;
+ ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
+ uint8* buffer;
+ size_t size;
+ frame1.Detach(&buffer, &size),
+ frame2.Attach(buffer, size, kWidth, kHeight, 1, 1,
+ frame1.GetElapsedTime(), frame1.GetTimeStamp(), 0);
+ EXPECT_TRUE(IsNull(frame1));
+ EXPECT_TRUE(IsSize(frame2, kWidth, kHeight));
}
-// Test creating an empty image and initing it to black.
-TEST_F(WebRtcVideoFrameTest, ConstructBlack) {
- WebRtcVideoFrame frame;
- for (int i = 0; i < repeat_; ++i) {
- EXPECT_TRUE(frame.InitToBlack(kWidth, kHeight, 1, 1, 0, 0));
- }
- EXPECT_TRUE(IsSize(frame, kWidth, kHeight));
- EXPECT_EQ(16, *frame.GetYPlane());
- EXPECT_EQ(128, *frame.GetUPlane());
- EXPECT_EQ(128, *frame.GetVPlane());
-}
-
-TEST_F(WebRtcVideoFrameTest, Copy) {
- WebRtcVideoFrame frame1;
- talk_base::scoped_ptr<cricket::WebRtcVideoFrame> frame2;
- ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
- &frame1, ROTATION_0));
- frame2.reset(static_cast<WebRtcVideoFrame*>(frame1.Copy()));
- EXPECT_TRUE(IsEqual(frame1, *frame2.get(), 0));
-}
-
-TEST_F(WebRtcVideoFrameTest, CopyToBuffer) {
- size_t out_size = kWidth * kHeight * 3 / 2;
- talk_base::scoped_array<uint8> out(new uint8[out_size]);
- WebRtcVideoFrame frame;
- talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
- ASSERT_TRUE(ms.get() != NULL);
- size_t data_size;
- ASSERT_TRUE(ms->GetSize(&data_size));
- EXPECT_TRUE(LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()),
- data_size, FOURCC_I420,
- kWidth, kHeight, &frame, ROTATION_0));
- for (int i = 0; i < repeat_; ++i) {
- EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
- }
- EXPECT_EQ(0, memcmp(out.get(), ms->GetBuffer(), out_size));
-}
-
-TEST_F(WebRtcVideoFrameTest, CopyToBuffer1Pixel) {
- size_t out_size = 3;
- talk_base::scoped_array<uint8> out(new uint8[out_size + 1]);
- memset(out.get(), 0xfb, out_size + 1); // Fill buffer
- uint8 pixel[3] = { 1, 2, 3 };
- WebRtcVideoFrame frame;
- EXPECT_TRUE(LoadFrame(pixel, sizeof(pixel), FOURCC_I420,
- 1, 1, &frame, ROTATION_0));
- for (int i = 0; i < repeat_; ++i) {
- EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
- }
- EXPECT_EQ(1, out.get()[0]); // Check Y. Should be 1.
- EXPECT_EQ(2, out.get()[1]); // Check U. Should be 2.
- EXPECT_EQ(3, out.get()[2]); // Check V. Should be 3.
- EXPECT_EQ(0xfb, out.get()[3]); // Check sentinel is still intact.
-}
-
-// TODO: Merge this with the LmiVideoFrame test for more test cases
-// when they are supported.
diff --git a/talk/session/phone/webrtcvie.h b/talk/session/phone/webrtcvie.h
index d6ef007..75fe837 100644
--- a/talk/session/phone/webrtcvie.h
+++ b/talk/session/phone/webrtcvie.h
@@ -37,14 +37,14 @@
#include "modules/interface/module_common_types.h"
#include "modules/video_capture/main/interface/video_capture.h"
#include "modules/video_render/main/interface/video_render.h"
-#include "video_engine/main/interface/vie_base.h"
-#include "video_engine/main/interface/vie_capture.h"
-#include "video_engine/main/interface/vie_codec.h"
-#include "video_engine/main/interface/vie_errors.h"
-#include "video_engine/main/interface/vie_image_process.h"
-#include "video_engine/main/interface/vie_network.h"
-#include "video_engine/main/interface/vie_render.h"
-#include "video_engine/main/interface/vie_rtp_rtcp.h"
+#include "video_engine/include/vie_base.h"
+#include "video_engine/include/vie_capture.h"
+#include "video_engine/include/vie_codec.h"
+#include "video_engine/include/vie_errors.h"
+#include "video_engine/include/vie_image_process.h"
+#include "video_engine/include/vie_network.h"
+#include "video_engine/include/vie_render.h"
+#include "video_engine/include/vie_rtp_rtcp.h"
#else
#include "third_party/webrtc/files/include/common_types.h"
#include "third_party/webrtc/files/include/module_common_types.h"
@@ -126,7 +126,7 @@
webrtc::ViENetwork* network() { return network_.get(); }
webrtc::ViERender* render() { return render_.get(); }
webrtc::ViERTP_RTCP* rtp() { return rtp_.get(); }
- webrtc::ViEImageProcess* sync() { return image_.get(); }
+ webrtc::ViEImageProcess* image() { return image_.get(); }
int error() { return base_->LastError(); }
private:
@@ -139,6 +139,23 @@
scoped_vie_ptr<webrtc::ViERTP_RTCP> rtp_;
scoped_vie_ptr<webrtc::ViEImageProcess> image_;
};
-}
+
+// Adds indirection to static WebRtc functions, allowing them to be mocked.
+class ViETraceWrapper {
+ public:
+ virtual ~ViETraceWrapper() {}
+
+ virtual int SetTraceFilter(const unsigned int filter) {
+ return webrtc::VideoEngine::SetTraceFilter(filter);
+ }
+ virtual int SetTraceFile(const char* fileNameUTF8) {
+ return webrtc::VideoEngine::SetTraceFile(fileNameUTF8);
+ }
+ virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+ return webrtc::VideoEngine::SetTraceCallback(callback);
+ }
+};
+
+} // namespace cricket
#endif // TALK_SESSION_PHONE_WEBRTCVIE_H_
diff --git a/talk/session/phone/webrtcvoe.h b/talk/session/phone/webrtcvoe.h
index 398928e..709b3d6 100644
--- a/talk/session/phone/webrtcvoe.h
+++ b/talk/session/phone/webrtcvoe.h
@@ -191,6 +191,7 @@
return webrtc::VoiceEngine::SetTraceCallback(callback);
}
};
-}
+
+} // namespace cricket
#endif // TALK_SESSION_PHONE_WEBRTCVOE_H_
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index 3dfad11..98a1b85 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -25,6 +25,9 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#ifdef HAVE_WEBRTC_VOICE
@@ -42,6 +45,8 @@
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
#include "talk/base/stringutils.h"
+#include "talk/session/phone/streamparams.h"
+#include "talk/session/phone/voiceprocessor.h"
#include "talk/session/phone/webrtcvoe.h"
#ifdef WIN32
@@ -301,6 +306,10 @@
adm_sc_ = NULL;
}
+ // Test to see if the media processor was deregistered properly
+ ASSERT(SignalRxMediaFrame.is_empty());
+ ASSERT(SignalTxMediaFrame.is_empty());
+
tracing_->SetTraceCallback(NULL);
}
@@ -373,17 +382,23 @@
// First check whether there is a valid sound device for playback.
// TODO: Clean this up when we support setting the soundclip device.
#ifdef WIN32
- int num_of_devices = 0;
- if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
- num_of_devices > 0) {
- if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
- == -1) {
- LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
- voe_wrapper_sc_->error());
- return false;
+ // The SetPlayoutDevice may not be implemented in the case of external ADM.
+ // TODO: We should only check the adm_sc_ here, but current
+ // PeerConnection interface never set the adm_sc_, so need to check both
+ // in order to determine if the external adm is used.
+ if (!adm_ && !adm_sc_) {
+ int num_of_devices = 0;
+ if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
+ num_of_devices > 0) {
+ if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
+ == -1) {
+ LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
+ voe_wrapper_sc_->error());
+ return false;
+ }
+ } else {
+ LOG(LS_WARNING) << "No valid sound playout device found.";
}
- } else {
- LOG(LS_WARNING) << "No valid sound playout device found.";
}
#endif
@@ -438,6 +453,12 @@
LOG_RTCERR1(SetEcStatus, aec);
return false;
}
+ if (aec) {
+ if (voe_wrapper_->processing()->SetEcMetricsStatus(true) == -1) {
+ LOG_RTCERR1(SetEcMetricsStatus, true);
+ return false;
+ }
+ }
if (voe_wrapper_->processing()->SetAgcStatus(agc) == -1) {
LOG_RTCERR1(SetAgcStatus, agc);
@@ -480,7 +501,7 @@
}
// No typing detection support on iOS or Android.
-#endif // !IOS && !ANDROID
+#endif // !IOS && !ANDROID
return true;
}
@@ -825,15 +846,16 @@
// Ignore spammy trace messages, mostly from the stats API when we haven't
// gotten RTCP info yet from the remote side.
-static bool ShouldIgnoreTrace(const std::string& trace) {
+bool WebRtcVoiceEngine::ShouldIgnoreTrace(const std::string& trace) {
static const char* kTracesToIgnore[] = {
"\tfailed to GetReportBlockInformation",
"GetRecCodec() failed to get received codec",
- "GetRemoteRTCPData() failed to measure statistics dueto lack of received RTP and/or RTCP packets", // NOLINT
- "GetRemoteRTCPData() failed to retrieve sender info for remoteside",
- "GetRTPStatistics() failed to measure RTT since noRTP packets have been received yet", // NOLINT
+ "GetReceivedRtcpStatistics: Could not get received RTP statistics",
+ "GetRemoteRTCPData() failed to measure statistics due to lack of received RTP and/or RTCP packets", // NOLINT
+ "GetRemoteRTCPData() failed to retrieve sender info for remote side",
+ "GetRTPStatistics() failed to measure RTT since no RTP packets have been received yet", // NOLINT
"GetRTPStatistics() failed to read RTP statistics from the RTP/RTCP module",
- "GetRTPStatistics() failed to retrieve RTT fromthe RTP/RTCP module",
+ "GetRTPStatistics() failed to retrieve RTT from the RTP/RTCP module",
"webrtc::RTCPReceiver::SenderInfoReceived No received SR",
"StatisticsRTP() no statisitics availble",
NULL
@@ -865,7 +887,7 @@
} else {
std::string msg(trace + 71, length - 72);
if (!ShouldIgnoreTrace(msg)) {
- LOG_V(sev) << "WebRtc VoE:" << msg;
+ LOG_V(sev) << "WebRtc:" << msg;
}
}
}
@@ -877,13 +899,13 @@
WebRtcVoiceMediaChannel* channel = NULL;
uint32 ssrc = 0;
LOG(LS_WARNING) << "VoiceEngine error " << err_code << " reported on channel "
- << channel_num << ".";
+ << channel_num << ".";
if (FindChannelAndSsrc(channel_num, &channel, &ssrc)) {
ASSERT(channel != NULL);
channel->OnError(ssrc, err_code);
} else {
LOG(LS_ERROR) << "VoiceEngine channel " << channel_num
- << " could not be found in the channel list when error reported.";
+ << " could not be found in channel list when error reported.";
}
}
@@ -906,6 +928,35 @@
return false;
}
+// This method will search through the WebRtcVoiceMediaChannels and
+// obtain the voice engine's channel number.
+bool WebRtcVoiceEngine::FindChannelNumFromSsrc(
+ uint32 ssrc, MediaProcessorDirection direction, int* channel_num) {
+ ASSERT(channel_num != NULL);
+
+ *channel_num = -1;
+ // Find corresponding channel for ssrc.
+ for (ChannelList::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ ASSERT(*it != NULL);
+ uint32 local_ssrc;
+ if ((direction & MPD_RX) != 0) {
+ *channel_num = (*it)->GetChannelNum(ssrc);
+ }
+ if (*channel_num == -1 &&
+ voe()->rtp()->GetLocalSSRC((*it)->voe_channel(), local_ssrc) != -1) {
+ if (ssrc == local_ssrc) {
+ *channel_num = (*it)->voe_channel();
+ }
+ }
+ if (*channel_num != -1) {
+ return true;
+ }
+ }
+ LOG(LS_WARNING) << "FindChannelFromSsrc. No Channel Found for Ssrc: " << ssrc;
+ return false;
+}
+
void WebRtcVoiceEngine::RegisterChannel(WebRtcVoiceMediaChannel *channel) {
talk_base::CritScope lock(&channels_cs_);
channels_.push_back(channel);
@@ -1013,18 +1064,144 @@
return true;
}
+bool WebRtcVoiceEngine::RegisterProcessor(
+ uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) {
+ bool register_with_webrtc = false;
+ int channel_id = -1;
+ bool success = false;
+ bool found_channel = FindChannelNumFromSsrc(ssrc, direction, &channel_id);
+ if (voice_processor == NULL || !found_channel) {
+ LOG(LS_WARNING) << "Media Processing Registration Failed. ssrc: " << ssrc
+ << " foundChannel: " << found_channel;
+ return false;
+ }
+ talk_base::CritScope cs(&signal_media_critical_);
+ webrtc::ProcessingTypes processing_type;
+ if (direction == MPD_RX) {
+ processing_type = webrtc::kPlaybackPerChannel;
+ if (SignalRxMediaFrame.is_empty()) {
+ register_with_webrtc = true;
+ }
+ SignalRxMediaFrame.connect(voice_processor,
+ &VoiceProcessor::OnFrame);
+ } else {
+ processing_type = webrtc::kRecordingPerChannel;
+ if (SignalTxMediaFrame.is_empty()) {
+ register_with_webrtc = true;
+ }
+ SignalTxMediaFrame.connect(voice_processor,
+ &VoiceProcessor::OnFrame);
+ }
+ if (register_with_webrtc) {
+ if (voe()->media()->
+ RegisterExternalMediaProcessing(channel_id,
+ processing_type,
+ *this) == -1) {
+ LOG_RTCERR2(RegisterExternalMediaProcessing,
+ channel_id,
+ processing_type);
+ success = false;
+ } else {
+ LOG(LS_INFO) << "Media Processing Registration Succeeded. channel:"
+ << channel_id;
+ success = true;
+ }
+ } else {
+ // If we don't have to register with the engine, we just needed to
+ // connect a new processor, set success to true;
+ success = true;
+ }
+ return success;
+}
+
+bool WebRtcVoiceEngine::UnregisterProcessor(
+ uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction) {
+ int channel_id = -1;
+ bool found_channel = FindChannelNumFromSsrc(ssrc, direction, &channel_id);
+ bool success = true;
+ if (voice_processor == NULL || !found_channel) {
+ LOG(LS_WARNING) << "Media Processing Deregistration Failed. ssrc: "
+ << ssrc
+ << " foundChannel: "
+ << found_channel;
+ return false;
+ }
+ talk_base::CritScope cs(&signal_media_critical_);
+ if ((direction & MPD_RX) != 0) {
+ SignalRxMediaFrame.disconnect(voice_processor);
+ if (SignalRxMediaFrame.is_empty()) {
+ if (voe()->media()->DeRegisterExternalMediaProcessing(channel_id,
+ webrtc::kPlaybackPerChannel) != -1) {
+ LOG(LS_INFO) << "Media Processing DeRegistration Succeeded. channel:"
+ << channel_id;
+ } else {
+ LOG_RTCERR2(DeRegisterExternalMediaProcessing,
+ channel_id,
+ webrtc::kPlaybackPerChannel);
+ success = false;
+ }
+ }
+ }
+ if ((direction & MPD_TX) != 0) {
+ SignalTxMediaFrame.disconnect(voice_processor);
+ if (SignalTxMediaFrame.is_empty()) {
+ if (voe()->media()->DeRegisterExternalMediaProcessing(channel_id,
+ webrtc::kRecordingPerChannel) != -1) {
+ LOG(LS_INFO) << "Media Processing DeRegistration Succeeded. channel:"
+ << channel_id;
+ } else {
+ LOG_RTCERR2(DeRegisterExternalMediaProcessing,
+ channel_id,
+ webrtc::kRecordingPerChannel);
+ success = false;
+ }
+ }
+ }
+ return success;
+}
+
+// Implementing method from WebRtc VoEMediaProcess interface
+void WebRtcVoiceEngine::Process(const int channel,
+ const webrtc::ProcessingTypes type,
+ WebRtc_Word16 audio10ms[],
+ const int length,
+ const int sampling_freq,
+ const bool is_stereo) {
+ uint32 ssrc;
+ WebRtcVoiceMediaChannel* media_channel;
+
+ if (FindChannelAndSsrc(channel, &media_channel, &ssrc)) {
+ talk_base::CritScope cs(&signal_media_critical_);
+ AudioFrame frame(audio10ms, length, sampling_freq, is_stereo);
+ if (type == webrtc::kPlaybackPerChannel) {
+ SignalRxMediaFrame(ssrc, &frame);
+ } else if (type == webrtc::kRecordingPerChannel) {
+ SignalTxMediaFrame(ssrc, &frame);
+ }
+ } else {
+ LOG(LS_WARNING) << "MediaProcess Callback invoked with unexpected channel: "
+ << channel;
+ }
+}
+
// WebRtcVoiceMediaChannel
WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
: WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
engine,
engine->voe()->base()->CreateChannel()),
+ recv_codecs_set_(false),
channel_options_(0),
agc_adjusted_(false),
dtmf_allowed_(false),
desired_playout_(false),
playout_(false),
desired_send_(SEND_NOTHING),
- send_(SEND_NOTHING) {
+ send_(SEND_NOTHING),
+ local_ssrc_(0) {
engine->RegisterChannel(this);
LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
<< voe_channel();
@@ -1038,9 +1215,6 @@
// Enable RTCP (for quality stats and feedback messages)
EnableRtcp(voe_channel());
- // Create a random but nonzero send SSRC
- SetSendSsrc(talk_base::CreateRandomNonZeroId());
-
// Reset all recv codecs; they will be enabled via SetRecvCodecs.
ResetRecvCodecs(voe_channel());
}
@@ -1059,8 +1233,9 @@
engine()->UnregisterChannel(this);
// Remove any remaining streams.
while (!mux_channels_.empty()) {
- RemoveStream(mux_channels_.begin()->first);
+ RemoveRecvStream(mux_channels_.begin()->first);
}
+
// Delete the primary channel.
if (engine()->voe()->base()->DeleteChannel(voe_channel()) == -1) {
LOG_RTCERR1(DeleteChannel, voe_channel());
@@ -1099,12 +1274,21 @@
LOG_RTCERR2(SetRecPayloadType, voe_channel(), ToString(voe_codec));
ret = false;
}
+ // Set the receive codecs on all receiving channels.
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end() && ret; ++it) {
+ if (engine()->voe()->codec()->SetRecPayloadType(
+ it->second, voe_codec) == -1) {
+ LOG_RTCERR2(SetRecPayloadType, it->second, ToString(voe_codec));
+ ret = false;
+ }
+ }
} else {
LOG(LS_WARNING) << "Unknown codec " << ToString(*it);
ret = false;
}
}
-
+ recv_codecs_set_ = ret;
return ret;
}
@@ -1210,6 +1394,7 @@
return false;
}
+ send_codec_.reset(new webrtc::CodecInst(send_codec));
return true;
}
@@ -1293,7 +1478,9 @@
bool WebRtcVoiceMediaChannel::SetSend(SendFlags send) {
desired_send_ = send;
- return ChangeSend(desired_send_);
+ if (local_ssrc_ != 0)
+ return ChangeSend(desired_send_);
+ return true;
}
bool WebRtcVoiceMediaChannel::PauseSend() {
@@ -1325,6 +1512,11 @@
}
#endif // CHROMEOS
+ if ((channel_options_ & OPT_AGC_MINUS_10DB) && !agc_adjusted_) {
+ if (engine()->AdjustAgcLevel(kMinus10DbAdjustment)) {
+ agc_adjusted_ = true;
+ }
+ }
// VoiceEngine resets sequence number when StopSend is called. This
// sometimes causes libSRTP to complain about packets being
@@ -1399,9 +1591,65 @@
return true;
}
-bool WebRtcVoiceMediaChannel::AddStream(uint32 ssrc) {
+bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) {
+ if (local_ssrc_ != 0) {
+ LOG(LS_ERROR) << "WebRtcVoiceMediaChannel supports one sending channel.";
+ return false;
+ }
+
+ if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), sp.first_ssrc())
+ == -1) {
+ LOG_RTCERR2(SetSendSSRC, voe_channel(), sp.first_ssrc());
+ return false;
+ }
+ // Set the SSRC on the receive channels.
+ // Receive channels have to have the same SSRC in order to send receiver
+ // reports with this SSRC.
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ int channel_id = it->second;
+ if (engine()->voe()->rtp()->SetLocalSSRC(channel_id,
+ sp.first_ssrc()) != 0) {
+ LOG_RTCERR1(SetLocalSSRC, it->first);
+ return false;
+ }
+ }
+
+ if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
+ sp.cname.c_str()) == -1) {
+ LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), sp.cname);
+ return false;
+ }
+
+ local_ssrc_ = sp.first_ssrc();
+ if (desired_send_ != send_)
+ return ChangeSend(desired_send_);
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::RemoveSendStream(uint32 ssrc) {
+ if (ssrc != local_ssrc_) {
+ return false;
+ }
+ local_ssrc_ = 0;
+ ChangeSend(SEND_NOTHING);
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) {
talk_base::CritScope lock(&mux_channels_cs_);
+ // Reuse default channel for recv stream in 1:1 call.
+ if ((channel_options_ & OPT_CONFERENCE) == 0) {
+ LOG(LS_INFO) << "Recv stream " << sp.first_ssrc()
+ << " reuse default channel";
+ return true;
+ }
+
+ if (!VERIFY(sp.ssrcs.size() == 1))
+ return false;
+ uint32 ssrc = sp.first_ssrc();
+
if (mux_channels_.find(ssrc) != mux_channels_.end()) {
return false;
}
@@ -1434,17 +1682,19 @@
// Use the same recv payload types as our default channel.
ResetRecvCodecs(channel);
- int ncodecs = engine()->voe()->codec()->NumOfCodecs();
- for (int i = 0; i < ncodecs; ++i) {
- webrtc::CodecInst voe_codec;
- if (engine()->voe()->codec()->GetCodec(i, voe_codec) != -1) {
- voe_codec.rate = 0; // Needed to make GetRecPayloadType work for ISAC
- if (engine()->voe()->codec()->GetRecPayloadType(
- voe_channel(), voe_codec) != -1) {
- if (engine()->voe()->codec()->SetRecPayloadType(
- channel, voe_codec) == -1) {
- LOG_RTCERR2(SetRecPayloadType, channel, ToString(voe_codec));
- return false;
+ if (recv_codecs_set_) {
+ int ncodecs = engine()->voe()->codec()->NumOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ webrtc::CodecInst voe_codec;
+ if (engine()->voe()->codec()->GetCodec(i, voe_codec) != -1) {
+ voe_codec.rate = 0; // Needed to make GetRecPayloadType work for ISAC
+ if (engine()->voe()->codec()->GetRecPayloadType(
+ voe_channel(), voe_codec) != -1) {
+ if (engine()->voe()->codec()->SetRecPayloadType(
+ channel, voe_codec) == -1) {
+ LOG_RTCERR2(SetRecPayloadType, channel, ToString(voe_codec));
+ return false;
+ }
}
}
}
@@ -1470,7 +1720,7 @@
return SetPlayout(channel, playout_);
}
-bool WebRtcVoiceMediaChannel::RemoveStream(uint32 ssrc) {
+bool WebRtcVoiceMediaChannel::RemoveRecvStream(uint32 ssrc) {
talk_base::CritScope lock(&mux_channels_cs_);
ChannelMap::iterator it = mux_channels_.find(ssrc);
@@ -1538,7 +1788,7 @@
channels.push_back(it->second);
}
} else { // Collect only the channel of the specified ssrc.
- int channel = GetChannel(ssrc);
+ int channel = GetChannelNum(ssrc);
if (-1 == channel) {
LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
return false;
@@ -1579,7 +1829,7 @@
talk_base::CritScope lock(&mux_channels_cs_);
// Determine which channel based on ssrc.
- int channel = (0 == ssrc) ? voe_channel() : GetChannel(ssrc);
+ int channel = (0 == ssrc) ? voe_channel() : GetChannelNum(ssrc);
if (channel == -1) {
LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
return false;
@@ -1619,7 +1869,7 @@
}
// Determine which VoiceEngine channel to play on.
- int channel = (ssrc == 0) ? voe_channel() : GetChannel(ssrc);
+ int channel = (ssrc == 0) ? voe_channel() : GetChannelNum(ssrc);
if (channel == -1) {
return false;
}
@@ -1637,8 +1887,8 @@
ringback_channels_.insert(channel);
LOG(LS_INFO) << "Started ringback on channel " << channel;
} else {
- if (engine()->voe()->file()->StopPlayingFileLocally(channel)
- == -1) {
+ if (engine()->voe()->file()->IsPlayingFileLocally(channel) == 1 &&
+ engine()->voe()->file()->StopPlayingFileLocally(channel) == -1) {
LOG_RTCERR1(StopPlayingFileLocally, channel);
return false;
}
@@ -1675,7 +1925,7 @@
// Pick which channel to send this packet to. If this packet doesn't match
// any multiplexed streams, just send it to the default channel. Otherwise,
// send it to the specific decoder instance for that stream.
- int which_channel = GetChannel(
+ int which_channel = GetChannelNum(
ParseSsrc(packet->data(), packet->length(), false));
if (which_channel == -1) {
which_channel = voe_channel();
@@ -1703,7 +1953,7 @@
void WebRtcVoiceMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
// See above.
- int which_channel = GetChannel(
+ int which_channel = GetChannelNum(
ParseSsrc(packet->data(), packet->length(), true));
if (which_channel == -1) {
which_channel = voe_channel();
@@ -1714,22 +1964,6 @@
packet->length());
}
-void WebRtcVoiceMediaChannel::SetSendSsrc(uint32 ssrc) {
- if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), ssrc)
- == -1) {
- LOG_RTCERR2(SetSendSSRC, voe_channel(), ssrc);
- }
-}
-
-bool WebRtcVoiceMediaChannel::SetRtcpCName(const std::string& cname) {
- if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
- cname.c_str()) == -1) {
- LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), cname);
- return false;
- }
- return true;
-}
-
bool WebRtcVoiceMediaChannel::Mute(bool muted) {
if (engine()->voe()->volume()->SetInputMute(voe_channel(),
muted) == -1) {
@@ -1752,7 +1986,6 @@
// Fill in the sender info, based on what we know, and what the
// remote side told us it got from its RTCP report.
VoiceSenderInfo sinfo;
- memset(&sinfo, 0, sizeof(sinfo));
// Data we obtain locally.
memset(&cs, 0, sizeof(cs));
@@ -1762,6 +1995,7 @@
}
sinfo.ssrc = ssrc;
+ sinfo.codec_name = send_codec_.get() ? send_codec_->plname : "";
sinfo.bytes_sent = cs.bytesSent;
sinfo.packets_sent = cs.packetsSent;
// RTT isn't known until a RTCP report is received. Until then, VoiceEngine
@@ -1792,6 +2026,35 @@
// Local speech level.
sinfo.audio_level = (engine()->voe()->volume()->
GetSpeechInputLevelFullRange(level) != -1) ? level : -1;
+
+ bool echo_metrics_on = false;
+ // These can take on valid negative values, so use the lowest possible level
+ // as default rather than -1.
+ sinfo.echo_return_loss = -100;
+ sinfo.echo_return_loss_enhancement = -100;
+ // These can also be negative, but in practice -1 is only used to signal
+ // insufficient data, since the resolution is limited to multiples of 4 ms.
+ sinfo.echo_delay_median_ms = -1;
+ sinfo.echo_delay_std_ms = -1;
+ if (engine()->voe()->processing()->GetEcMetricsStatus(echo_metrics_on) !=
+ -1 && echo_metrics_on) {
+ // TODO: we may want to use VoECallReport::GetEchoMetricsSummary
+ // here, but it appears to be unsuitable currently. Revisit after this is
+ // investigated: http://b/issue?id=5666755
+ int erl, erle, rerl, anlp;
+ if (engine()->voe()->processing()->GetEchoMetrics(erl, erle, rerl, anlp) !=
+ -1) {
+ sinfo.echo_return_loss = erl;
+ sinfo.echo_return_loss_enhancement = erle;
+ }
+
+ int median, std;
+ if (engine()->voe()->processing()->GetEcDelayMetrics(median, std) != -1) {
+ sinfo.echo_delay_median_ms = median;
+ sinfo.echo_delay_std_ms = std;
+ }
+ }
+
info->senders.push_back(sinfo);
// Build the list of receivers, one for each mux channel, or 1 in a 1:1 call.
@@ -1812,7 +2075,6 @@
engine()->voe()->rtp()->GetRTCPStatistics(*it, cs) != -1 &&
engine()->voe()->codec()->GetRecCodec(*it, codec) != -1) {
VoiceReceiverInfo rinfo;
- memset(&rinfo, 0, sizeof(rinfo));
rinfo.ssrc = ssrc;
rinfo.bytes_rcvd = cs.bytesReceived;
rinfo.packets_rcvd = cs.packetsReceived;
@@ -1899,7 +2161,7 @@
return (ret == 0) ? static_cast<int>(ulevel) : -1;
}
-int WebRtcVoiceMediaChannel::GetChannel(uint32 ssrc) {
+int WebRtcVoiceMediaChannel::GetChannelNum(uint32 ssrc) {
ChannelMap::iterator it = mux_channels_.find(ssrc);
return (it != mux_channels_.end()) ? it->second : -1;
}
diff --git a/talk/session/phone/webrtcvoiceengine.h b/talk/session/phone/webrtcvoiceengine.h
index 66c9785..7cc148a 100644
--- a/talk/session/phone/webrtcvoiceengine.h
+++ b/talk/session/phone/webrtcvoiceengine.h
@@ -41,6 +41,7 @@
#include "talk/session/phone/channel.h"
#include "talk/session/phone/rtputils.h"
#include "talk/session/phone/webrtccommon.h"
+#include "talk/session/phone/webrtcvoe.h"
#ifdef WEBRTC_RELATIVE_PATH
#include "voice_engine/main/interface/voe_base.h"
#else
@@ -76,6 +77,7 @@
class AudioDeviceModule;
class VoETraceWrapper;
class VoEWrapper;
+class VoiceProcessor;
class WebRtcSoundclipMedia;
class WebRtcVoiceMediaChannel;
@@ -83,7 +85,8 @@
// It uses the WebRtc VoiceEngine library for audio handling.
class WebRtcVoiceEngine
: public webrtc::VoiceEngineObserver,
- public webrtc::TraceCallback {
+ public webrtc::TraceCallback,
+ public webrtc::VoEMediaProcess {
public:
WebRtcVoiceEngine();
// Dependency injection for testing.
@@ -112,6 +115,21 @@
void SetLogging(int min_sev, const char* filter);
+ bool RegisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction);
+ bool UnregisterProcessor(uint32 ssrc,
+ VoiceProcessor* voice_processor,
+ MediaProcessorDirection direction);
+
+ // Method from webrtc::VoEMediaProcess
+ virtual void Process(const int channel,
+ const webrtc::ProcessingTypes type,
+ WebRtc_Word16 audio10ms[],
+ const int length,
+ const int sampling_freq,
+ const bool is_stereo);
+
// For tracking WebRtc channels. Needed because we have to pause them
// all when switching devices.
// May only be called by WebRtcVoiceMediaChannel.
@@ -138,6 +156,9 @@
bool SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
webrtc::AudioDeviceModule* adm_sc);
+ // Check whether the supplied trace should be ignored.
+ bool ShouldIgnoreTrace(const std::string& trace);
+
private:
typedef std::vector<WebRtcSoundclipMedia *> SoundclipList;
typedef std::vector<WebRtcVoiceMediaChannel *> ChannelList;
@@ -162,10 +183,19 @@
bool FindChannelAndSsrc(int channel_num,
WebRtcVoiceMediaChannel** channel,
uint32* ssrc) const;
+ bool FindChannelNumFromSsrc(uint32 ssrc,
+ MediaProcessorDirection direction,
+ int* channel_num);
bool ChangeLocalMonitor(bool enable);
bool PauseLocalMonitor();
bool ResumeLocalMonitor();
+ // When a voice processor registers with the engine, it is connected
+ // to either the Rx or Tx signals, based on the direction parameter.
+ // SignalXXMediaFrame will be invoked for every audio packet.
+ sigslot::signal2<uint32, AudioFrame*> SignalRxMediaFrame;
+ sigslot::signal2<uint32, AudioFrame*> SignalTxMediaFrame;
+
static const int kDefaultLogSeverity = talk_base::LS_WARNING;
static const CodecPref kCodecPrefs[];
@@ -190,6 +220,8 @@
talk_base::CriticalSection channels_cs_;
webrtc::AgcConfig default_agc_config_;
bool initialized_;
+
+ talk_base::CriticalSection signal_media_critical_;
};
// WebRtcMediaChannel is a class that implements the common WebRtc channel
@@ -247,8 +279,7 @@
// WebRtcVoiceMediaChannel is an implementation of VoiceMediaChannel that uses
// WebRtc Voice Engine.
class WebRtcVoiceMediaChannel
- : public WebRtcMediaChannel<VoiceMediaChannel,
- WebRtcVoiceEngine> {
+ : public WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine> {
public:
explicit WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine);
virtual ~WebRtcVoiceMediaChannel();
@@ -265,8 +296,10 @@
virtual bool SetSend(SendFlags send);
bool PauseSend();
bool ResumeSend();
- virtual bool AddStream(uint32 ssrc);
- virtual bool RemoveStream(uint32 ssrc);
+ virtual bool AddSendStream(const StreamParams& sp);
+ virtual bool RemoveSendStream(uint32 ssrc);
+ virtual bool AddRecvStream(const StreamParams& sp);
+ virtual bool RemoveRecvStream(uint32 ssrc);
virtual bool GetActiveStreams(AudioInfo::StreamList* actives);
virtual int GetOutputLevel();
virtual bool SetOutputScaling(uint32 ssrc, double left, double right);
@@ -278,8 +311,6 @@
virtual void OnPacketReceived(talk_base::Buffer* packet);
virtual void OnRtcpReceived(talk_base::Buffer* packet);
- virtual void SetSendSsrc(uint32 id);
- virtual bool SetRtcpCName(const std::string& cname);
virtual bool Mute(bool mute);
virtual bool SetSendBandwidth(bool autobw, int bps) { return false; }
virtual bool GetStats(VoiceMediaInfo* info);
@@ -290,9 +321,11 @@
bool FindSsrc(int channel_num, uint32* ssrc);
void OnError(uint32 ssrc, int error);
+ bool sending() const { return send_ != SEND_NOTHING; }
+ int GetChannelNum(uint32 ssrc);
+
protected:
int GetLastEngineError() { return engine()->GetLastEngineError(); }
- int GetChannel(uint32 ssrc);
int GetOutputLevel(int channel);
bool GetRedSendCodec(const AudioCodec& red_codec,
const std::vector<AudioCodec>& all_codecs,
@@ -304,9 +337,9 @@
static Error WebRtcErrorToChannelError(int err_code);
private:
- // Tandberg-bridged conferences require a -10dB gain adjustment,
- // which is actually +10 in AgcConfig.targetLeveldBOv
- static const int kTandbergDbAdjustment = 10;
+ // A -10dB gain adjustment is actually +10 in
+ // AgcConfig.targetLeveldBOv
+ static const int kMinus10DbAdjustment = 10;
bool ChangePlayout(bool playout);
bool ChangeSend(SendFlags send);
@@ -314,6 +347,8 @@
typedef std::map<uint32, int> ChannelMap;
talk_base::scoped_ptr<WebRtcSoundclipStream> ringback_tone_;
std::set<int> ringback_channels_; // channels playing ringback
+ bool recv_codecs_set_;
+ talk_base::scoped_ptr<webrtc::CodecInst> send_codec_;
int channel_options_;
bool agc_adjusted_;
bool dtmf_allowed_;
@@ -321,12 +356,15 @@
bool playout_;
SendFlags desired_send_;
SendFlags send_;
+
+ uint32 local_ssrc_;
ChannelMap mux_channels_; // for multiple sources
// mux_channels_ can be read from WebRtc callback thread. Accesses off the
// WebRtc thread must be synchronized with edits on the worker thread. Reads
// on the worker thread are ok.
mutable talk_base::CriticalSection mux_channels_cs_;
};
-}
+
+} // namespace cricket
#endif // TALK_SESSION_PHONE_WEBRTCVOICEENGINE_H_
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
index f4e437f..f971106 100644
--- a/talk/session/phone/webrtcvoiceengine_unittest.cc
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -4,10 +4,11 @@
#include "talk/base/byteorder.h"
#include "talk/base/gunit.h"
+#include "talk/p2p/base/fakesession.h"
#include "talk/session/phone/channel.h"
#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakemediaprocessor.h"
#include "talk/session/phone/fakertp.h"
-#include "talk/session/phone/fakesession.h"
#include "talk/session/phone/fakewebrtcvoiceengine.h"
#include "talk/session/phone/webrtcvoiceengine.h"
@@ -25,6 +26,7 @@
&kTelephoneEventCodec,
};
const char kRingbackTone[] = "RIFF____WAVE____ABCD1234";
+static uint32 kSsrc1 = 0x99;
class FakeVoEWrapper : public cricket::VoEWrapper {
public:
@@ -57,7 +59,7 @@
}
};
-class WebRtcVoiceEngineTest : public testing::Test {
+class WebRtcVoiceEngineTestFake : public testing::Test {
public:
class ChannelErrorListener : public sigslot::has_slots<> {
public:
@@ -88,7 +90,7 @@
cricket::VoiceMediaChannel::Error error_;
};
- WebRtcVoiceEngineTest()
+ WebRtcVoiceEngineTestFake()
: voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
engine_(new FakeVoEWrapper(&voe_),
@@ -102,6 +104,10 @@
channel_ = engine_.CreateChannel();
result = (channel_ != NULL);
}
+ if (result) {
+ result = channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(kSsrc1));
+ }
return result;
}
void DeliverPacket(const void* data, int len) {
@@ -123,7 +129,7 @@
};
// Tests that our stub library "works".
-TEST_F(WebRtcVoiceEngineTest, StartupShutdown) {
+TEST_F(WebRtcVoiceEngineTestFake, StartupShutdown) {
EXPECT_FALSE(voe_.IsInited());
EXPECT_FALSE(voe_sc_.IsInited());
EXPECT_TRUE(engine_.Init());
@@ -135,14 +141,14 @@
}
// Tests that we can create and destroy a channel.
-TEST_F(WebRtcVoiceEngineTest, CreateChannel) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannel) {
EXPECT_TRUE(engine_.Init());
channel_ = engine_.CreateChannel();
EXPECT_TRUE(channel_ != NULL);
}
// Tests that we properly handle failures in CreateChannel.
-TEST_F(WebRtcVoiceEngineTest, CreateChannelFail) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateChannelFail) {
voe_.set_fail_create_channel(true);
EXPECT_TRUE(engine_.Init());
channel_ = engine_.CreateChannel();
@@ -151,7 +157,7 @@
// Tests that the list of supported codecs is created properly and ordered
// correctly
-TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
+TEST_F(WebRtcVoiceEngineTestFake, CodecPreference) {
const std::vector<cricket::AudioCodec>& codecs = engine_.codecs();
ASSERT_FALSE(codecs.empty());
EXPECT_EQ("ISAC", codecs[0].name);
@@ -166,7 +172,7 @@
// Tests that we can find codecs by name or id, and that we interpret the
// clockrate and bitrate fields properly.
-TEST_F(WebRtcVoiceEngineTest, FindCodec) {
+TEST_F(WebRtcVoiceEngineTestFake, FindCodec) {
cricket::AudioCodec codec;
webrtc::CodecInst codec_inst;
// Find PCMU with explicit clockrate and bitrate.
@@ -201,7 +207,7 @@
}
// Test that we set our inbound codecs properly, including changing PT.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecs) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -226,7 +232,7 @@
}
// Test that we fail to set an unknown inbound codec.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsUnsupportedCodec) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsUnsupportedCodec) {
EXPECT_TRUE(SetupEngine());
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kIsacCodec);
@@ -235,7 +241,7 @@
}
// Test that we fail if we have duplicate types in the inbound list.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsDuplicatePayloadType) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsDuplicatePayloadType) {
EXPECT_TRUE(SetupEngine());
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kIsacCodec);
@@ -245,8 +251,9 @@
}
// Test that changes to recv codecs are applied to all streams.
-TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsWithMultipleStreams) {
EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kIsacCodec);
codecs.push_back(kPcmuCodec);
@@ -254,7 +261,8 @@
codecs[0].id = 106; // collide with existing telephone-event
codecs[2].id = 126;
EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->AddRecvStream(
+ cricket::StreamParams::CreateLegacy(kSsrc1)));
int channel_num2 = voe_.GetLastChannel();
webrtc::CodecInst gcodec;
talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
@@ -270,8 +278,28 @@
EXPECT_STREQ("telephone-event", gcodec.plname);
}
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvCodecsAfterAddingStreams) {
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs[0].id = 106; // collide with existing telephone-event
+
+ EXPECT_TRUE(channel_->AddRecvStream(
+ cricket::StreamParams::CreateLegacy(kSsrc1)));
+ EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+
+ int channel_num2 = voe_.GetLastChannel();
+ webrtc::CodecInst gcodec;
+ talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
+ gcodec.plfreq = 16000;
+ EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num2, gcodec));
+ EXPECT_EQ(106, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+}
+
// Test that we apply codecs properly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecs) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -292,7 +320,7 @@
}
// Test that we handle various ways of specifying bitrate.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBitrate) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBitrate) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -330,7 +358,7 @@
}
// Test that we fall back to PCMU if no codecs are specified.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsNoCodecs) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsNoCodecs) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -347,7 +375,7 @@
}
// Test that we set VAD and DTMF types correctly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCNandDTMF) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCNandDTMF) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -374,7 +402,7 @@
}
// Test that we perform case-insensitive matching of codec names.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCaseInsensitive) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsCaseInsensitive) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -401,7 +429,7 @@
}
// Test that we set up FEC correctly.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsRED) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsRED) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -421,7 +449,7 @@
}
// Test that we set up FEC correctly if params are omitted.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsREDNoParams) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsREDNoParams) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -440,7 +468,7 @@
}
// Test that we ignore RED if the parameters aren't named the way we expect.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED1) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED1) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -459,7 +487,7 @@
}
// Test that we ignore RED if it uses different primary/secondary encoding.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED2) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED2) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -478,7 +506,7 @@
}
// Test that we ignore RED if it uses more than 2 encodings.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED3) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED3) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -497,7 +525,7 @@
}
// Test that we ignore RED if it has bogus codec ids.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED4) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED4) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -516,7 +544,7 @@
}
// Test that we ignore RED if it refers to a codec that is not present.
-TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED5) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecsBadRED5) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -535,7 +563,7 @@
}
// Test that we support setting an empty list of recv header extensions.
-TEST_F(WebRtcVoiceEngineTest, SetRecvRtpHeaderExtensions) {
+TEST_F(WebRtcVoiceEngineTestFake, SetRecvRtpHeaderExtensions) {
EXPECT_TRUE(SetupEngine());
std::vector<cricket::RtpHeaderExtension> extensions;
int channel_num = voe_.GetLastChannel();
@@ -558,7 +586,7 @@
}
// Test that we support setting certain send header extensions.
-TEST_F(WebRtcVoiceEngineTest, SetSendRtpHeaderExtensions) {
+TEST_F(WebRtcVoiceEngineTestFake, SetSendRtpHeaderExtensions) {
EXPECT_TRUE(SetupEngine());
std::vector<cricket::RtpHeaderExtension> extensions;
int channel_num = voe_.GetLastChannel();
@@ -594,7 +622,7 @@
}
// Test that we can create a channel and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, SendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, SendAndPlayout) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -612,16 +640,17 @@
// Test that we can add and remove streams, and do proper send/playout.
// We can receive on multiple streams, but will only send on one.
-TEST_F(WebRtcVoiceEngineTest, SendAndPlayoutWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, SendAndPlayoutWithMultipleStreams) {
EXPECT_TRUE(SetupEngine());
int channel_num1 = voe_.GetLastChannel();
// Start playout on the default channel.
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
EXPECT_TRUE(channel_->SetPlayout(true));
EXPECT_TRUE(voe_.GetPlayout(channel_num1));
// Adding another stream should disable playout on the default channel.
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
int channel_num2 = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kPcmuCodec);
@@ -635,7 +664,7 @@
EXPECT_TRUE(voe_.GetPlayout(channel_num2));
// Adding yet another stream should have stream 2 and 3 enabled for playout.
- EXPECT_TRUE(channel_->AddStream(3));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
int channel_num3 = voe_.GetLastChannel();
EXPECT_FALSE(voe_.GetPlayout(channel_num1));
EXPECT_TRUE(voe_.GetPlayout(channel_num2));
@@ -662,14 +691,14 @@
// Now remove the new streams and verify that the default channel is
// played out again.
- EXPECT_TRUE(channel_->RemoveStream(3));
- EXPECT_TRUE(channel_->RemoveStream(2));
+ EXPECT_TRUE(channel_->RemoveRecvStream(3));
+ EXPECT_TRUE(channel_->RemoveRecvStream(2));
EXPECT_TRUE(voe_.GetPlayout(channel_num1));
}
// Test that we can set the devices to use.
-TEST_F(WebRtcVoiceEngineTest, SetDevices) {
+TEST_F(WebRtcVoiceEngineTestFake, SetDevices) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -723,7 +752,7 @@
// Test that we can set the devices to use even if we failed to
// open the initial ones.
-TEST_F(WebRtcVoiceEngineTest, SetDevicesWithInitiallyBadDevices) {
+TEST_F(WebRtcVoiceEngineTestFake, SetDevicesWithInitiallyBadDevices) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
std::vector<cricket::AudioCodec> codecs;
@@ -763,7 +792,7 @@
// Test that we can create a channel configured for multi-point conferences,
// and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, ConferenceSendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, ConferenceSendAndPlayout) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
@@ -806,13 +835,13 @@
// Test that we can create a channel configured for Codian bridges,
// and start sending/playing out on it.
-TEST_F(WebRtcVoiceEngineTest, CodianSendAndPlayout) {
+TEST_F(WebRtcVoiceEngineTestFake, CodianSendAndPlayout) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
webrtc::AgcConfig agc_config;
EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
EXPECT_EQ(0, agc_config.targetLeveldBOv);
- EXPECT_TRUE(channel_->SetOptions(cricket::OPT_AGC_TANDBERG_LEVELS));
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_AGC_MINUS_10DB));
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kPcmuCodec);
EXPECT_TRUE(channel_->SetSendCodecs(codecs));
@@ -831,48 +860,72 @@
}
// Test that we can set the outgoing SSRC properly.
-TEST_F(WebRtcVoiceEngineTest, SetSendSsrc) {
+// SSRC is set in SetupEngine by calling AddSendStream.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrc) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
unsigned int send_ssrc;
EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
EXPECT_NE(0U, send_ssrc);
- channel_->SetSendSsrc(0x99);
EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
- EXPECT_EQ(0x99U, send_ssrc);
+ EXPECT_EQ(kSsrc1, send_ssrc);
}
// Test that we can set the outgoing SSRC properly with multiple streams.
-TEST_F(WebRtcVoiceEngineTest, SetSendSsrcWithMultipleStreams) {
+// SSRC is set in SetupEngine by calling AddSendStream.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcWithMultipleStreams) {
EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
int channel_num1 = voe_.GetLastChannel();
unsigned int send_ssrc;
- channel_->SetSendSsrc(0x99);
EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
- EXPECT_EQ(0x99U, send_ssrc);
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_EQ(kSsrc1, send_ssrc);
+
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
int channel_num2 = voe_.GetLastChannel();
EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num2, send_ssrc));
- EXPECT_EQ(0x99U, send_ssrc);
+ EXPECT_EQ(kSsrc1, send_ssrc);
+}
+
+// Test that the local SSRC is the same on sending and receiving channels if the
+// receive channel is created before the send channel.
+TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) {
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel();
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ int receive_channel_num = voe_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddSendStream(
+ cricket::StreamParams::CreateLegacy(1234)));
+ int send_channel_num = voe_.GetLastChannel();
+
+ unsigned int ssrc = 0;
+ EXPECT_EQ(0, voe_.GetLocalSSRC(send_channel_num, ssrc));
+ EXPECT_EQ(1234U, ssrc);
+ ssrc = 0;
+ EXPECT_EQ(0, voe_.GetLocalSSRC(receive_channel_num, ssrc));
+ EXPECT_EQ(1234U, ssrc);
}
// Test that we can properly receive packets.
-TEST_F(WebRtcVoiceEngineTest, Recv) {
+TEST_F(WebRtcVoiceEngineTestFake, Recv) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
EXPECT_TRUE(voe_.CheckPacket(channel_num, kPcmuFrame,
- sizeof(kPcmuFrame)));
+ sizeof(kPcmuFrame)));
}
// Test that we can properly receive packets on multiple streams.
-TEST_F(WebRtcVoiceEngineTest, RecvWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, RecvWithMultipleStreams) {
EXPECT_TRUE(SetupEngine());
- EXPECT_TRUE(channel_->AddStream(1));
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
int channel_num1 = voe_.GetLastChannel();
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
int channel_num2 = voe_.GetLastChannel();
- EXPECT_TRUE(channel_->AddStream(3));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
int channel_num3 = voe_.GetLastChannel();
// Create packets with the right SSRCs.
char packets[4][sizeof(kPcmuFrame)];
@@ -889,37 +942,51 @@
EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
DeliverPacket(packets[1], sizeof(packets[1]));
EXPECT_TRUE(voe_.CheckPacket(channel_num1, packets[1],
- sizeof(packets[1])));
+ sizeof(packets[1])));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
DeliverPacket(packets[2], sizeof(packets[2]));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
EXPECT_TRUE(voe_.CheckPacket(channel_num2, packets[2],
- sizeof(packets[2])));
+ sizeof(packets[2])));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
DeliverPacket(packets[3], sizeof(packets[3]));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
EXPECT_TRUE(voe_.CheckPacket(channel_num3, packets[3],
- sizeof(packets[3])));
- EXPECT_TRUE(channel_->RemoveStream(3));
- EXPECT_TRUE(channel_->RemoveStream(2));
- EXPECT_TRUE(channel_->RemoveStream(1));
+ sizeof(packets[3])));
+ EXPECT_TRUE(channel_->RemoveRecvStream(3));
+ EXPECT_TRUE(channel_->RemoveRecvStream(2));
+ EXPECT_TRUE(channel_->RemoveRecvStream(1));
}
// Test that we properly handle failures to add a stream.
-TEST_F(WebRtcVoiceEngineTest, AddStreamFail) {
+TEST_F(WebRtcVoiceEngineTestFake, AddStreamFail) {
EXPECT_TRUE(SetupEngine());
voe_.set_fail_create_channel(true);
- EXPECT_FALSE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+
+ // In 1:1 call, we should not try to create a new channel.
+ EXPECT_TRUE(channel_->SetOptions(0));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+}
+
+// Test that AddRecvStream doesn't create new channel for 1:1 call.
+TEST_F(WebRtcVoiceEngineTestFake, AddRecvStream1On1) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_EQ(channel_num, voe_.GetLastChannel());
}
// Test that we properly clean up any streams that were added, even if
// not explicitly removed.
-TEST_F(WebRtcVoiceEngineTest, StreamCleanup) {
+TEST_F(WebRtcVoiceEngineTestFake, StreamCleanup) {
EXPECT_TRUE(SetupEngine());
- EXPECT_TRUE(channel_->AddStream(1));
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
EXPECT_EQ(3, voe_.GetNumChannels()); // default channel + 2 added
delete channel_;
channel_ = NULL;
@@ -928,7 +995,7 @@
// Test that we can send DTMF properly, but only if the other side supports
// telephone-event.
-TEST_F(WebRtcVoiceEngineTest, SendDtmf) {
+TEST_F(WebRtcVoiceEngineTestFake, SendDtmf) {
EXPECT_TRUE(SetupEngine());
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kPcmuCodec);
@@ -941,7 +1008,7 @@
}
// Test that we can play a ringback tone properly in a single-stream call.
-TEST_F(WebRtcVoiceEngineTest, PlayRingback) {
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingback) {
EXPECT_TRUE(SetupEngine());
int channel_num = voe_.GetLastChannel();
EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
@@ -963,10 +1030,11 @@
}
// Test that we can play a ringback tone properly in a multi-stream call.
-TEST_F(WebRtcVoiceEngineTest, PlayRingbackWithMultipleStreams) {
+TEST_F(WebRtcVoiceEngineTestFake, PlayRingbackWithMultipleStreams) {
EXPECT_TRUE(SetupEngine());
- EXPECT_TRUE(channel_->AddStream(1));
- EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
int channel_num = voe_.GetLastChannel();
EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
// Check we fail if no ringback tone specified.
@@ -995,7 +1063,7 @@
}
// Tests creating soundclips, and make sure they come from the right engine.
-TEST_F(WebRtcVoiceEngineTest, CreateSoundclip) {
+TEST_F(WebRtcVoiceEngineTestFake, CreateSoundclip) {
EXPECT_TRUE(engine_.Init());
soundclip_ = engine_.CreateSoundclip();
ASSERT_TRUE(soundclip_ != NULL);
@@ -1009,7 +1077,7 @@
}
// Tests playing out a fake sound.
-TEST_F(WebRtcVoiceEngineTest, PlaySoundclip) {
+TEST_F(WebRtcVoiceEngineTestFake, PlaySoundclip) {
static const char kZeroes[16000] = {};
EXPECT_TRUE(engine_.Init());
soundclip_ = engine_.CreateSoundclip();
@@ -1017,12 +1085,13 @@
EXPECT_TRUE(soundclip_->PlaySound(kZeroes, sizeof(kZeroes), 0));
}
-TEST_F(WebRtcVoiceEngineTest, MediaEngineCallbackOnError) {
+TEST_F(WebRtcVoiceEngineTestFake, MediaEngineCallbackOnError) {
talk_base::scoped_ptr<ChannelErrorListener> listener;
cricket::WebRtcVoiceMediaChannel* media_channel;
unsigned int ssrc = 0;
EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
media_channel = reinterpret_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
@@ -1030,7 +1099,7 @@
// Test on WebRtc VoE channel.
voe_.TriggerCallbackOnError(media_channel->voe_channel(),
- VE_SATURATION_WARNING);
+ VE_SATURATION_WARNING);
EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
listener->error());
EXPECT_NE(-1, voe_.GetLocalSSRC(voe_.GetLastChannel(), ssrc));
@@ -1044,10 +1113,11 @@
// Add another stream and test on that.
++ssrc;
- EXPECT_TRUE(channel_->AddStream(ssrc));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(
+ ssrc)));
listener->Reset();
voe_.TriggerCallbackOnError(voe_.GetLastChannel(),
- VE_SATURATION_WARNING);
+ VE_SATURATION_WARNING);
EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
listener->error());
EXPECT_EQ(ssrc, listener->ssrc());
@@ -1055,28 +1125,103 @@
// Testing a non-existing channel.
listener->Reset();
voe_.TriggerCallbackOnError(voe_.GetLastChannel() + 2,
- VE_SATURATION_WARNING);
+ VE_SATURATION_WARNING);
EXPECT_EQ(0, listener->error());
}
-TEST_F(WebRtcVoiceEngineTest, TestSetPlayoutError) {
+TEST_F(WebRtcVoiceEngineTestFake, TestSetPlayoutError) {
EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kPcmuCodec);
EXPECT_TRUE(channel_->SetSendCodecs(codecs));
EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
- EXPECT_TRUE(channel_->AddStream(2));
- EXPECT_TRUE(channel_->AddStream(3));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(3)));
EXPECT_TRUE(channel_->SetPlayout(true));
voe_.set_playout_fail_channel(voe_.GetLastChannel() - 1);
EXPECT_TRUE(channel_->SetPlayout(false));
EXPECT_FALSE(channel_->SetPlayout(true));
}
+// Test that the Registering/Unregistering with the
+// webrtcvoiceengine works as expected
+TEST_F(WebRtcVoiceEngineTestFake, RegisterVoiceProcessor) {
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ EXPECT_TRUE(channel_->AddRecvStream(
+ cricket::StreamParams::CreateLegacy(kSsrc1)));
+ uint32 ssrc = 0;
+ voe_.GetLocalSSRC(0, ssrc);
+ cricket::FakeMediaProcessor vp_1;
+ cricket::FakeMediaProcessor vp_2;
+
+ EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_RX));
+ EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_2, cricket::MPD_RX));
+ voe_.TriggerProcessPacket(cricket::MPD_RX);
+ voe_.TriggerProcessPacket(cricket::MPD_TX);
+
+ EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+ EXPECT_EQ(1, vp_1.voice_frame_count());
+ EXPECT_EQ(1, vp_2.voice_frame_count());
+
+ EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+ &vp_2,
+ cricket::MPD_RX));
+ voe_.TriggerProcessPacket(cricket::MPD_RX);
+ EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+ EXPECT_EQ(1, vp_2.voice_frame_count());
+ EXPECT_EQ(2, vp_1.voice_frame_count());
+
+ EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+ &vp_1,
+ cricket::MPD_RX));
+ voe_.TriggerProcessPacket(cricket::MPD_RX);
+ EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+ EXPECT_EQ(2, vp_1.voice_frame_count());
+
+ EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_TX));
+ voe_.TriggerProcessPacket(cricket::MPD_RX);
+ voe_.TriggerProcessPacket(cricket::MPD_TX);
+ EXPECT_TRUE(voe_.IsExternalMediaProcessorRegistered());
+ EXPECT_EQ(3, vp_1.voice_frame_count());
+
+ EXPECT_TRUE(engine_.UnregisterProcessor(ssrc,
+ &vp_1,
+ cricket::MPD_RX_AND_TX));
+ voe_.TriggerProcessPacket(cricket::MPD_TX);
+ EXPECT_FALSE(voe_.IsExternalMediaProcessorRegistered());
+ EXPECT_EQ(3, vp_1.voice_frame_count());
+ EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc1));
+
+ // Test that after removing the recvstream we can we can still register
+ // the processor. This tests the 1:1 case.
+ EXPECT_TRUE(engine_.RegisterProcessor(ssrc, &vp_1, cricket::MPD_RX));
+ EXPECT_TRUE(engine_.UnregisterProcessor(ssrc, &vp_1, cricket::MPD_RX_AND_TX));
+
+ // The following tests test that FindChannelNumFromSsrc is doing
+ // what we expect.
+ // pick an invalid ssrc and make sure we can't register
+ EXPECT_FALSE(engine_.RegisterProcessor(0,
+ &vp_1,
+ cricket::MPD_RX));
+ EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+ EXPECT_TRUE(engine_.RegisterProcessor(1,
+ &vp_1,
+ cricket::MPD_RX));
+ EXPECT_TRUE(engine_.UnregisterProcessor(1,
+ &vp_1,
+ cricket::MPD_RX));
+ EXPECT_FALSE(engine_.RegisterProcessor(1,
+ &vp_1,
+ cricket::MPD_TX));
+ EXPECT_TRUE(channel_->RemoveRecvStream(1));
+}
+
// Tests for the actual WebRtc VoE library.
// Tests that the library initializes and shuts down properly.
-TEST(WebRtcVoiceEngineLibTest, StartupShutdown) {
+TEST(WebRtcVoiceEngineTest, StartupShutdown) {
cricket::WebRtcVoiceEngine engine;
EXPECT_TRUE(engine.Init());
cricket::VoiceMediaChannel* channel = engine.CreateChannel();
@@ -1090,9 +1235,7 @@
}
// Tests that the logging from the library is cleartext.
-// TODO: This test case is disabled due to a known bug in VoE4.0
-// which sends out truncated log message. Will be fixed in next VoE release.
-TEST(WebRtcVoiceEngineLibTest, DISABLED_HasUnencryptedLogging) {
+TEST(WebRtcVoiceEngineTest, DISABLED_HasUnencryptedLogging) {
cricket::WebRtcVoiceEngine engine;
talk_base::scoped_ptr<talk_base::MemoryStream> stream(
new talk_base::MemoryStream);
@@ -1117,7 +1260,7 @@
// Tests we do not see any references to a monitor thread being spun up
// when initiating the engine.
-TEST(WebRtcVoiceEngineLibTest, HasNoMonitorThread) {
+TEST(WebRtcVoiceEngineTest, HasNoMonitorThread) {
cricket::WebRtcVoiceEngine engine;
talk_base::scoped_ptr<talk_base::MemoryStream> stream(
new talk_base::MemoryStream);
@@ -1135,7 +1278,7 @@
}
// Tests that the library is configured with the codecs we want.
-TEST(WebRtcVoiceEngineLibTest, HasCorrectCodecs) {
+TEST(WebRtcVoiceEngineTest, HasCorrectCodecs) {
cricket::WebRtcVoiceEngine engine;
// Check codecs by name.
EXPECT_TRUE(engine.FindCodec(
@@ -1211,7 +1354,7 @@
}
// Tests that VoE supports at least 32 channels
-TEST(WebRtcVoiceEngineLibTest, Has32Channels) {
+TEST(WebRtcVoiceEngineTest, Has32Channels) {
cricket::WebRtcVoiceEngine engine;
EXPECT_TRUE(engine.Init());
@@ -1238,7 +1381,7 @@
#ifdef WIN32
// Test our workarounds to WebRtc VoE' munging of the coinit count
-TEST(WebRtcVoiceEngineLibTest, CoInitialize) {
+TEST(WebRtcVoiceEngineTest, CoInitialize) {
cricket::WebRtcVoiceEngine* engine = new cricket::WebRtcVoiceEngine();
// Initial refcount should be 0.
diff --git a/talk/session/phone/win32devicemanager.cc b/talk/session/phone/win32devicemanager.cc
new file mode 100644
index 0000000..f8a928e
--- /dev/null
+++ b/talk/session/phone/win32devicemanager.cc
@@ -0,0 +1,405 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/win32devicemanager.h"
+
+#include <atlbase.h>
+#include <dbt.h>
+#include <strmif.h> // must come before ks.h
+#include <ks.h>
+#include <ksmedia.h>
+#define INITGUID // For PKEY_AudioEndpoint_GUID
+#include <mmdeviceapi.h>
+#include <mmsystem.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <uuids.h>
+
+#include "talk/base/win32.h" // ToUtf8
+#include "talk/base/win32window.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/mediacommon.h"
+#ifdef HAVE_LOGITECH_HEADERS
+#include "third_party/logitech/files/logitechquickcam.h"
+#endif
+
+namespace cricket {
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+ return new Win32DeviceManager();
+}
+
+class Win32DeviceWatcher
+ : public DeviceWatcher,
+ public talk_base::Win32Window {
+ public:
+ explicit Win32DeviceWatcher(Win32DeviceManager* dm);
+ virtual ~Win32DeviceWatcher();
+ virtual bool Start();
+ virtual void Stop();
+
+ private:
+ HDEVNOTIFY Register(REFGUID guid);
+ void Unregister(HDEVNOTIFY notify);
+ virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+
+ Win32DeviceManager* manager_;
+ HDEVNOTIFY audio_notify_;
+ HDEVNOTIFY video_notify_;
+};
+
+static const char* kFilteredAudioDevicesName[] = {
+ NULL,
+};
+static const char* const kFilteredVideoDevicesName[] = {
+ "Google Camera Adapter", // Our own magiccams
+ "Asus virtual Camera", // Bad Asus desktop virtual cam
+ "Bluetooth Video", // Bad Sony viao bluetooth sharing driver
+ NULL,
+};
+static const wchar_t kFriendlyName[] = L"FriendlyName";
+static const wchar_t kDevicePath[] = L"DevicePath";
+static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
+static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
+static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
+static bool GetWaveDevices(bool input, std::vector<Device>* devs);
+
+Win32DeviceManager::Win32DeviceManager()
+ : need_couninitialize_(false) {
+ set_watcher(new Win32DeviceWatcher(this));
+}
+
+Win32DeviceManager::~Win32DeviceManager() {
+ if (initialized()) {
+ Terminate();
+ }
+}
+
+bool Win32DeviceManager::Init() {
+ if (!initialized()) {
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ need_couninitialize_ = SUCCEEDED(hr);
+ if (FAILED(hr)) {
+ LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+ if (hr != RPC_E_CHANGED_MODE) {
+ return false;
+ }
+ }
+ if (!watcher()->Start()) {
+ return false;
+ }
+ set_initialized(true);
+ }
+ return true;
+}
+
+void Win32DeviceManager::Terminate() {
+ if (initialized()) {
+ watcher()->Stop();
+ if (need_couninitialize_) {
+ CoUninitialize();
+ need_couninitialize_ = false;
+ }
+ set_initialized(false);
+ }
+}
+
+bool Win32DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+ bool ret = false;
+ // If there are multiple capture devices, we want the first USB one.
+ // This avoids issues with defaulting to virtual cameras or grabber cards.
+ std::vector<Device> devices;
+ ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
+ if (ret) {
+ *device = devices[0];
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
+ ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
+ *device = devices[i];
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+bool Win32DeviceManager::GetAudioDevices(bool input,
+ std::vector<Device>* devs) {
+ devs->clear();
+
+ if (talk_base::IsWindowsVistaOrLater()) {
+ if (!GetCoreAudioDevices(input, devs))
+ return false;
+ } else {
+ if (!GetWaveDevices(input, devs))
+ return false;
+ }
+ return FilterDevices(devs, kFilteredAudioDevicesName);
+}
+
+bool Win32DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+ devices->clear();
+ if (!GetDevices(CLSID_VideoInputDeviceCategory, devices)) {
+ return false;
+ }
+ return FilterDevices(devices, kFilteredVideoDevicesName);
+}
+
+bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
+ HRESULT hr;
+
+ // CComPtr is a scoped pointer that will be auto released when going
+ // out of scope. CoUninitialize must not be called before the
+ // release.
+ CComPtr<ICreateDevEnum> sys_dev_enum;
+ CComPtr<IEnumMoniker> cam_enum;
+ if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
+ FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
+ LOG(LS_ERROR) << "Failed to create device enumerator, hr=" << hr;
+ return false;
+ }
+
+ // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
+ // devices available, S_FALSE will be returned, but enumMk will be NULL.
+ if (hr == S_OK) {
+ CComPtr<IMoniker> mk;
+ while (cam_enum->Next(1, &mk, NULL) == S_OK) {
+#ifdef HAVE_LOGITECH_HEADERS
+ // Initialize Logitech device if applicable
+ MaybeLogitechDeviceReset(mk);
+#endif
+ CComPtr<IPropertyBag> bag;
+ if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
+ __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
+ CComVariant name, path;
+ std::string name_str, path_str;
+ if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
+ name.vt == VT_BSTR) {
+ name_str = talk_base::ToUtf8(name.bstrVal);
+ // Get the device id if one exists.
+ if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
+ path.vt == VT_BSTR) {
+ path_str = talk_base::ToUtf8(path.bstrVal);
+ }
+
+ devices->push_back(Device(name_str, path_str));
+ }
+ }
+ mk = NULL;
+ }
+ }
+
+ return true;
+}
+
+HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
+ out->clear();
+ PROPVARIANT var;
+ PropVariantInit(&var);
+
+ HRESULT hr = bag->GetValue(key, &var);
+ if (SUCCEEDED(hr)) {
+ if (var.pwszVal)
+ *out = talk_base::ToUtf8(var.pwszVal);
+ else
+ hr = E_FAIL;
+ }
+
+ PropVariantClear(&var);
+ return hr;
+}
+
+// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
+HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
+ CComPtr<IPropertyStore> props;
+
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Get the endpoint's name and id.
+ std::string name, guid;
+ hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
+ if (SUCCEEDED(hr)) {
+ hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
+
+ if (SUCCEEDED(hr)) {
+ out->name = name;
+ out->id = guid;
+ }
+ }
+ return hr;
+}
+
+bool GetCoreAudioDevices(
+ bool input, std::vector<Device>* devs) {
+ HRESULT hr = S_OK;
+ CComPtr<IMMDeviceEnumerator> enumerator;
+
+ hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
+ if (SUCCEEDED(hr)) {
+ CComPtr<IMMDeviceCollection> devices;
+ hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
+ DEVICE_STATE_ACTIVE, &devices);
+ if (SUCCEEDED(hr)) {
+ unsigned int count;
+ hr = devices->GetCount(&count);
+
+ if (SUCCEEDED(hr)) {
+ for (unsigned int i = 0; i < count; i++) {
+ CComPtr<IMMDevice> device;
+
+ // Get pointer to endpoint number i.
+ hr = devices->Item(i, &device);
+ if (FAILED(hr)) {
+ break;
+ }
+
+ Device dev;
+ hr = CricketDeviceFromImmDevice(device, &dev);
+ if (SUCCEEDED(hr)) {
+ devs->push_back(dev);
+ } else {
+ LOG(LS_WARNING) << "Unable to query IMM Device, skipping. HR="
+ << hr;
+ hr = S_FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ if (FAILED(hr)) {
+ LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
+ return false;
+ }
+ return true;
+}
+
+bool GetWaveDevices(bool input, std::vector<Device>* devs) {
+ // Note, we don't use the System Device Enumerator interface here since it
+ // adds lots of pseudo-devices to the list, such as DirectSound and Wave
+ // variants of the same device.
+ if (input) {
+ int num_devs = waveInGetNumDevs();
+ for (int i = 0; i < num_devs; ++i) {
+ WAVEINCAPS caps;
+ if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+ caps.wChannels > 0) {
+ devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
+ talk_base::ToString(i)));
+ }
+ }
+ } else {
+ int num_devs = waveOutGetNumDevs();
+ for (int i = 0; i < num_devs; ++i) {
+ WAVEOUTCAPS caps;
+ if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+ caps.wChannels > 0) {
+ devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
+ }
+ }
+ }
+ return true;
+}
+
+Win32DeviceWatcher::Win32DeviceWatcher(Win32DeviceManager* manager)
+ : DeviceWatcher(manager),
+ manager_(manager),
+ audio_notify_(NULL),
+ video_notify_(NULL) {
+}
+
+Win32DeviceWatcher::~Win32DeviceWatcher() {
+}
+
+bool Win32DeviceWatcher::Start() {
+ if (!Create(NULL, _T("libjingle Win32DeviceWatcher Window"),
+ 0, 0, 0, 0, 0, 0)) {
+ return false;
+ }
+
+ audio_notify_ = Register(KSCATEGORY_AUDIO);
+ if (!audio_notify_) {
+ Stop();
+ return false;
+ }
+
+ video_notify_ = Register(KSCATEGORY_VIDEO);
+ if (!video_notify_) {
+ Stop();
+ return false;
+ }
+
+ return true;
+}
+
+void Win32DeviceWatcher::Stop() {
+ UnregisterDeviceNotification(video_notify_);
+ video_notify_ = NULL;
+ UnregisterDeviceNotification(audio_notify_);
+ audio_notify_ = NULL;
+ Destroy();
+}
+
+HDEVNOTIFY Win32DeviceWatcher::Register(REFGUID guid) {
+ DEV_BROADCAST_DEVICEINTERFACE dbdi;
+ dbdi.dbcc_size = sizeof(dbdi);
+ dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ dbdi.dbcc_classguid = guid;
+ dbdi.dbcc_name[0] = '\0';
+ return RegisterDeviceNotification(handle(), &dbdi,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void Win32DeviceWatcher::Unregister(HDEVNOTIFY handle) {
+ UnregisterDeviceNotification(handle);
+}
+
+bool Win32DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result) {
+ if (uMsg == WM_DEVICECHANGE) {
+ if (wParam == DBT_DEVICEARRIVAL ||
+ wParam == DBT_DEVICEREMOVECOMPLETE) {
+ DEV_BROADCAST_DEVICEINTERFACE* dbdi =
+ reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
+ if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
+ dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
+ manager_->SignalDevicesChange();
+ }
+ }
+ result = 0;
+ return true;
+ }
+
+ return false;
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/win32devicemanager.h b/talk/session/phone/win32devicemanager.h
new file mode 100644
index 0000000..98e73c1
--- /dev/null
+++ b/talk/session/phone/win32devicemanager.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2004 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class Win32DeviceManager : public DeviceManager {
+ public:
+ Win32DeviceManager();
+ virtual ~Win32DeviceManager();
+
+ // Initialization
+ virtual bool Init();
+ virtual void Terminate();
+
+ virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
+
+ private:
+ virtual bool GetAudioDevices(bool input, std::vector<Device>* devs);
+ virtual bool GetDefaultVideoCaptureDevice(Device* device);
+
+ bool need_couninitialize_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_WIN32DEVICEMANAGER_H_
diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc
index 84ec5e0..71f38aa 100644
--- a/talk/session/tunnel/securetunnelsessionclient.cc
+++ b/talk/session/tunnel/securetunnelsessionclient.cc
@@ -44,15 +44,16 @@
// XML elements and namespaces for XMPP stanzas used in content exchanges.
-const std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
-const buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
- "description");
-const buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
-const buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
- "client-cert");
-const buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
- "server-cert");
-const std::string CN_SECURE_TUNNEL("securetunnel");
+const char NS_SECURE_TUNNEL[] = "http://www.google.com/talk/securetunnel";
+const buzz::StaticQName QN_SECURE_TUNNEL_DESCRIPTION =
+ { NS_SECURE_TUNNEL, "description" };
+const buzz::StaticQName QN_SECURE_TUNNEL_TYPE =
+ { NS_SECURE_TUNNEL, "type" };
+const buzz::StaticQName QN_SECURE_TUNNEL_CLIENT_CERT =
+ { NS_SECURE_TUNNEL, "client-cert" };
+const buzz::StaticQName QN_SECURE_TUNNEL_SERVER_CERT =
+ { NS_SECURE_TUNNEL, "server-cert" };
+const char CN_SECURE_TUNNEL[] = "securetunnel";
// SecureTunnelContentDescription
diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc
index 13d5c10..05cc757 100644
--- a/talk/session/tunnel/tunnelsessionclient.cc
+++ b/talk/session/tunnel/tunnelsessionclient.cc
@@ -39,10 +39,10 @@
namespace cricket {
-const std::string NS_TUNNEL("http://www.google.com/talk/tunnel");
-const buzz::QName QN_TUNNEL_DESCRIPTION(NS_TUNNEL, "description");
-const buzz::QName QN_TUNNEL_TYPE(NS_TUNNEL, "type");
-const std::string CN_TUNNEL("tunnel");
+const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel";
+const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" };
+const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" };
+const char CN_TUNNEL[] = "tunnel";
enum {
MSG_CLOCK = 1,
diff --git a/talk/session/tunnel/tunnelsessionclient_unittest.cc b/talk/session/tunnel/tunnelsessionclient_unittest.cc
new file mode 100644
index 0000000..7ead1bf
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient_unittest.cc
@@ -0,0 +1,235 @@
+/*
+ * libjingle
+ * Copyright 2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/gunit.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+#include "talk/base/timeutils.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/client/fakeportallocator.h"
+#include "talk/session/tunnel/tunnelsessionclient.h"
+
+static const int kTimeoutMs = 10000;
+static const int kBlockSize = 4096;
+static const buzz::Jid kLocalJid("local@localhost");
+static const buzz::Jid kRemoteJid("remote@localhost");
+
+// This test fixture creates the necessary plumbing to create and run
+// two TunnelSessionClients that talk to each other.
+class TunnelSessionClientTest : public testing::Test,
+ public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ TunnelSessionClientTest()
+ : local_pa_(talk_base::Thread::Current(), NULL),
+ remote_pa_(talk_base::Thread::Current(), NULL),
+ local_sm_(&local_pa_, talk_base::Thread::Current()),
+ remote_sm_(&remote_pa_, talk_base::Thread::Current()),
+ local_client_(kLocalJid, &local_sm_),
+ remote_client_(kRemoteJid, &remote_sm_),
+ done_(false) {
+ local_sm_.SignalSessionCreate.connect(this,
+ &TunnelSessionClientTest::OnSessionCreate);
+ local_sm_.SignalRequestSignaling.connect(this,
+ &TunnelSessionClientTest::OnLocalRequestSignaling);
+ local_sm_.SignalOutgoingMessage.connect(this,
+ &TunnelSessionClientTest::OnOutgoingMessage);
+ remote_sm_.SignalSessionCreate.connect(this,
+ &TunnelSessionClientTest::OnSessionCreate);
+ remote_sm_.SignalRequestSignaling.connect(this,
+ &TunnelSessionClientTest::OnRemoteRequestSignaling);
+ remote_sm_.SignalOutgoingMessage.connect(this,
+ &TunnelSessionClientTest::OnOutgoingMessage);
+ remote_client_.SignalIncomingTunnel.connect(this,
+ &TunnelSessionClientTest::OnIncomingTunnel);
+ }
+
+ // Transfer the desired amount of data from the local to the remote client.
+ void TestTransfer(int size) {
+ // Create some dummy data to send.
+ send_stream_.ReserveSize(size);
+ for (int i = 0; i < size; ++i) {
+ char ch = static_cast<char>(i);
+ send_stream_.Write(&ch, 1, NULL, NULL);
+ }
+ send_stream_.Rewind();
+ // Prepare the receive stream.
+ recv_stream_.ReserveSize(size);
+ // Create the tunnel and set things in motion.
+ local_tunnel_.reset(local_client_.CreateTunnel(kRemoteJid, "test"));
+ local_tunnel_->SignalEvent.connect(this,
+ &TunnelSessionClientTest::OnStreamEvent);
+ EXPECT_TRUE_WAIT(done_, kTimeoutMs);
+ // Make sure we received the right data.
+ EXPECT_EQ(0, memcmp(send_stream_.GetBuffer(),
+ recv_stream_.GetBuffer(), size));
+ }
+
+ private:
+ enum { MSG_LSIGNAL, MSG_RSIGNAL };
+
+ // Use this callback to allow local ips for this test.
+ void OnSessionCreate(cricket::Session* session, bool incoming) {
+ session->set_allow_local_ips(true);
+ }
+
+ // There's no SessionManager* argument in this callback, so we need 2 of them.
+ void OnLocalRequestSignaling() {
+ local_sm_.OnSignalingReady();
+ }
+ void OnRemoteRequestSignaling() {
+ remote_sm_.OnSignalingReady();
+ }
+
+ // Post a message, to avoid problems with directly connecting the callbacks.
+ void OnOutgoingMessage(cricket::SessionManager* manager,
+ const buzz::XmlElement* stanza) {
+ if (manager == &local_sm_) {
+ talk_base::Thread::Current()->Post(this, MSG_LSIGNAL,
+ talk_base::WrapMessageData(*stanza));
+ } else if (manager == &remote_sm_) {
+ talk_base::Thread::Current()->Post(this, MSG_RSIGNAL,
+ talk_base::WrapMessageData(*stanza));
+ }
+ }
+
+ // Need to add a "from=" attribute (normally added by the server)
+ // Then route the incoming signaling message to the "other" session manager.
+ virtual void OnMessage(talk_base::Message* message) {
+ talk_base::TypedMessageData<buzz::XmlElement>* data =
+ static_cast<talk_base::TypedMessageData<buzz::XmlElement>*>(
+ message->pdata);
+ bool response = data->data().Attr(buzz::QN_TYPE) == buzz::STR_RESULT;
+ if (message->message_id == MSG_RSIGNAL) {
+ data->data().AddAttr(buzz::QN_FROM, remote_client_.jid().Str());
+ if (!response) {
+ local_sm_.OnIncomingMessage(&data->data());
+ } else {
+ local_sm_.OnIncomingResponse(NULL, &data->data());
+ }
+ } else if (message->message_id == MSG_LSIGNAL) {
+ data->data().AddAttr(buzz::QN_FROM, local_client_.jid().Str());
+ if (!response) {
+ remote_sm_.OnIncomingMessage(&data->data());
+ } else {
+ remote_sm_.OnIncomingResponse(NULL, &data->data());
+ }
+ }
+ delete data;
+ }
+
+ // Accept the tunnel when it arrives and wire up the stream.
+ void OnIncomingTunnel(cricket::TunnelSessionClient* client,
+ buzz::Jid jid, std::string description,
+ cricket::Session* session) {
+ remote_tunnel_.reset(remote_client_.AcceptTunnel(session));
+ remote_tunnel_->SignalEvent.connect(this,
+ &TunnelSessionClientTest::OnStreamEvent);
+ }
+
+ // Send from send_stream_ as long as we're not flow-controlled.
+ // Read bytes out into recv_stream_ as they arrive.
+ // End the test when we are notified that the local side has closed the
+ // tunnel. All data has been read out at this point.
+ void OnStreamEvent(talk_base::StreamInterface* stream, int events,
+ int error) {
+ if (events & talk_base::SE_READ) {
+ if (stream == remote_tunnel_.get()) {
+ ReadData();
+ }
+ }
+ if (events & talk_base::SE_WRITE) {
+ if (stream == local_tunnel_.get()) {
+ bool done = false;
+ WriteData(&done);
+ if (done) {
+ local_tunnel_->Close();
+ }
+ }
+ }
+ if (events & talk_base::SE_CLOSE) {
+ if (stream == remote_tunnel_.get()) {
+ remote_tunnel_->Close();
+ done_ = true;
+ }
+ }
+ }
+
+ // Spool from the tunnel into recv_stream.
+ // Flow() doesn't work here because it won't write if the read blocks.
+ void ReadData() {
+ char block[kBlockSize];
+ size_t read, position;
+ talk_base::StreamResult res;
+ while ((res = remote_tunnel_->Read(block, sizeof(block), &read, NULL)) ==
+ talk_base::SR_SUCCESS) {
+ recv_stream_.Write(block, read, NULL, NULL);
+ }
+ ASSERT(res != talk_base::SR_EOS);
+ recv_stream_.GetPosition(&position);
+ LOG(LS_VERBOSE) << "Recv position: " << position;
+ }
+ // Spool from send_stream into the tunnel. Back up if we get flow controlled.
+ void WriteData(bool* done) {
+ char block[kBlockSize];
+ size_t leftover = 0, position;
+ talk_base::StreamResult res = talk_base::Flow(&send_stream_,
+ block, sizeof(block), local_tunnel_.get(), &leftover);
+ if (res == talk_base::SR_BLOCK) {
+ send_stream_.GetPosition(&position);
+ send_stream_.SetPosition(position - leftover);
+ LOG(LS_VERBOSE) << "Send position: " << position - leftover;
+ *done = false;
+ } else if (res == talk_base::SR_SUCCESS) {
+ *done = true;
+ } else {
+ ASSERT(false); // shouldn't happen
+ }
+ }
+
+ private:
+ cricket::FakePortAllocator local_pa_;
+ cricket::FakePortAllocator remote_pa_;
+ cricket::SessionManager local_sm_;
+ cricket::SessionManager remote_sm_;
+ cricket::TunnelSessionClient local_client_;
+ cricket::TunnelSessionClient remote_client_;
+ talk_base::scoped_ptr<talk_base::StreamInterface> local_tunnel_;
+ talk_base::scoped_ptr<talk_base::StreamInterface> remote_tunnel_;
+ talk_base::MemoryStream send_stream_;
+ talk_base::MemoryStream recv_stream_;
+ bool done_;
+};
+
+// Test the normal case of sending data from one side to the other.
+TEST_F(TunnelSessionClientTest, TestTransfer) {
+ TestTransfer(1000000);
+}
diff --git a/talk/site_scons/talk.py b/talk/site_scons/talk.py
index 87650fa..2989160 100644
--- a/talk/site_scons/talk.py
+++ b/talk/site_scons/talk.py
@@ -417,15 +417,6 @@
return merged
-# Whether to enable extra 64-bit targets marked with the also64bit setting. Used
-# only for the legacy mixed 32/64 desktop Linux build.
-def EnableExtra64BitTargets(env):
- return (env.Bit('linux') and env.Bit('build_platform_64bit') and
- not env.Bit('host_platform_64bit') and
- not env.Bit('host_platform_mipsel')
- )
-
-
def MergeSettingsFromLibraryDependencies(env, params):
if 'libs' in params:
for lib in params['libs']:
@@ -523,13 +514,6 @@
if values is not None:
env.Prepend(**{var : values})
- # workaround for pulse stripping link flag for unknown reason
- if EnableExtra64BitTargets(env):
- env['SHLINKCOM'] = ('$SHLINK -o $TARGET -m32 $SHLINKFLAGS $SOURCES '
- '$_LIBDIRFLAGS $_LIBFLAGS')
- env['LINKCOM'] = ('$LINK -o $TARGET -m32 $LINKFLAGS $SOURCES '
- '$_LIBDIRFLAGS $_LIBFLAGS')
-
# any other parameters are replaced without renaming
for field, value in params.items():
env.Replace(**{field : value})
@@ -556,36 +540,7 @@
node = builder(name, srcs)
- # make a parallel 64bit version if requested
- if EnableExtra64BitTargets(env) and PopEntry(params, 'also64bit'):
- env_64bit = env.Clone()
- env_64bit.FilterOut(CCFLAGS = ['-m32'], LINKFLAGS = ['-m32'])
- env_64bit.Prepend(CCFLAGS = ['-m64', '-fPIC'], LINKFLAGS = ['-m64'])
- name_64bit = name + '64'
- env_64bit.Replace(OBJSUFFIX = '64' + env_64bit['OBJSUFFIX'])
- env_64bit.Replace(SHOBJSUFFIX = '64' + env_64bit['SHOBJSUFFIX'])
- if ('ComponentProgram' == component or
- ('ComponentLibrary' == component and
- env_64bit['COMPONENT_STATIC'] == False)):
- # link 64 bit versions of libraries
- libs = []
- for lib in env_64bit['LIBS']:
- libparams = _GetLibParams(env, lib)
- if libparams and 'also64bit' in libparams:
- libs.append(lib + '64')
- else:
- libs.append(lib)
- env_64bit.Replace(LIBS = libs)
-
- env_64bit['SHLINKCOM'] = ('$SHLINK -o $TARGET -m64 $SHLINKFLAGS $SOURCES '
- '$_LIBDIRFLAGS $_LIBFLAGS')
- env_64bit['LINKCOM'] = ('$LINK -o $TARGET -m64 $LINKFLAGS $SOURCES '
- '$_LIBDIRFLAGS $_LIBFLAGS')
- builder = getattr(env_64bit, component)
- nodes = [node, builder(name_64bit, srcs)]
- return nodes
-
- if signed: # Note currently incompatible with 64Bit flag
+ if signed:
# Get the name of the built binary, then get the name of the final signed
# version from it. We need the output path since we don't know the file
# extension beforehand.
diff --git a/talk/sound/alsasoundsystem.cc b/talk/sound/alsasoundsystem.cc
index fdfb800..c6dc73a 100644
--- a/talk/sound/alsasoundsystem.cc
+++ b/talk/sound/alsasoundsystem.cc
@@ -31,6 +31,7 @@
#include "talk/base/logging.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/stringutils.h"
+#include "talk/base/timeutils.h"
#include "talk/base/worker.h"
#include "talk/sound/sounddevicelocator.h"
#include "talk/sound/soundinputstreaminterface.h"
@@ -59,9 +60,6 @@
// The latency we'll use for kNoLatencyRequirements (chosen arbitrarily).
static const int kDefaultLatencyUsecs = kMinimumLatencyUsecs * 2;
-static const int kUsecsPerSec = 1000000;
-static const int kUsecsPerMsec = 1000;
-
// We translate newlines in ALSA device descriptions to hyphens.
static const char kAlsaDescriptionSearch[] = "\n";
static const char kAlsaDescriptionReplace[] = " - ";
@@ -172,7 +170,7 @@
return 0;
}
// The delay is in frames. Convert to microseconds.
- return delay * kUsecsPerSec / freq_;
+ return delay * talk_base::kNumMicrosecsPerSec / freq_;
}
// Used to recover from certain recoverable errors, principally buffer overrun
@@ -656,7 +654,7 @@
int freq)) {
if (!IsInitialized()) {
- return false;
+ return NULL;
}
StreamInterface *stream;
@@ -686,7 +684,7 @@
} else {
// kLowLatency is 0, so we treat it the same as a request for zero latency.
// Compute what the user asked for.
- latency = kUsecsPerSec *
+ latency = talk_base::kNumMicrosecsPerSec *
params.latency /
params.freq /
FrameSize(params);
@@ -721,7 +719,7 @@
FrameSize(params),
// We set the wait time to twice the requested latency, so that wait
// timeouts should be rare.
- 2 * latency / kUsecsPerMsec,
+ 2 * latency / talk_base::kNumMicrosecsPerMillisec,
params.flags,
params.freq);
if (stream) {
diff --git a/talk/sound/automaticallychosensoundsystem_unittest.cc b/talk/sound/automaticallychosensoundsystem_unittest.cc
new file mode 100644
index 0000000..a8afeec
--- /dev/null
+++ b/talk/sound/automaticallychosensoundsystem_unittest.cc
@@ -0,0 +1,214 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/gunit.h"
+#include "talk/sound/automaticallychosensoundsystem.h"
+#include "talk/sound/nullsoundsystem.h"
+
+namespace cricket {
+
+class NeverFailsToFailSoundSystem : public NullSoundSystem {
+ public:
+ // Overrides superclass.
+ virtual bool Init() {
+ return false;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new NeverFailsToFailSoundSystem();
+ }
+};
+
+class InitCheckingSoundSystem1 : public NullSoundSystem {
+ public:
+ // Overrides superclass.
+ virtual bool Init() {
+ created_ = true;
+ return true;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new InitCheckingSoundSystem1();
+ }
+
+ static bool created_;
+};
+
+bool InitCheckingSoundSystem1::created_ = false;
+
+class InitCheckingSoundSystem2 : public NullSoundSystem {
+ public:
+ // Overrides superclass.
+ virtual bool Init() {
+ created_ = true;
+ return true;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new InitCheckingSoundSystem2();
+ }
+
+ static bool created_;
+};
+
+bool InitCheckingSoundSystem2::created_ = false;
+
+class DeletionCheckingSoundSystem1 : public NeverFailsToFailSoundSystem {
+ public:
+ virtual ~DeletionCheckingSoundSystem1() {
+ deleted_ = true;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new DeletionCheckingSoundSystem1();
+ }
+
+ static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem1::deleted_ = false;
+
+class DeletionCheckingSoundSystem2 : public NeverFailsToFailSoundSystem {
+ public:
+ virtual ~DeletionCheckingSoundSystem2() {
+ deleted_ = true;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new DeletionCheckingSoundSystem2();
+ }
+
+ static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem2::deleted_ = false;
+
+class DeletionCheckingSoundSystem3 : public NullSoundSystem {
+ public:
+ virtual ~DeletionCheckingSoundSystem3() {
+ deleted_ = true;
+ }
+
+ static SoundSystemInterface *Create() {
+ return new DeletionCheckingSoundSystem3();
+ }
+
+ static bool deleted_;
+};
+
+bool DeletionCheckingSoundSystem3::deleted_ = false;
+
+extern const SoundSystemCreator kSingleSystemFailingCreators[] = {
+ &NeverFailsToFailSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, SingleSystemFailing) {
+ AutomaticallyChosenSoundSystem<
+ kSingleSystemFailingCreators,
+ ARRAY_SIZE(kSingleSystemFailingCreators)> sound_system;
+ EXPECT_FALSE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kSingleSystemSucceedingCreators[] = {
+ &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, SingleSystemSucceeding) {
+ AutomaticallyChosenSoundSystem<
+ kSingleSystemSucceedingCreators,
+ ARRAY_SIZE(kSingleSystemSucceedingCreators)> sound_system;
+ EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator
+ kFailedFirstSystemResultsInUsingSecondCreators[] = {
+ &NeverFailsToFailSoundSystem::Create,
+ &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, FailedFirstSystemResultsInUsingSecond) {
+ AutomaticallyChosenSoundSystem<
+ kFailedFirstSystemResultsInUsingSecondCreators,
+ ARRAY_SIZE(kFailedFirstSystemResultsInUsingSecondCreators)> sound_system;
+ EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kEarlierEntriesHavePriorityCreators[] = {
+ &InitCheckingSoundSystem1::Create,
+ &InitCheckingSoundSystem2::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, EarlierEntriesHavePriority) {
+ AutomaticallyChosenSoundSystem<
+ kEarlierEntriesHavePriorityCreators,
+ ARRAY_SIZE(kEarlierEntriesHavePriorityCreators)> sound_system;
+ InitCheckingSoundSystem1::created_ = false;
+ InitCheckingSoundSystem2::created_ = false;
+ EXPECT_TRUE(sound_system.Init());
+ EXPECT_TRUE(InitCheckingSoundSystem1::created_);
+ EXPECT_FALSE(InitCheckingSoundSystem2::created_);
+}
+
+extern const SoundSystemCreator kManySoundSystemsCreators[] = {
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+ &NullSoundSystem::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, ManySoundSystems) {
+ AutomaticallyChosenSoundSystem<
+ kManySoundSystemsCreators,
+ ARRAY_SIZE(kManySoundSystemsCreators)> sound_system;
+ EXPECT_TRUE(sound_system.Init());
+}
+
+extern const SoundSystemCreator kDeletesAllCreatedSoundSystemsCreators[] = {
+ &DeletionCheckingSoundSystem1::Create,
+ &DeletionCheckingSoundSystem2::Create,
+ &DeletionCheckingSoundSystem3::Create,
+};
+
+TEST(AutomaticallyChosenSoundSystem, DeletesAllCreatedSoundSystems) {
+ typedef AutomaticallyChosenSoundSystem<
+ kDeletesAllCreatedSoundSystemsCreators,
+ ARRAY_SIZE(kDeletesAllCreatedSoundSystemsCreators)> TestSoundSystem;
+ TestSoundSystem *sound_system = new TestSoundSystem();
+ DeletionCheckingSoundSystem1::deleted_ = false;
+ DeletionCheckingSoundSystem2::deleted_ = false;
+ DeletionCheckingSoundSystem3::deleted_ = false;
+ EXPECT_TRUE(sound_system->Init());
+ delete sound_system;
+ EXPECT_TRUE(DeletionCheckingSoundSystem1::deleted_);
+ EXPECT_TRUE(DeletionCheckingSoundSystem2::deleted_);
+ EXPECT_TRUE(DeletionCheckingSoundSystem3::deleted_);
+}
+
+} // namespace cricket
diff --git a/talk/sound/pulseaudiosoundsystem.cc b/talk/sound/pulseaudiosoundsystem.cc
index 3b45f8c..4ccedc1 100644
--- a/talk/sound/pulseaudiosoundsystem.cc
+++ b/talk/sound/pulseaudiosoundsystem.cc
@@ -39,6 +39,7 @@
#include "talk/base/fileutils.h" // for GetApplicationName()
#include "talk/base/logging.h"
#include "talk/base/worker.h"
+#include "talk/base/timeutils.h"
#include "talk/sound/sounddevicelocator.h"
#include "talk/sound/soundinputstreaminterface.h"
#include "talk/sound/soundoutputstreaminterface.h"
@@ -48,13 +49,6 @@
// First PulseAudio protocol version that supports PA_STREAM_ADJUST_LATENCY.
static const uint32_t kAdjustLatencyProtocolVersion = 13;
-// We define this flag if it's missing from our headers, because we want to be
-// able to compile against old headers but still use PA_STREAM_ADJUST_LATENCY
-// if run against a recent version of the library.
-#ifndef PA_STREAM_ADJUST_LATENCY
-#define PA_STREAM_ADJUST_LATENCY 0x2000U
-#endif
-
// Lookup table from the cricket format enum in soundsysteminterface.h to
// Pulse's enums.
static const pa_sample_format_t kCricketFormatToPulseFormatTable[] = {
@@ -99,8 +93,6 @@
// kNoLatencyRequirements case.)
static const int kCaptureBufferExtraMsecs = 750;
-static const int kMsecsPerSec = 1000;
-
static void FillPlaybackBufferAttr(int latency,
pa_buffer_attr *attr) {
attr->maxlength = latency;
@@ -909,7 +901,8 @@
size_t bytes_per_sec = LATE(pa_bytes_per_second)(spec);
int new_latency = configured_latency_ +
- bytes_per_sec * kPlaybackLatencyIncrementMsecs / kMsecsPerSec;
+ bytes_per_sec * kPlaybackLatencyIncrementMsecs /
+ talk_base::kNumMicrosecsPerSec;
pa_buffer_attr new_attr = {0};
FillPlaybackBufferAttr(new_latency, &new_attr);
@@ -1468,7 +1461,8 @@
latency = talk_base::_max(
latency,
static_cast<int>(
- bytes_per_sec * kPlaybackLatencyMinimumMsecs / kMsecsPerSec));
+ bytes_per_sec * kPlaybackLatencyMinimumMsecs /
+ talk_base::kNumMicrosecsPerSec));
FillPlaybackBufferAttr(latency, &attr);
pattr = &attr;
}
@@ -1498,13 +1492,14 @@
if (latency != kNoLatencyRequirements) {
size_t bytes_per_sec = LATE(pa_bytes_per_second)(&spec);
if (latency == kLowLatency) {
- latency = bytes_per_sec * kLowCaptureLatencyMsecs / kMsecsPerSec;
+ latency = bytes_per_sec * kLowCaptureLatencyMsecs /
+ talk_base::kNumMicrosecsPerSec;
}
// Note: fragsize specifies a maximum transfer size, not a minimum, so it is
// not possible to force a high latency setting, only a low one.
attr.fragsize = latency;
- attr.maxlength = latency +
- bytes_per_sec * kCaptureBufferExtraMsecs / kMsecsPerSec;
+ attr.maxlength = latency + bytes_per_sec * kCaptureBufferExtraMsecs /
+ talk_base::kNumMicrosecsPerSec;
LOG(LS_VERBOSE) << "Configuring latency = " << attr.fragsize
<< ", maxlength = " << attr.maxlength;
pattr = &attr;
diff --git a/talk/xmllite/qname.cc b/talk/xmllite/qname.cc
index 7486524..0dadb79 100644
--- a/talk/xmllite/qname.cc
+++ b/talk/xmllite/qname.cc
@@ -25,138 +25,71 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <string>
-#include "talk/base/common.h"
-#include "talk/xmllite/xmlelement.h"
#include "talk/xmllite/qname.h"
-#include "talk/xmllite/xmlconstants.h"
namespace buzz {
-static int QName_Hash(const std::string & ns, const char * local) {
- int result = static_cast<int>(ns.size()) * 101;
- while (*local) {
- result *= 19;
- result += *local;
- local += 1;
- }
- return result;
+QName::QName() {
}
-static const int bits = 9;
-static QName::Data * get_qname_table() {
- static QName::Data qname_table[1 << bits];
- return qname_table;
+QName::QName(const QName& qname)
+ : namespace_(qname.namespace_),
+ local_part_(qname.local_part_) {
}
-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;
- }
+QName::QName(const StaticQName& const_value)
+ : namespace_(const_value.ns),
+ local_part_(const_value.local) {
}
-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(const std::string& ns, const std::string& local)
+ : namespace_(ns),
+ local_part_(local) {
+}
+
+QName::QName(const std::string& merged_or_local) {
+ size_t i = merged_or_local.rfind(':');
+ if (i == std::string::npos) {
+ local_part_ = merged_or_local;
+ } else {
+ namespace_ = merged_or_local.substr(0, i);
+ local_part_ = merged_or_local.substr(i + 1);
}
}
QName::~QName() {
- data_->Release();
}
-QName::QName() : data_(QN_EMPTY.data_) {
- data_->AddRef();
-}
+std::string QName::Merged() const {
+ if (namespace_[0] == '\0')
+ return local_part_;
-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());
+ std::string result;
+ result.reserve(namespace_.length() + 1 + local_part_.length());
+ result += namespace_;
result += ':';
- result += data_->localPart_;
+ result += local_part_;
return result;
}
-bool
-QName::operator==(const QName & other) const {
- return other.data_ == data_ ||
- (data_->localPart_ == other.data_->localPart_ &&
- data_->namespace_ == other.data_->namespace_);
+bool QName::IsEmpty() const {
+ return namespace_.empty() && local_part_.empty();
}
-int
-QName::Compare(const QName & other) const {
- if (data_ == other.data_)
- return 0;
-
- int result = data_->localPart_.compare(other.data_->localPart_);
- if (result)
+int QName::Compare(const StaticQName& other) const {
+ int result = local_part_.compare(other.local);
+ if (result != 0)
return result;
- return data_->namespace_.compare(other.data_->namespace_);
+ return namespace_.compare(other.ns);
}
+int QName::Compare(const QName& other) const {
+ int result = local_part_.compare(other.local_part_);
+ if (result != 0)
+ return result;
+
+ return namespace_.compare(other.namespace_);
}
+
+} // namespace buzz
diff --git a/talk/xmllite/qname.h b/talk/xmllite/qname.h
index 172a067..92e54d0 100644
--- a/talk/xmllite/qname.h
+++ b/talk/xmllite/qname.h
@@ -2,87 +2,99 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_
+#ifndef TALK_XMLLITE_QNAME_H_
+#define TALK_XMLLITE_QNAME_H_
#include <string>
-#include "talk/base/criticalsection.h"
namespace buzz {
+class QName;
-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();
+// StaticQName is used to represend constant quailified names. They
+// can be initialized statically and don't need intializers code, e.g.
+// const StaticQName QN_FOO = { "foo_namespace", "foo" };
+//
+// Beside this use case, QName should be used everywhere
+// else. StaticQName instances are implicitly converted to QName
+// objects.
+struct StaticQName {
+ const char* const ns;
+ const char* const local;
- const std::string & Namespace() const { return data_->namespace_; }
- const std::string & LocalPart() const { return data_->localPart_; }
- std::string Merged() const;
- int Compare(const QName & other) const;
- bool operator==(const QName & other) const;
- bool operator!=(const QName & other) const { return !operator==(other); }
- bool operator<(const QName & other) const { return Compare(other) < 0; }
-
- class Data {
- public:
- Data(const std::string & ns, const std::string & local) :
- namespace_(ns),
- localPart_(local),
- refcount_(1) {}
-
- Data() : refcount_(0) {}
-
- std::string namespace_;
- std::string localPart_;
- void AddRef() { talk_base::AtomicOps::Increment(&refcount_); }
- void Release() { if (!talk_base::AtomicOps::Decrement(&refcount_)) { delete this; } }
- bool Occupied() { return !!refcount_; }
-
- private:
- int refcount_;
- };
-
-private:
- Data * data_;
+ bool operator==(const QName& other) const;
+ bool operator!=(const QName& other) const;
};
+class QName {
+ public:
+ QName();
+ QName(const QName& qname);
+ QName(const StaticQName& const_value);
+ QName(const std::string& ns, const std::string& local);
+ explicit QName(const std::string& merged_or_local);
+ ~QName();
+ const std::string& Namespace() const { return namespace_; }
+ const std::string& LocalPart() const { return local_part_; }
+ std::string Merged() const;
+ bool IsEmpty() const;
+
+ int Compare(const StaticQName& other) const;
+ int Compare(const QName& other) const;
+
+ bool operator==(const StaticQName& other) const {
+ return Compare(other) == 0;
+ }
+ bool operator==(const QName& other) const {
+ return Compare(other) == 0;
+ }
+ bool operator!=(const StaticQName& other) const {
+ return Compare(other) != 0;
+ }
+ bool operator!=(const QName& other) const {
+ return Compare(other) != 0;
+ }
+ bool operator<(const QName& other) const {
+ return Compare(other) < 0;
+ }
+
+ private:
+ std::string namespace_;
+ std::string local_part_;
+};
+
+inline bool StaticQName::operator==(const QName& other) const {
+ return other.Compare(*this) == 0;
}
-#endif
+inline bool StaticQName::operator!=(const QName& other) const {
+ return other.Compare(*this) != 0;
+}
+
+} // namespace buzz
+
+#endif // TALK_XMLLITE_QNAME_H_
diff --git a/talk/xmllite/qname_unittest.cc b/talk/xmllite/qname_unittest.cc
new file mode 100644
index 0000000..976d822
--- /dev/null
+++ b/talk/xmllite/qname_unittest.cc
@@ -0,0 +1,131 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+
+using buzz::StaticQName;
+using buzz::QName;
+
+TEST(QNameTest, TestTrivial) {
+ QName name("test");
+ EXPECT_EQ(name.LocalPart(), "test");
+ EXPECT_EQ(name.Namespace(), "");
+}
+
+TEST(QNameTest, TestSplit) {
+ QName name("a:test");
+ EXPECT_EQ(name.LocalPart(), "test");
+ EXPECT_EQ(name.Namespace(), "a");
+ QName name2("a-very:long:namespace:test-this");
+ EXPECT_EQ(name2.LocalPart(), "test-this");
+ EXPECT_EQ(name2.Namespace(), "a-very:long:namespace");
+}
+
+TEST(QNameTest, TestMerge) {
+ QName name("a", "test");
+ EXPECT_EQ(name.LocalPart(), "test");
+ EXPECT_EQ(name.Namespace(), "a");
+ EXPECT_EQ(name.Merged(), "a:test");
+ QName name2("a-very:long:namespace", "test-this");
+ EXPECT_EQ(name2.LocalPart(), "test-this");
+ EXPECT_EQ(name2.Namespace(), "a-very:long:namespace");
+ EXPECT_EQ(name2.Merged(), "a-very:long:namespace:test-this");
+}
+
+TEST(QNameTest, TestAssignment) {
+ QName name("a", "test");
+ // copy constructor
+ QName namecopy(name);
+ EXPECT_EQ(namecopy.LocalPart(), "test");
+ EXPECT_EQ(namecopy.Namespace(), "a");
+ QName nameassigned("");
+ nameassigned = name;
+ EXPECT_EQ(nameassigned.LocalPart(), "test");
+ EXPECT_EQ(nameassigned.Namespace(), "a");
+}
+
+TEST(QNameTest, TestConstAssignment) {
+ StaticQName name = { "a", "test" };
+ QName namecopy(name);
+ EXPECT_EQ(namecopy.LocalPart(), "test");
+ EXPECT_EQ(namecopy.Namespace(), "a");
+ QName nameassigned("");
+ nameassigned = name;
+ EXPECT_EQ(nameassigned.LocalPart(), "test");
+ EXPECT_EQ(nameassigned.Namespace(), "a");
+}
+
+TEST(QNameTest, TestEquality) {
+ QName name("a-very:long:namespace:test-this");
+ QName name2("a-very:long:namespace", "test-this");
+ QName name3("a-very:long:namespaxe", "test-this");
+ EXPECT_TRUE(name == name2);
+ EXPECT_FALSE(name == name3);
+}
+
+TEST(QNameTest, TestCompare) {
+ QName name("a");
+ QName name2("nsa", "a");
+ QName name3("nsa", "b");
+ QName name4("nsb", "b");
+
+ EXPECT_TRUE(name < name2);
+ EXPECT_FALSE(name2 < name);
+
+ EXPECT_FALSE(name2 < name2);
+
+ EXPECT_TRUE(name2 < name3);
+ EXPECT_FALSE(name3 < name2);
+
+ EXPECT_TRUE(name3 < name4);
+ EXPECT_FALSE(name4 < name3);
+}
+
+TEST(QNameTest, TestStaticQName) {
+ const StaticQName const_name1 = { "namespace", "local-name1" };
+ const StaticQName const_name2 = { "namespace", "local-name2" };
+ const QName name("namespace", "local-name1");
+ const QName name1 = const_name1;
+ const QName name2 = const_name2;
+
+ EXPECT_TRUE(name == const_name1);
+ EXPECT_TRUE(const_name1 == name);
+ EXPECT_FALSE(name != const_name1);
+ EXPECT_FALSE(const_name1 != name);
+
+ EXPECT_TRUE(name == name1);
+ EXPECT_TRUE(name1 == name);
+ EXPECT_FALSE(name != name1);
+ EXPECT_FALSE(name1 != name);
+
+ EXPECT_FALSE(name == name2);
+ EXPECT_FALSE(name2 == name);
+ EXPECT_TRUE(name != name2);
+ EXPECT_TRUE(name2 != name);
+}
diff --git a/talk/xmllite/xmlbuilder.cc b/talk/xmllite/xmlbuilder.cc
index 75daa57..486b6d5 100644
--- a/talk/xmllite/xmlbuilder.cc
+++ b/talk/xmllite/xmlbuilder.cc
@@ -30,6 +30,7 @@
#include <vector>
#include <set>
#include "talk/base/common.h"
+#include "talk/xmllite/xmlconstants.h"
#include "talk/xmllite/xmlelement.h"
namespace buzz {
@@ -51,7 +52,7 @@
XmlBuilder::BuildElement(XmlParseContext * pctx,
const char * name, const char ** atts) {
QName tagName(pctx->ResolveQName(name, false));
- if (tagName == QN_EMPTY)
+ if (tagName.IsEmpty())
return NULL;
XmlElement * pelNew = new XmlElement(tagName);
@@ -63,7 +64,7 @@
while (*atts) {
QName attName(pctx->ResolveQName(*atts, true));
- if (attName == QN_EMPTY) {
+ if (attName.IsEmpty()) {
delete pelNew;
return NULL;
}
@@ -143,6 +144,4 @@
XmlBuilder::~XmlBuilder() {
}
-
-
-}
+} // namespace buzz
diff --git a/talk/xmllite/xmlbuilder_unittest.cc b/talk/xmllite/xmlbuilder_unittest.cc
new file mode 100644
index 0000000..9302276
--- /dev/null
+++ b/talk/xmllite/xmlbuilder_unittest.cc
@@ -0,0 +1,194 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlparser.h"
+
+using buzz::XmlBuilder;
+using buzz::XmlElement;
+using buzz::XmlParser;
+
+TEST(XmlBuilderTest, TestTrivial) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing/>");
+ EXPECT_EQ("<testing/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestAttributes1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='b'/>");
+ EXPECT_EQ("<testing a=\"b\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestAttributes2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing e='' long='some text'/>");
+ EXPECT_EQ("<testing e=\"\" long=\"some text\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNesting1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<top><first/><second><third></third></second></top>");
+ EXPECT_EQ("<top><first/><second><third/></second></top>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNesting2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<top><fifth><deeper><and><deeper/></and><sibling><leaf/>"
+ "</sibling></deeper></fifth><first/><second><third></third>"
+ "</second></top>");
+ EXPECT_EQ("<top><fifth><deeper><and><deeper/></and><sibling><leaf/>"
+ "</sibling></deeper></fifth><first/><second><third/>"
+ "</second></top>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='>'/>");
+ EXPECT_EQ("<testing a=\">\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='<>&"'/>");
+ EXPECT_EQ("<testing a=\"<>&"\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting3) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='so "important"'/>");
+ EXPECT_EQ("<testing a=\"so "important"\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting4) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='"important", yes'/>");
+ EXPECT_EQ("<testing a=\""important", yes\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestQuoting5) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<testing a='<what is "important">'/>");
+ EXPECT_EQ("<testing a=\"<what is "important">\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing>></testing>");
+ EXPECT_EQ("<testing>></testing>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing><>&"</testing>");
+ EXPECT_EQ("<testing><>&\"</testing>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText3) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing>so <important></testing>");
+ EXPECT_EQ("<testing>so <important></testing>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText4) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing><important>, yes</testing>");
+ EXPECT_EQ("<testing><important>, yes</testing>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestText5) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<testing>importance &<important>&</testing>");
+ EXPECT_EQ("<testing>importance &<important>&</testing>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing xmlns='foo'/>");
+ EXPECT_EQ("<testing xmlns=\"foo\"/>", builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing xmlns:a='foo' a:b='c'/>");
+ EXPECT_EQ("<testing xmlns:a=\"foo\" a:b=\"c\"/>",
+ builder.BuiltElement()->Str());
+}
+
+TEST(XmlBuilderTest, TestNamespace3) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing xmlns:a=''/>");
+ EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestNamespace4) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a:b='c'/>");
+ EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision1) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, "<testing a='first' a='second'/>");
+ EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision2) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<testing xmlns:a='foo' xmlns:b='foo' a:x='c' b:x='d'/>");
+ EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
+TEST(XmlBuilderTest, TestAttrCollision3) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder,
+ "<testing xmlns:a='foo'><nested xmlns:b='foo' a:x='c' b:x='d'/>"
+ "</testing>");
+ EXPECT_TRUE(NULL == builder.BuiltElement());
+}
+
diff --git a/talk/xmllite/xmlconstants.cc b/talk/xmllite/xmlconstants.cc
index 503f832..f94d779 100644
--- a/talk/xmllite/xmlconstants.cc
+++ b/talk/xmllite/xmlconstants.cc
@@ -2,64 +2,41 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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"
+#include "talk/xmllite/xmlconstants.h"
-using namespace buzz;
+namespace buzz {
-const std::string & XmlConstants::str_empty() {
- static const std::string str_empty_;
- return str_empty_;
-}
+const char STR_EMPTY[] = "";
+const char NS_XML[] = "http://www.w3.org/XML/1998/namespace";
+const char NS_XMLNS[] = "http://www.w3.org/2000/xmlns/";
+const char STR_XMLNS[] = "xmlns";
+const char STR_XML[] = "xml";
+const char STR_VERSION[] = "version";
+const char STR_ENCODING[] = "encoding";
-const std::string & XmlConstants::ns_xml() {
- static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace");
- return ns_xml_;
-}
+const StaticQName QN_XMLNS = { STR_EMPTY, STR_XMLNS };
-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_;
-}
+} // namespace buzz
diff --git a/talk/xmllite/xmlconstants.h b/talk/xmllite/xmlconstants.h
index 8514d6f..3e5da98 100644
--- a/talk/xmllite/xmlconstants.h
+++ b/talk/xmllite/xmlconstants.h
@@ -2,60 +2,46 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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_XMLCONSTANTS_H_
+#define TALK_XMLLITE_XMLCONSTANTS_H_
-#ifndef TALK_XMLLITE_CONSTANTS_H_
-#define TALK_XMLLITE_CONSTANTS_H_
+#include "talk/xmllite/qname.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();
-};
-}
+extern const char STR_EMPTY[];
+extern const char NS_XML[];
+extern const char NS_XMLNS[];
+extern const char STR_XMLNS[];
+extern const char STR_XML[];
+extern const char STR_VERSION[];
+extern const char STR_ENCODING[];
-#endif // TALK_XMLLITE_CONSTANTS_H_
+extern const StaticQName QN_XMLNS;
+
+} // namespace buzz
+
+#endif // TALK_XMLLITE_XMLCONSTANTS_H_
diff --git a/talk/xmllite/xmlelement.cc b/talk/xmllite/xmlelement.cc
index bf436fb..176ce5c 100644
--- a/talk/xmllite/xmlelement.cc
+++ b/talk/xmllite/xmlelement.cc
@@ -25,12 +25,14 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/xmllite/xmlelement.h"
+
+#include <ostream>
+#include <sstream>
#include <string>
#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"
@@ -39,242 +41,235 @@
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 {
+bool XmlText::IsTextImpl() const {
return true;
}
-XmlElement *
-XmlText::AsElementImpl() const {
+XmlElement* XmlText::AsElementImpl() const {
return NULL;
}
-XmlText *
-XmlText::AsTextImpl() const {
+XmlText* XmlText::AsTextImpl() const {
return const_cast<XmlText *>(this);
}
-void
-XmlText::SetText(const std::string & text) {
+void XmlText::SetText(const std::string& text) {
text_ = text;
}
-void
-XmlText::AddParsedText(const char * buf, int len) {
+void XmlText::AddParsedText(const char* buf, int len) {
text_.append(buf, len);
}
-void
-XmlText::AddText(const std::string & text) {
+void XmlText::AddText(const std::string& text) {
text_ += text;
}
XmlText::~XmlText() {
}
-XmlElement::XmlElement(const QName & name) :
+XmlElement::XmlElement(const QName& name) :
name_(name),
- pFirstAttr_(NULL),
- pLastAttr_(NULL),
- pFirstChild_(NULL),
- pLastChild_(NULL),
+ first_attr_(NULL),
+ last_attr_(NULL),
+ first_child_(NULL),
+ last_child_(NULL),
cdata_(false) {
}
-XmlElement::XmlElement(const XmlElement & elt) :
+XmlElement::XmlElement(const XmlElement& elt) :
XmlChild(),
name_(elt.name_),
- pFirstAttr_(NULL),
- pLastAttr_(NULL),
- pFirstChild_(NULL),
- pLastChild_(NULL),
+ first_attr_(NULL),
+ last_attr_(NULL),
+ first_child_(NULL),
+ last_child_(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_);
+ XmlAttr* attr;
+ XmlAttr ** plast_attr = &first_attr_;
+ XmlAttr* newAttr = NULL;
+ for (attr = elt.first_attr_; attr; attr = attr->NextAttr()) {
+ newAttr = new XmlAttr(*attr);
+ *plast_attr = newAttr;
+ plast_attr = &(newAttr->next_attr_);
}
- pLastAttr_ = newAttr;
+ last_attr_ = newAttr;
// copy children
- XmlChild * pChild;
- XmlChild ** ppLast = &pFirstChild_;
- XmlChild * newChild = NULL;
+ XmlChild* pChild;
+ XmlChild ** ppLast = &first_child_;
+ XmlChild* newChild = NULL;
- for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) {
+ for (pChild = elt.first_child_; pChild; pChild = pChild->NextChild()) {
if (pChild->IsText()) {
newChild = new XmlText(*(pChild->AsText()));
} else {
newChild = new XmlElement(*(pChild->AsElement()));
}
*ppLast = newChild;
- ppLast = &(newChild->pNextChild_);
+ ppLast = &(newChild->next_child_);
}
- pLastChild_ = newChild;
+ last_child_ = newChild;
cdata_ = elt.cdata_;
}
-XmlElement::XmlElement(const QName & name, bool useDefaultNs) :
+XmlElement::XmlElement(const QName& name, bool useDefaultNs) :
name_(name),
- pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
- pLastAttr_(pFirstAttr_),
- pFirstChild_(NULL),
- pLastChild_(NULL),
+ first_attr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
+ last_attr_(first_attr_),
+ first_child_(NULL),
+ last_child_(NULL),
cdata_(false) {
}
-bool
-XmlElement::IsTextImpl() const {
+bool XmlElement::IsTextImpl() const {
return false;
}
-XmlElement *
-XmlElement::AsElementImpl() const {
+XmlElement* XmlElement::AsElementImpl() const {
return const_cast<XmlElement *>(this);
}
-XmlText *
-XmlElement::AsTextImpl() const {
+XmlText* XmlElement::AsTextImpl() const {
return NULL;
}
-const std::string &
-XmlElement::BodyText() const {
- if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
- return pFirstChild_->AsText()->Text();
+const std::string XmlElement::BodyText() const {
+ if (first_child_ && first_child_->IsText() && last_child_ == first_child_) {
+ return first_child_->AsText()->Text();
}
- return STR_EMPTY;
+ return std::string();
}
-void
-XmlElement::SetBodyText(const std::string & text) {
- if (text == STR_EMPTY) {
+void XmlElement::SetBodyText(const std::string& text) {
+ if (text.empty()) {
ClearChildren();
- } else if (pFirstChild_ == NULL) {
+ } else if (first_child_ == NULL) {
AddText(text);
- } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
- pFirstChild_->AsText()->SetText(text);
+ } else if (first_child_->IsText() && last_child_ == first_child_) {
+ first_child_->AsText()->SetText(text);
} else {
ClearChildren();
AddText(text);
}
}
-const QName &
-XmlElement::FirstElementName() const {
- const XmlElement * element = FirstElement();
+const QName XmlElement::FirstElementName() const {
+ const XmlElement* element = FirstElement();
if (element == NULL)
- return QN_EMPTY;
+ return QName();
return element->Name();
}
-XmlAttr *
-XmlElement::FirstAttr() {
- return pFirstAttr_;
+XmlAttr* XmlElement::FirstAttr() {
+ return first_attr_;
}
-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_;
+const std::string XmlElement::Attr(const StaticQName& name) const {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->name_ == name)
+ return attr->value_;
}
- return STR_EMPTY;
+ return std::string();
}
-bool
-XmlElement::HasAttr(const QName & name) const {
- XmlAttr * pattr;
- for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
- if (pattr->name_ == name)
+const std::string XmlElement::Attr(const QName& name) const {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->name_ == name)
+ return attr->value_;
+ }
+ return std::string();
+}
+
+bool XmlElement::HasAttr(const StaticQName& name) const {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->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)
+bool XmlElement::HasAttr(const QName& name) const {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->name_ == name)
+ return true;
+ }
+ return false;
+}
+
+void XmlElement::SetAttr(const QName& name, const std::string& value) {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->name_ == name)
break;
}
- if (!pattr) {
- pattr = new XmlAttr(name, value);
- if (pLastAttr_)
- pLastAttr_->pNextAttr_ = pattr;
+ if (!attr) {
+ attr = new XmlAttr(name, value);
+ if (last_attr_)
+ last_attr_->next_attr_ = attr;
else
- pFirstAttr_ = pattr;
- pLastAttr_ = pattr;
+ first_attr_ = attr;
+ last_attr_ = attr;
return;
}
- pattr->value_ = value;
+ attr->value_ = value;
}
-void
-XmlElement::ClearAttr(const QName & name) {
- XmlAttr * pattr;
- XmlAttr *pLastAttr = NULL;
- for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
- if (pattr->name_ == name)
+void XmlElement::ClearAttr(const QName& name) {
+ XmlAttr* attr;
+ XmlAttr* last_attr = NULL;
+ for (attr = first_attr_; attr; attr = attr->next_attr_) {
+ if (attr->name_ == name)
break;
- pLastAttr = pattr;
+ last_attr = attr;
}
- if (!pattr)
+ if (!attr)
return;
- if (!pLastAttr)
- pFirstAttr_ = pattr->pNextAttr_;
+ if (!last_attr)
+ first_attr_ = attr->next_attr_;
else
- pLastAttr->pNextAttr_ = pattr->pNextAttr_;
- if (pLastAttr_ == pattr)
- pLastAttr_ = pLastAttr;
- delete pattr;
+ last_attr->next_attr_ = attr->next_attr_;
+ if (last_attr_ == attr)
+ last_attr_ = last_attr;
+ delete attr;
}
-XmlChild *
-XmlElement::FirstChild() {
- return pFirstChild_;
+XmlChild* XmlElement::FirstChild() {
+ return first_child_;
}
-XmlElement *
-XmlElement::FirstElement() {
- XmlChild * pChild;
- for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::FirstElement() {
+ XmlChild* pChild;
+ for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText())
return pChild->AsElement();
}
return NULL;
}
-XmlElement *
-XmlElement::NextElement() {
- XmlChild * pChild;
- for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::NextElement() {
+ XmlChild* pChild;
+ for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText())
return pChild->AsElement();
}
return NULL;
}
-XmlElement *
-XmlElement::FirstWithNamespace(const std::string & ns) {
- XmlChild * pChild;
- for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement* XmlElement::FirstWithNamespace(const std::string& ns) {
+ XmlChild* pChild;
+ for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
return pChild->AsElement();
}
@@ -282,9 +277,9 @@
}
XmlElement *
-XmlElement::NextWithNamespace(const std::string & ns) {
- XmlChild * pChild;
- for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::NextWithNamespace(const std::string& ns) {
+ XmlChild* pChild;
+ for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
return pChild->AsElement();
}
@@ -292,9 +287,9 @@
}
XmlElement *
-XmlElement::FirstNamed(const QName & name) {
- XmlChild * pChild;
- for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::FirstNamed(const QName& name) {
+ XmlChild* pChild;
+ for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText() && pChild->AsElement()->Name() == name)
return pChild->AsElement();
}
@@ -302,9 +297,29 @@
}
XmlElement *
-XmlElement::NextNamed(const QName & name) {
- XmlChild * pChild;
- for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+XmlElement::FirstNamed(const StaticQName& name) {
+ XmlChild* pChild;
+ for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const QName& name) {
+ XmlChild* pChild;
+ for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const StaticQName& name) {
+ XmlChild* pChild;
+ for (pChild = next_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText() && pChild->AsElement()->Name() == name)
return pChild->AsElement();
}
@@ -321,132 +336,121 @@
return child;
}
-const std::string &
-XmlElement::TextNamed(const QName & name) const {
- XmlChild * pChild;
- for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+const std::string XmlElement::TextNamed(const QName& name) const {
+ XmlChild* pChild;
+ for (pChild = first_child_; pChild; pChild = pChild->next_child_) {
if (!pChild->IsText() && pChild->AsElement()->Name() == name)
return pChild->AsElement()->BodyText();
}
- return STR_EMPTY;
+ return std::string();
}
-void
-XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) {
- if (pPredecessor == NULL) {
- pNext->pNextChild_ = pFirstChild_;
- pFirstChild_ = pNext;
+void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) {
+ if (predecessor == NULL) {
+ next->next_child_ = first_child_;
+ first_child_ = next;
}
else {
- pNext->pNextChild_ = pPredecessor->pNextChild_;
- pPredecessor->pNextChild_ = pNext;
+ next->next_child_ = predecessor->next_child_;
+ predecessor->next_child_ = next;
}
}
-void
-XmlElement::RemoveChildAfter(XmlChild * pPredecessor) {
- XmlChild * pNext;
+void XmlElement::RemoveChildAfter(XmlChild* predecessor) {
+ XmlChild* next;
- if (pPredecessor == NULL) {
- pNext = pFirstChild_;
- pFirstChild_ = pNext->pNextChild_;
+ if (predecessor == NULL) {
+ next = first_child_;
+ first_child_ = next->next_child_;
}
else {
- pNext = pPredecessor->pNextChild_;
- pPredecessor->pNextChild_ = pNext->pNextChild_;
+ next = predecessor->next_child_;
+ predecessor->next_child_ = next->next_child_;
}
- if (pLastChild_ == pNext)
- pLastChild_ = pPredecessor;
+ if (last_child_ == next)
+ last_child_ = predecessor;
- delete pNext;
+ delete next;
}
-void
-XmlElement::AddAttr(const QName & name, const std::string & value) {
+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));
+ XmlAttr ** pprev = last_attr_ ? &(last_attr_->next_attr_) : &first_attr_;
+ last_attr_ = (*pprev = new XmlAttr(name, value));
}
-void
-XmlElement::AddAttr(const QName & name, const std::string & value,
+void XmlElement::AddAttr(const QName& name, const std::string& value,
int depth) {
- XmlElement * element = this;
+ XmlElement* element = this;
while (depth--) {
- element = element->pLastChild_->AsElement();
+ element = element->last_child_->AsElement();
}
element->AddAttr(name, value);
}
-void
-XmlElement::AddParsedText(const char * cstr, int len) {
+void XmlElement::AddParsedText(const char* cstr, int len) {
if (len == 0)
return;
- if (pLastChild_ && pLastChild_->IsText()) {
- pLastChild_->AsText()->AddParsedText(cstr, len);
+ if (last_child_ && last_child_->IsText()) {
+ last_child_->AsText()->AddParsedText(cstr, len);
return;
}
- XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
- pLastChild_ = *pprev = new XmlText(cstr, len);
+ XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+ last_child_ = *pprev = new XmlText(cstr, len);
}
-void
-XmlElement::AddCDATAText(const char * buf, int len) {
+void XmlElement::AddCDATAText(const char* buf, int len) {
cdata_ = true;
AddParsedText(buf, len);
}
-void
-XmlElement::AddText(const std::string & text) {
+void XmlElement::AddText(const std::string& text) {
if (text == STR_EMPTY)
return;
- if (pLastChild_ && pLastChild_->IsText()) {
- pLastChild_->AsText()->AddText(text);
+ if (last_child_ && last_child_->IsText()) {
+ last_child_->AsText()->AddText(text);
return;
}
- XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
- pLastChild_ = *pprev = new XmlText(text);
+ XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+ last_child_ = *pprev = new XmlText(text);
}
-void
-XmlElement::AddText(const std::string & text, int depth) {
+void XmlElement::AddText(const std::string& text, int depth) {
// note: the first syntax is ambigious for msvc 6
- // XmlElement * pel(this);
- XmlElement * element = this;
+ // XmlElement* pel(this);
+ XmlElement* element = this;
while (depth--) {
- element = element->pLastChild_->AsElement();
+ element = element->last_child_->AsElement();
}
element->AddText(text);
}
-void
-XmlElement::AddElement(XmlElement *pelChild) {
- if (pelChild == NULL)
+void XmlElement::AddElement(XmlElement *child) {
+ if (child == NULL)
return;
- XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
- pLastChild_ = *pprev = pelChild;
- pelChild->pNextChild_ = NULL;
+ XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_;
+ *pprev = child;
+ last_child_ = child;
+ child->next_child_ = NULL;
}
-void
-XmlElement::AddElement(XmlElement *pelChild, int depth) {
- XmlElement * element = this;
+void XmlElement::AddElement(XmlElement *child, int depth) {
+ XmlElement* element = this;
while (depth--) {
- element = element->pLastChild_->AsElement();
+ element = element->last_child_->AsElement();
}
- element->AddElement(pelChild);
+ element->AddElement(child);
}
-void
-XmlElement::ClearNamedChildren(const QName & name) {
- XmlChild * prev_child = NULL;
- XmlChild * next_child;
- XmlChild * child;
+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)
@@ -458,62 +462,52 @@
}
}
-void
-XmlElement::ClearAttributes() {
- XmlAttr * pattr;
- for (pattr = pFirstAttr_; pattr; ) {
- XmlAttr * pToDelete = pattr;
- pattr = pattr->pNextAttr_;
- delete pToDelete;
+void XmlElement::ClearAttributes() {
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; ) {
+ XmlAttr* to_delete = attr;
+ attr = attr->next_attr_;
+ delete to_delete;
}
- pFirstAttr_ = pLastAttr_ = NULL;
+ first_attr_ = last_attr_ = NULL;
}
-void
-XmlElement::ClearChildren() {
- XmlChild * pchild;
- for (pchild = pFirstChild_; pchild; ) {
- XmlChild * pToDelete = pchild;
- pchild = pchild->pNextChild_;
- delete pToDelete;
+void XmlElement::ClearChildren() {
+ XmlChild* pchild;
+ for (pchild = first_child_; pchild; ) {
+ XmlChild* to_delete = pchild;
+ pchild = pchild->next_child_;
+ delete to_delete;
}
- pFirstChild_ = pLastChild_ = NULL;
+ first_child_ = last_child_ = NULL;
}
-std::string
-XmlElement::Str() const {
+std::string XmlElement::Str() const {
std::stringstream ss;
- Print(&ss, NULL, 0);
+ XmlPrinter::PrintXml(&ss, this);
return ss.str();
}
-XmlElement *
-XmlElement::ForStr(const std::string & 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;
+ XmlAttr* attr;
+ for (attr = first_attr_; attr; ) {
+ XmlAttr* to_delete = attr;
+ attr = attr->next_attr_;
+ delete to_delete;
}
- XmlChild * pchild;
- for (pchild = pFirstChild_; pchild; ) {
- XmlChild * pToDelete = pchild;
- pchild = pchild->pNextChild_;
- delete pToDelete;
+ XmlChild* pchild;
+ for (pchild = first_child_; pchild; ) {
+ XmlChild* to_delete = pchild;
+ pchild = pchild->next_child_;
+ delete to_delete;
}
}
-}
+} // namespace buzz
diff --git a/talk/xmllite/xmlelement.h b/talk/xmllite/xmlelement.h
index 38e31a9..ffdc333 100644
--- a/talk/xmllite/xmlelement.h
+++ b/talk/xmllite/xmlelement.h
@@ -2,238 +2,250 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_
+#ifndef TALK_XMLLITE_XMLELEMENT_H_
+#define TALK_XMLLITE_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_; }
+ public:
+ XmlChild* NextChild() { return next_child_; }
+ const XmlChild* NextChild() const { return next_child_; }
bool IsText() const { return IsTextImpl(); }
- XmlElement * AsElement() { return AsElementImpl(); }
- const XmlElement * AsElement() const { return AsElementImpl(); }
+ XmlElement* AsElement() { return AsElementImpl(); }
+ const XmlElement* AsElement() const { return AsElementImpl(); }
- XmlText * AsText() { return AsTextImpl(); }
- const XmlText * AsText() const { return AsTextImpl(); }
+ XmlText* AsText() { return AsTextImpl(); }
+ const XmlText* AsText() const { return AsTextImpl(); }
-protected:
-
+ protected:
XmlChild() :
- pNextChild_(NULL) {
+ next_child_(NULL) {
}
virtual bool IsTextImpl() const = 0;
- virtual XmlElement * AsElementImpl() const = 0;
- virtual XmlText * AsTextImpl() const = 0;
+ virtual XmlElement* AsElementImpl() const = 0;
+ virtual XmlText* AsTextImpl() const = 0;
virtual ~XmlChild();
-private:
- XmlChild(const XmlChild & noimpl);
+ private:
+ friend class XmlElement;
- XmlChild * pNextChild_;
+ XmlChild(const XmlChild& noimpl);
+ XmlChild* next_child_;
};
class XmlText : public XmlChild {
-public:
- explicit XmlText(const std::string & text) :
+ public:
+ explicit XmlText(const std::string& text) :
XmlChild(),
text_(text) {
}
- explicit XmlText(const XmlText & t) :
+ explicit XmlText(const XmlText& t) :
XmlChild(),
text_(t.text_) {
}
- explicit XmlText(const char * cstr, size_t len) :
+ 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);
+ 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:
+ protected:
virtual bool IsTextImpl() const;
- virtual XmlElement * AsElementImpl() const;
- virtual XmlText * AsTextImpl() const;
+ virtual XmlElement* AsElementImpl() const;
+ virtual XmlText* AsTextImpl() const;
-private:
+ private:
std::string text_;
};
class XmlAttr {
-friend class XmlElement;
+ public:
+ XmlAttr* NextAttr() const { return next_attr_; }
+ const QName& Name() const { return name_; }
+ const std::string& Value() const { return value_; }
-public:
- XmlAttr * NextAttr() const { return pNextAttr_; }
- const QName & Name() const { return name_; }
- const std::string & Value() const { return value_; }
+ private:
+ friend class XmlElement;
-private:
- explicit XmlAttr(const QName & name, const std::string & value) :
- pNextAttr_(NULL),
+ explicit XmlAttr(const QName& name, const std::string& value) :
+ next_attr_(NULL),
name_(name),
value_(value) {
}
- explicit XmlAttr(const XmlAttr & att) :
- pNextAttr_(NULL),
+ explicit XmlAttr(const XmlAttr& att) :
+ next_attr_(NULL),
name_(att.name_),
value_(att.value_) {
}
- XmlAttr * pNextAttr_;
+ XmlAttr* next_attr_;
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);
+ 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 std::string BodyText() const;
+ void SetBodyText(const std::string& text);
- const QName & FirstElementName() const;
+ const QName FirstElementName() const;
- XmlAttr * FirstAttr();
- const XmlAttr * FirstAttr() 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);
+ // Attr will return an empty string if the attribute isn't there:
+ // use HasAttr to test presence of an attribute.
+ const std::string Attr(const StaticQName& name) const;
+ const std::string Attr(const QName& name) const;
+ bool HasAttr(const StaticQName& 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(); }
+ 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* FirstElement();
+ const XmlElement* FirstElement() const {
+ return const_cast<XmlElement *>(this)->FirstElement();
+ }
- XmlElement * NextElement();
- const XmlElement * NextElement() const
- { return const_cast<XmlElement *>(this)->NextElement(); }
+ 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* 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* 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* FirstNamed(const StaticQName& name);
+ const XmlElement* FirstNamed(const StaticQName& 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); }
+ XmlElement* FirstNamed(const QName& name);
+ const XmlElement* FirstNamed(const QName& name) const {
+ return const_cast<XmlElement *>(this)->FirstNamed(name);
+ }
+
+ XmlElement* NextNamed(const StaticQName& name);
+ const XmlElement* NextNamed(const StaticQName& name) const {
+ return const_cast<XmlElement *>(this)->NextNamed(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;
+ const std::string TextNamed(const QName& name) const;
- void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild);
- void RemoveChildAfter(XmlChild * pPredecessor);
+ void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child);
+ void RemoveChildAfter(XmlChild* predecessor);
- void AddParsedText(const char * buf, int len);
+ 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 AddCDATAText(const char* buf, int len);
+ void AddText(const std::string& text);
+ void AddText(const std::string& text, int depth);
+ void AddElement(XmlElement* child);
+ void AddElement(XmlElement* child, int depth);
+ void AddAttr(const QName& name, const std::string& value);
+ void AddAttr(const QName& name, const std::string& value, int depth);
+ void ClearNamedChildren(const QName& name);
void ClearAttributes();
void ClearChildren();
- static XmlElement * ForStr(const std::string & str);
+ 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:
+ protected:
virtual bool IsTextImpl() const;
- virtual XmlElement * AsElementImpl() const;
- virtual XmlText * AsTextImpl() const;
+ virtual XmlElement* AsElementImpl() const;
+ virtual XmlText* AsTextImpl() const;
-private:
+ private:
QName name_;
- XmlAttr * pFirstAttr_;
- XmlAttr * pLastAttr_;
- XmlChild * pFirstChild_;
- XmlChild * pLastChild_;
+ XmlAttr* first_attr_;
+ XmlAttr* last_attr_;
+ XmlChild* first_child_;
+ XmlChild* last_child_;
bool cdata_;
};
-}
-#endif
+} // namespace buzz
+
+#endif // TALK_XMLLITE_XMLELEMENT_H_
diff --git a/talk/xmllite/xmlelement_unittest.cc b/talk/xmllite/xmlelement_unittest.cc
new file mode 100644
index 0000000..6d488fa
--- /dev/null
+++ b/talk/xmllite/xmlelement_unittest.cc
@@ -0,0 +1,271 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/xmllite/xmlelement.h"
+
+using buzz::QName;
+using buzz::XmlAttr;
+using buzz::XmlChild;
+using buzz::XmlElement;
+
+std::ostream& operator<<(std::ostream& os, const QName& name) {
+ os << name.Namespace() << ":" << name.LocalPart();
+ return os;
+}
+
+TEST(XmlElementTest, TestConstructors) {
+ XmlElement elt(QName("google:test", "first"));
+ EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", elt.Str());
+
+ XmlElement elt2(QName("google:test", "first"), true);
+ EXPECT_EQ("<first xmlns=\"google:test\"/>", elt2.Str());
+}
+
+TEST(XmlElementTest, TestAdd) {
+ XmlElement elt(QName("google:test", "root"), true);
+ elt.AddElement(new XmlElement(QName("google:test", "first")));
+ elt.AddElement(new XmlElement(QName("google:test", "nested")), 1);
+ elt.AddText("nested-value", 2);
+ elt.AddText("between-", 1);
+ elt.AddText("value", 1);
+ elt.AddElement(new XmlElement(QName("google:test", "nested2")), 1);
+ elt.AddElement(new XmlElement(QName("google:test", "second")));
+ elt.AddText("init-value", 1);
+ elt.AddElement(new XmlElement(QName("google:test", "nested3")), 1);
+ elt.AddText("trailing-value", 1);
+
+ // make sure it looks ok overall
+ EXPECT_EQ("<root xmlns=\"google:test\">"
+ "<first><nested>nested-value</nested>between-value<nested2/></first>"
+ "<second>init-value<nested3/>trailing-value</second></root>",
+ elt.Str());
+
+ // make sure text was concatenated
+ XmlChild * pchild =
+ elt.FirstChild()->AsElement()->FirstChild()->NextChild();
+ EXPECT_TRUE(pchild->IsText());
+ EXPECT_EQ("between-value", pchild->AsText()->Text());
+}
+
+TEST(XmlElementTest, TestAttrs) {
+ XmlElement elt(QName("", "root"));
+ elt.SetAttr(QName("", "a"), "avalue");
+ EXPECT_EQ("<root a=\"avalue\"/>", elt.Str());
+
+ elt.SetAttr(QName("", "b"), "bvalue");
+ EXPECT_EQ("<root a=\"avalue\" b=\"bvalue\"/>", elt.Str());
+
+ elt.SetAttr(QName("", "a"), "avalue2");
+ EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue\"/>", elt.Str());
+
+ elt.SetAttr(QName("", "b"), "bvalue2");
+ EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\"/>", elt.Str());
+
+ elt.SetAttr(QName("", "c"), "cvalue");
+ EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\"/>", elt.Str());
+
+ XmlAttr * patt = elt.FirstAttr();
+ EXPECT_EQ(QName("", "a"), patt->Name());
+ EXPECT_EQ("avalue2", patt->Value());
+
+ patt = patt->NextAttr();
+ EXPECT_EQ(QName("", "b"), patt->Name());
+ EXPECT_EQ("bvalue2", patt->Value());
+
+ patt = patt->NextAttr();
+ EXPECT_EQ(QName("", "c"), patt->Name());
+ EXPECT_EQ("cvalue", patt->Value());
+
+ patt = patt->NextAttr();
+ EXPECT_TRUE(NULL == patt);
+
+ EXPECT_TRUE(elt.HasAttr(QName("", "a")));
+ EXPECT_TRUE(elt.HasAttr(QName("", "b")));
+ EXPECT_TRUE(elt.HasAttr(QName("", "c")));
+ EXPECT_FALSE(elt.HasAttr(QName("", "d")));
+
+ elt.SetAttr(QName("", "d"), "dvalue");
+ EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>",
+ elt.Str());
+ EXPECT_TRUE(elt.HasAttr(QName("", "d")));
+
+ elt.ClearAttr(QName("", "z")); // not found, no effect
+ EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>",
+ elt.Str());
+
+ elt.ClearAttr(QName("", "b"));
+ EXPECT_EQ("<root a=\"avalue2\" c=\"cvalue\" d=\"dvalue\"/>", elt.Str());
+
+ elt.ClearAttr(QName("", "a"));
+ EXPECT_EQ("<root c=\"cvalue\" d=\"dvalue\"/>", elt.Str());
+
+ elt.ClearAttr(QName("", "d"));
+ EXPECT_EQ("<root c=\"cvalue\"/>", elt.Str());
+
+ elt.ClearAttr(QName("", "c"));
+ EXPECT_EQ("<root/>", elt.Str());
+}
+
+TEST(XmlElementTest, TestBodyText) {
+ XmlElement elt(QName("", "root"));
+ EXPECT_EQ("", elt.BodyText());
+
+ elt.AddText("body value text");
+
+ EXPECT_EQ("body value text", elt.BodyText());
+
+ elt.ClearChildren();
+ elt.AddText("more value ");
+ elt.AddText("text");
+
+ EXPECT_EQ("more value text", elt.BodyText());
+
+ elt.ClearChildren();
+ elt.AddText("decoy");
+ elt.AddElement(new XmlElement(QName("", "dummy")));
+ EXPECT_EQ("", elt.BodyText());
+
+ elt.SetBodyText("replacement");
+ EXPECT_EQ("replacement", elt.BodyText());
+
+ elt.SetBodyText("");
+ EXPECT_TRUE(NULL == elt.FirstChild());
+
+ elt.SetBodyText("goodbye");
+ EXPECT_EQ("goodbye", elt.FirstChild()->AsText()->Text());
+ EXPECT_EQ("goodbye", elt.BodyText());
+}
+
+TEST(XmlElementTest, TestCopyConstructor) {
+ XmlElement * element = XmlElement::ForStr(
+ "<root xmlns='test-foo'>This is a <em a='avalue' b='bvalue'>"
+ "little <b>little</b></em> test</root>");
+
+ XmlElement * pelCopy = new XmlElement(*element);
+ EXPECT_EQ("<root xmlns=\"test-foo\">This is a <em a=\"avalue\" b=\"bvalue\">"
+ "little <b>little</b></em> test</root>", pelCopy->Str());
+ delete pelCopy;
+
+ pelCopy = new XmlElement(*(element->FirstChild()->NextChild()->AsElement()));
+ EXPECT_EQ("<foo:em a=\"avalue\" b=\"bvalue\" xmlns:foo=\"test-foo\">"
+ "little <foo:b>little</foo:b></foo:em>", pelCopy->Str());
+
+ XmlAttr * patt = pelCopy->FirstAttr();
+ EXPECT_EQ(QName("", "a"), patt->Name());
+ EXPECT_EQ("avalue", patt->Value());
+
+ patt = patt->NextAttr();
+ EXPECT_EQ(QName("", "b"), patt->Name());
+ EXPECT_EQ("bvalue", patt->Value());
+
+ patt = patt->NextAttr();
+ EXPECT_TRUE(NULL == patt);
+ delete pelCopy;
+ delete element;
+}
+
+TEST(XmlElementTest, TestNameSearch) {
+ XmlElement * element = XmlElement::ForStr(
+ "<root xmlns='test-foo'>"
+ "<firstname>George</firstname>"
+ "<middlename>X.</middlename>"
+ "some text"
+ "<lastname>Harrison</lastname>"
+ "<firstname>John</firstname>"
+ "<middlename>Y.</middlename>"
+ "<lastname>Lennon</lastname>"
+ "</root>");
+ EXPECT_TRUE(NULL ==
+ element->FirstNamed(QName("", "firstname")));
+ EXPECT_EQ(element->FirstChild(),
+ element->FirstNamed(QName("test-foo", "firstname")));
+ EXPECT_EQ(element->FirstChild()->NextChild(),
+ element->FirstNamed(QName("test-foo", "middlename")));
+ EXPECT_EQ(element->FirstElement()->NextElement(),
+ element->FirstNamed(QName("test-foo", "middlename")));
+ EXPECT_EQ("Harrison",
+ element->TextNamed(QName("test-foo", "lastname")));
+ EXPECT_EQ(element->FirstElement()->NextElement()->NextElement(),
+ element->FirstNamed(QName("test-foo", "lastname")));
+ EXPECT_EQ("John", element->FirstNamed(QName("test-foo", "firstname"))->
+ NextNamed(QName("test-foo", "firstname"))->BodyText());
+ EXPECT_EQ("Y.", element->FirstNamed(QName("test-foo", "middlename"))->
+ NextNamed(QName("test-foo", "middlename"))->BodyText());
+ EXPECT_EQ("Lennon", element->FirstNamed(QName("test-foo", "lastname"))->
+ NextNamed(QName("test-foo", "lastname"))->BodyText());
+ EXPECT_TRUE(NULL == element->FirstNamed(QName("test-foo", "firstname"))->
+ NextNamed(QName("test-foo", "firstname"))->
+ NextNamed(QName("test-foo", "firstname")));
+
+ delete element;
+}
+
+class XmlElementCreatorThread : public talk_base::Thread {
+ public:
+ XmlElementCreatorThread(int count, buzz::QName qname) :
+ count_(count), qname_(qname) {}
+
+ virtual void Run() {
+ std::vector<buzz::XmlElement*> elems;
+ for (int i = 0; i < count_; i++) {
+ elems.push_back(new XmlElement(qname_));
+ }
+ for (int i = 0; i < count_; i++) {
+ delete elems[i];
+ }
+ }
+
+ private:
+ int count_;
+ buzz::QName qname_;
+};
+
+// If XmlElement creation and destruction isn't thread safe,
+// this test should crash.
+TEST(XmlElementTest, TestMultithread) {
+ int thread_count = 2; // Was 100, but that's too slow.
+ int elem_count = 100; // Was 100000, but that's too slow.
+ buzz::QName qname("foo", "bar");
+
+ std::vector<talk_base::Thread*> threads;
+ for (int i = 0; i < thread_count; i++) {
+ threads.push_back(
+ new XmlElementCreatorThread(elem_count, qname));
+ threads[i]->Start();
+ }
+
+ for (int i = 0; i < thread_count; i++) {
+ threads[i]->Stop();
+ delete threads[i];
+ }
+}
diff --git a/talk/xmllite/xmlnsstack.cc b/talk/xmllite/xmlnsstack.cc
index b2bf370..26e27f8 100644
--- a/talk/xmllite/xmlnsstack.cc
+++ b/talk/xmllite/xmlnsstack.cc
@@ -25,11 +25,13 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/xmllite/xmlnsstack.h"
+
+#include <sstream>
#include <string>
#include <vector>
-#include <sstream>
+
#include "talk/xmllite/xmlelement.h"
-#include "talk/xmllite/xmlnsstack.h"
#include "talk/xmllite/xmlconstants.h"
namespace buzz {
@@ -41,13 +43,11 @@
XmlnsStack::~XmlnsStack() {}
-void
-XmlnsStack::PushFrame() {
+void XmlnsStack::PushFrame() {
pxmlnsDepthStack_->push_back(pxmlnsStack_->size());
}
-void
-XmlnsStack::PopFrame() {
+void XmlnsStack::PopFrame() {
size_t prev_size = pxmlnsDepthStack_->back();
pxmlnsDepthStack_->pop_back();
if (prev_size < pxmlnsStack_->size()) {
@@ -56,42 +56,41 @@
}
}
-const std::string *
-XmlnsStack::NsForPrefix(const std::string & prefix) {
+std::pair<std::string, bool> 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);
+ return std::make_pair(NS_XML, true);
if (prefix == "xmlns")
- return &(NS_XMLNS);
- return NULL;
+ return std::make_pair(NS_XMLNS, true);
+ // Other names with xml prefix are illegal.
+ return std::make_pair(STR_EMPTY, false);
}
std::vector<std::string>::iterator pos;
for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
pos -= 2;
if (*pos == prefix)
- return &(*(pos + 1));
+ return std::make_pair(*(pos + 1), true);
}
if (prefix == STR_EMPTY)
- return &(STR_EMPTY); // default namespace
+ return std::make_pair(STR_EMPTY, true); // default namespace
- return NULL; // none found
+ return std::make_pair(STR_EMPTY, false); // 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);
+bool XmlnsStack::PrefixMatchesNs(const std::string& prefix,
+ const std::string& ns) {
+ const std::pair<std::string, bool> match = NsForPrefix(prefix);
+ return match.second && (match.first == ns);
}
-std::pair<std::string, bool>
-XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) {
+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)
@@ -110,8 +109,7 @@
return std::make_pair(STR_EMPTY, false); // none found
}
-std::string
-XmlnsStack::FormatQName(const QName & name, bool isAttr) {
+std::string XmlnsStack::FormatQName(const QName& name, bool isAttr) {
std::string prefix(PrefixForNs(name.Namespace(), isAttr).first);
if (prefix == STR_EMPTY)
return name.LocalPart();
@@ -119,14 +117,12 @@
return prefix + ':' + name.LocalPart();
}
-void
-XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
+void XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
pxmlnsStack_->push_back(prefix);
pxmlnsStack_->push_back(ns);
}
-void
-XmlnsStack::RemoveXmlns() {
+void XmlnsStack::RemoveXmlns() {
pxmlnsStack_->pop_back();
pxmlnsStack_->pop_back();
}
@@ -173,16 +169,15 @@
return "ns";
}
-
-std::pair<std::string, bool>
-XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) {
+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) {
+ while (NsForPrefix(result).second) {
std::stringstream ss;
ss << base;
ss << (i++);
diff --git a/talk/xmllite/xmlnsstack.h b/talk/xmllite/xmlnsstack.h
index c7e9f89..f6b4b81 100644
--- a/talk/xmllite/xmlnsstack.h
+++ b/talk/xmllite/xmlnsstack.h
@@ -25,8 +25,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _xmlnsstack_h_
-#define _xmlnsstack_h_
+#ifndef TALK_XMLLITE_XMLNSSTACK_H_
+#define TALK_XMLLITE_XMLNSSTACK_H_
#include <string>
#include <vector>
@@ -40,16 +40,16 @@
XmlnsStack();
~XmlnsStack();
- void AddXmlns(const std::string & prefix, const std::string & ns);
+ 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);
+ std::pair<std::string, bool> 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::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:
@@ -59,4 +59,4 @@
};
}
-#endif
+#endif // TALK_XMLLITE_XMLNSSTACK_H_
diff --git a/talk/xmllite/xmlnsstack_unittest.cc b/talk/xmllite/xmlnsstack_unittest.cc
new file mode 100644
index 0000000..21e157b
--- /dev/null
+++ b/talk/xmllite/xmlnsstack_unittest.cc
@@ -0,0 +1,258 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlnsstack.h"
+
+#include <string>
+#include <sstream>
+#include <iostream>
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlconstants.h"
+
+using buzz::NS_XML;
+using buzz::NS_XMLNS;
+using buzz::QName;
+using buzz::XmlnsStack;
+
+TEST(XmlnsStackTest, TestBuiltin) {
+ XmlnsStack stack;
+
+ EXPECT_EQ(std::string(NS_XML), stack.NsForPrefix("xml").first);
+ EXPECT_EQ(std::string(NS_XMLNS), stack.NsForPrefix("xmlns").first);
+ EXPECT_EQ("", stack.NsForPrefix("").first);
+
+ EXPECT_EQ("xml", stack.PrefixForNs(NS_XML, false).first);
+ EXPECT_EQ("xmlns", stack.PrefixForNs(NS_XMLNS, false).first);
+ EXPECT_EQ("", stack.PrefixForNs("", false).first);
+ EXPECT_EQ("", stack.PrefixForNs("", true).first);
+}
+
+TEST(XmlnsStackTest, TestNsForPrefix) {
+ XmlnsStack stack;
+ stack.AddXmlns("pre1", "ns1");
+ stack.AddXmlns("pre2", "ns2");
+ stack.AddXmlns("pre1", "ns3");
+ stack.AddXmlns("", "ns4");
+
+ EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+ EXPECT_TRUE(stack.NsForPrefix("pre1").second);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("ns4", stack.NsForPrefix("").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+ EXPECT_EQ(false, stack.NsForPrefix("pre3").second);
+}
+
+TEST(XmlnsStackTest, TestPrefixForNs) {
+ XmlnsStack stack;
+ stack.AddXmlns("pre1", "ns1");
+ stack.AddXmlns("pre2", "ns2");
+ stack.AddXmlns("pre1", "ns3");
+ stack.AddXmlns("pre3", "ns2");
+ stack.AddXmlns("pre4", "ns4");
+ stack.AddXmlns("", "ns4");
+
+ EXPECT_EQ("", stack.PrefixForNs("ns1", false).first);
+ EXPECT_FALSE(stack.PrefixForNs("ns1", false).second);
+ EXPECT_EQ("", stack.PrefixForNs("ns1", true).first);
+ EXPECT_FALSE(stack.PrefixForNs("ns1", true).second);
+ EXPECT_EQ("pre3", stack.PrefixForNs("ns2", false).first);
+ EXPECT_TRUE(stack.PrefixForNs("ns2", false).second);
+ EXPECT_EQ("pre3", stack.PrefixForNs("ns2", true).first);
+ EXPECT_TRUE(stack.PrefixForNs("ns2", true).second);
+ EXPECT_EQ("pre1", stack.PrefixForNs("ns3", false).first);
+ EXPECT_EQ("pre1", stack.PrefixForNs("ns3", true).first);
+ EXPECT_EQ("", stack.PrefixForNs("ns4", false).first);
+ EXPECT_TRUE(stack.PrefixForNs("ns4", false).second);
+ EXPECT_EQ("pre4", stack.PrefixForNs("ns4", true).first);
+ EXPECT_EQ("", stack.PrefixForNs("ns5", false).first);
+ EXPECT_FALSE(stack.PrefixForNs("ns5", false).second);
+ EXPECT_EQ("", stack.PrefixForNs("ns5", true).first);
+ EXPECT_EQ("", stack.PrefixForNs("", false).first);
+ EXPECT_EQ("", stack.PrefixForNs("", true).first);
+
+ stack.AddXmlns("", "ns6");
+ EXPECT_EQ("", stack.PrefixForNs("ns6", false).first);
+ EXPECT_TRUE(stack.PrefixForNs("ns6", false).second);
+ EXPECT_EQ("", stack.PrefixForNs("ns6", true).first);
+ EXPECT_FALSE(stack.PrefixForNs("ns6", true).second);
+}
+
+TEST(XmlnsStackTest, TestFrames) {
+ XmlnsStack stack;
+ stack.PushFrame();
+ stack.AddXmlns("pre1", "ns1");
+ stack.AddXmlns("pre2", "ns2");
+
+ stack.PushFrame();
+ stack.AddXmlns("pre1", "ns3");
+ stack.AddXmlns("pre3", "ns2");
+ stack.AddXmlns("pre4", "ns4");
+
+ stack.PushFrame();
+ stack.PushFrame();
+ stack.AddXmlns("", "ns4");
+
+ // basic test
+ EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+ EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+ EXPECT_EQ("ns4", stack.NsForPrefix("").first);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+
+ // pop the default xmlns definition
+ stack.PopFrame();
+ EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+ EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+ EXPECT_EQ("", stack.NsForPrefix("").first);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+
+ // pop empty frame (nop)
+ stack.PopFrame();
+ EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first);
+ EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+ EXPECT_EQ("", stack.NsForPrefix("").first);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+
+ // pop frame with three defs
+ stack.PopFrame();
+ EXPECT_EQ("ns1", stack.NsForPrefix("pre1").first);
+ EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre3").second);
+ EXPECT_EQ("", stack.NsForPrefix("pre4").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre4").second);
+ EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+ EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+ EXPECT_EQ("", stack.NsForPrefix("").first);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+
+ // pop frame with last two defs
+ stack.PopFrame();
+ EXPECT_FALSE(stack.NsForPrefix("pre1").second);
+ EXPECT_FALSE(stack.NsForPrefix("pre2").second);
+ EXPECT_FALSE(stack.NsForPrefix("pre3").second);
+ EXPECT_FALSE(stack.NsForPrefix("pre4").second);
+ EXPECT_FALSE(stack.NsForPrefix("pre5").second);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+ EXPECT_EQ("", stack.NsForPrefix("pre1").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre2").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre3").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre4").first);
+ EXPECT_EQ("", stack.NsForPrefix("pre5").first);
+ EXPECT_EQ("", stack.NsForPrefix("").first);
+}
+
+TEST(XmlnsStackTest, TestAddNewPrefix) {
+ XmlnsStack stack;
+
+ // builtin namespaces cannot be added
+ EXPECT_FALSE(stack.AddNewPrefix("", true).second);
+ EXPECT_FALSE(stack.AddNewPrefix("", false).second);
+ EXPECT_FALSE(stack.AddNewPrefix(NS_XML, true).second);
+ EXPECT_FALSE(stack.AddNewPrefix(NS_XML, false).second);
+ EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, true).second);
+ EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, false).second);
+
+ // namespaces already added cannot be added again.
+ EXPECT_EQ("foo", stack.AddNewPrefix("http://a.b.com/foo.htm", true).first);
+ EXPECT_EQ("bare", stack.AddNewPrefix("http://a.b.com/bare", false).first);
+ EXPECT_EQ("z", stack.AddNewPrefix("z", false).first);
+ EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", true).second);
+ EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", true).second);
+ EXPECT_FALSE(stack.AddNewPrefix("z", true).second);
+ EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", false).second);
+ EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", false).second);
+ EXPECT_FALSE(stack.AddNewPrefix("z", false).second);
+
+ // default namespace usable by non-attributes only
+ stack.AddXmlns("", "http://my/default");
+ EXPECT_FALSE(stack.AddNewPrefix("http://my/default", false).second);
+ EXPECT_EQ("def", stack.AddNewPrefix("http://my/default", true).first);
+
+ // namespace cannot start with 'xml'
+ EXPECT_EQ("ns", stack.AddNewPrefix("http://a.b.com/xmltest", true).first);
+ EXPECT_EQ("ns2", stack.AddNewPrefix("xmlagain", false).first);
+
+ // verify added namespaces are still defined
+ EXPECT_EQ("http://a.b.com/foo.htm", stack.NsForPrefix("foo").first);
+ EXPECT_TRUE(stack.NsForPrefix("foo").second);
+ EXPECT_EQ("http://a.b.com/bare", stack.NsForPrefix("bare").first);
+ EXPECT_TRUE(stack.NsForPrefix("bare").second);
+ EXPECT_EQ("z", stack.NsForPrefix("z").first);
+ EXPECT_TRUE(stack.NsForPrefix("z").second);
+ EXPECT_EQ("http://my/default", stack.NsForPrefix("").first);
+ EXPECT_TRUE(stack.NsForPrefix("").second);
+ EXPECT_EQ("http://my/default", stack.NsForPrefix("def").first);
+ EXPECT_TRUE(stack.NsForPrefix("def").second);
+ EXPECT_EQ("http://a.b.com/xmltest", stack.NsForPrefix("ns").first);
+ EXPECT_TRUE(stack.NsForPrefix("ns").second);
+ EXPECT_EQ("xmlagain", stack.NsForPrefix("ns2").first);
+ EXPECT_TRUE(stack.NsForPrefix("ns2").second);
+}
+
+TEST(XmlnsStackTest, TestFormatQName) {
+ XmlnsStack stack;
+ stack.AddXmlns("pre1", "ns1");
+ stack.AddXmlns("pre2", "ns2");
+ stack.AddXmlns("pre1", "ns3");
+ stack.AddXmlns("", "ns4");
+
+ EXPECT_EQ("zip",
+ stack.FormatQName(QName("ns1", "zip"), false)); // no match
+ EXPECT_EQ("pre2:abracadabra",
+ stack.FormatQName(QName("ns2", "abracadabra"), false));
+ EXPECT_EQ("pre1:a",
+ stack.FormatQName(QName("ns3", "a"), false));
+ EXPECT_EQ("simple",
+ stack.FormatQName(QName("ns4", "simple"), false));
+ EXPECT_EQ("root",
+ stack.FormatQName(QName("", "root"), false)); // no match
+
+ EXPECT_EQ("zip",
+ stack.FormatQName(QName("ns1", "zip"), true)); // no match
+ EXPECT_EQ("pre2:abracadabra",
+ stack.FormatQName(QName("ns2", "abracadabra"), true));
+ EXPECT_EQ("pre1:a",
+ stack.FormatQName(QName("ns3", "a"), true));
+ EXPECT_EQ("simple",
+ stack.FormatQName(QName("ns4", "simple"), true)); // no match
+ EXPECT_EQ("root",
+ stack.FormatQName(QName("", "root"), true));
+}
diff --git a/talk/xmllite/xmlparser.cc b/talk/xmllite/xmlparser.cc
index b267414..3e4d733 100644
--- a/talk/xmllite/xmlparser.cc
+++ b/talk/xmllite/xmlparser.cc
@@ -29,6 +29,7 @@
#include <string>
#include <vector>
+
#include "talk/base/common.h"
#include "talk/xmllite/xmlconstants.h"
#include "talk/xmllite/xmlelement.h"
@@ -206,12 +207,7 @@
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));
+ xmlnsstack_.AddXmlns(*prefix ? prefix : STR_EMPTY, ns);
}
void
@@ -225,28 +221,25 @@
}
QName
-XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) {
+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);
+ const std::pair<std::string, bool> result =
+ xmlnsstack_.NsForPrefix(std::string(qname, c - qname));
+ if (!result.second)
+ return QName();
+ return QName(result.first, c + 1);
}
}
- if (isAttr) {
+ if (isAttr)
return QName(STR_EMPTY, qname);
- }
- const std::string * result;
- result = xmlnsstack_.NsForPrefix(STR_EMPTY);
- if (result == NULL)
- return QN_EMPTY;
+ std::pair<std::string, bool> result = xmlnsstack_.NsForPrefix(STR_EMPTY);
+ if (!result.second)
+ return QName();
- return QName(*result, qname);
+ return QName(result.first, qname);
}
void
@@ -283,4 +276,4 @@
XmlParser::ParseContext::~ParseContext() {
}
-}
+} // namespace buzz
diff --git a/talk/xmllite/xmlparser.h b/talk/xmllite/xmlparser.h
index b461e7e..69cde75 100644
--- a/talk/xmllite/xmlparser.h
+++ b/talk/xmllite/xmlparser.h
@@ -25,8 +25,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _xmlparser_h_
-#define _xmlparser_h_
+#ifndef TALK_XMLLITE_XMLPARSER_H_
+#define TALK_XMLLITE_XMLPARSER_H_
#include <string>
@@ -116,6 +116,6 @@
bool sentError_;
};
-}
+} // namespace buzz
-#endif
+#endif // TALK_XMLLITE_XMLPARSER_H_
diff --git a/talk/xmllite/xmlparser_unittest.cc b/talk/xmllite/xmlparser_unittest.cc
new file mode 100644
index 0000000..24947fb
--- /dev/null
+++ b/talk/xmllite/xmlparser_unittest.cc
@@ -0,0 +1,302 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlparser.h"
+
+using buzz::QName;
+using buzz::XmlParser;
+using buzz::XmlParseContext;
+using buzz::XmlParseHandler;
+
+class XmlParserTestHandler : public XmlParseHandler {
+ public:
+ virtual void StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts) {
+ ss_ << "START (" << pctx->ResolveQName(name, false).Merged();
+ while (*atts) {
+ ss_ << ", " << pctx->ResolveQName(*atts, true).Merged()
+ << "='" << *(atts+1) << "'";
+ atts += 2;
+ }
+ ss_ << ") ";
+ }
+ virtual void EndElement(XmlParseContext * pctx, const char * name) {
+ UNUSED(pctx);
+ UNUSED(name);
+ ss_ << "END ";
+ }
+ virtual void CharacterData(XmlParseContext * pctx,
+ const char * text, int len) {
+ UNUSED(pctx);
+ ss_ << "TEXT (" << std::string(text, len) << ") ";
+ }
+ virtual void Error(XmlParseContext * pctx, XML_Error code) {
+ UNUSED(pctx);
+ ss_ << "ERROR (" << static_cast<int>(code) << ") ";
+ }
+ virtual ~XmlParserTestHandler() {
+ }
+
+ std::string Str() {
+ return ss_.str();
+ }
+
+ std::string StrClear() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+ private:
+ std::stringstream ss_;
+};
+
+
+TEST(XmlParserTest, TestTrivial) {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<testing/>");
+ EXPECT_EQ("START (testing) END ", handler.Str());
+}
+
+TEST(XmlParserTest, TestAttributes) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<testing a='b'/>");
+ EXPECT_EQ("START (testing, a='b') END ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<testing e='' long='some text'/>");
+ EXPECT_EQ("START (testing, e='', long='some text') END ", handler.Str());
+ }
+}
+
+TEST(XmlParserTest, TestNesting) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<top><first/><second><third></third></second></top>");
+ EXPECT_EQ("START (top) START (first) END START (second) START (third) "
+ "END END END ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<top><fifth><deeper><and><deeper/></and>"
+ "<sibling><leaf/></sibling></deeper></fifth><first/><second>"
+ "<third></third></second></top>");
+ EXPECT_EQ("START (top) START (fifth) START (deeper) START (and) START "
+ "(deeper) END END START (sibling) START (leaf) END END END "
+ "END START (first) END START (second) START (third) END END END ",
+ handler.Str());
+ }
+}
+
+TEST(XmlParserTest, TestXmlDecl) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<?xml version=\"1.0\"?><testing/>");
+ EXPECT_EQ("START (testing) END ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?><testing/>");
+ EXPECT_EQ("START (testing) END ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<testing/>");
+ EXPECT_EQ("START (testing) END ", handler.Str());
+ }
+}
+
+TEST(XmlParserTest, TestNamespace) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<top xmlns='my-namespace' a='b'/>");
+ EXPECT_EQ("START (my-namespace:top, xmlns='my-namespace', a='b') END ",
+ handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<foo:top xmlns:foo='my-namespace' "
+ "a='b' foo:c='d'/>");
+ EXPECT_EQ("START (my-namespace:top, "
+ "http://www.w3.org/2000/xmlns/:foo='my-namespace', "
+ "a='b', my-namespace:c='d') END ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<top><nested xmlns='my-namespace'><leaf/>"
+ "</nested><sibling/></top>");
+ EXPECT_EQ("START (top) START (my-namespace:nested, xmlns='my-namespace') "
+ "START (my-namespace:leaf) END END START (sibling) END END ",
+ handler.Str());
+ }
+}
+
+TEST(XmlParserTest, TestIncremental) {
+ XmlParserTestHandler handler;
+ XmlParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = " id=\"abcdefg\" xmlns=\"";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "j:c\" xmlns:stream='hm";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "ph'><test";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (hmph:stream, id='abcdefg', xmlns='j:c', "
+ "http://www.w3.org/2000/xmlns/:stream='hmph') ", handler.StrClear());
+
+ fragment = "ing/><again/>abracad";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (j:c:testing) END START (j:c:again) END TEXT (abracad) ",
+ handler.StrClear());
+
+ fragment = "abra</stream:";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("TEXT (abra) ", handler.StrClear());
+
+ fragment = "stream>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("END ", handler.StrClear());
+}
+
+TEST(XmlParserTest, TestReset) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<top><first/><second><third></third>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (top) START (first) END START (second) START (third) END ",
+ handler.StrClear());
+
+ parser.Reset();
+ fragment = "<tip><first/><second><third></third>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (tip) START (first) END START (second) START (third) END ",
+ handler.StrClear());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<top xmlns='m'>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (m:top, xmlns='m') ", handler.StrClear());
+
+ fragment = "<testing/><frag";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (m:testing) END ", handler.StrClear());
+
+ parser.Reset();
+ fragment = "<testing><fragment/";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (testing) ", handler.StrClear());
+
+ fragment = ">";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START (fragment) END ", handler.StrClear());
+ }
+}
+
+TEST(XmlParserTest, TestError) {
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "junk");
+ EXPECT_EQ("ERROR (2) ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<top/> garbage ");
+ EXPECT_EQ("START (top) END ERROR (9) ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<-hm->");
+ EXPECT_EQ("ERROR (4) ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler, "<hello>&foobar;</hello>");
+ EXPECT_EQ("START (hello) ERROR (11) ", handler.Str());
+ }
+ {
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<!DOCTYPE HTML PUBLIC \"foobar\" \"barfoo\">");
+ EXPECT_EQ("ERROR (3) ", handler.Str());
+ }
+ {
+ // XmlParser requires utf-8
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><test/>");
+ EXPECT_EQ("ERROR (19) ", handler.Str());
+ }
+ {
+ // XmlParser requires version 1.0
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<?xml version=\"2.0\"?><test/>");
+ EXPECT_EQ("ERROR (2) ", handler.Str());
+ }
+ {
+ // XmlParser requires standalone documents
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<?xml version=\"1.0\" standalone=\"no\"?><test/>");
+ EXPECT_EQ("ERROR (2) ", handler.Str());
+ }
+ {
+ // XmlParser doesn't like empty namespace URIs
+ XmlParserTestHandler handler;
+ XmlParser::ParseXml(&handler,
+ "<test xmlns:foo='' foo:bar='huh?'>");
+ EXPECT_EQ("ERROR (2) ", handler.Str());
+ }
+}
diff --git a/talk/xmllite/xmlprinter.cc b/talk/xmllite/xmlprinter.cc
index db6704b..1350454 100644
--- a/talk/xmllite/xmlprinter.cc
+++ b/talk/xmllite/xmlprinter.cc
@@ -25,130 +25,125 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <ostream>
+#include "talk/xmllite/xmlprinter.h"
+
#include <sstream>
#include <string>
#include <vector>
-#include "talk/xmllite/xmlelement.h"
-#include "talk/xmllite/xmlprinter.h"
-#include "talk/xmllite/xmlnsstack.h"
+
#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.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);
+ XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack);
+ 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_;
+ XmlnsStack* ns_stack_;
};
-void
-XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) {
- PrintXml(pout, element, NULL, 0);
+void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element) {
+ XmlnsStack ns_stack;
+ PrintXml(pout, element, &ns_stack);
}
-void
-XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element,
- const std::string * const xmlns, int xmlnsCount) {
- XmlPrinterImpl printer(pout, xmlns, xmlnsCount);
+void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element,
+ XmlnsStack* ns_stack) {
+ XmlPrinterImpl printer(pout, ns_stack);
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]);
- }
+XmlPrinterImpl::XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack)
+ : pout_(pout),
+ ns_stack_(ns_stack) {
}
-void
-XmlPrinterImpl::PrintElement(const XmlElement * element) {
- xmlnsStack_.PushFrame();
+void XmlPrinterImpl::PrintElement(const XmlElement* element) {
+ ns_stack_->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());
+ const XmlAttr* attr;
+ for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+ if (attr->Name() == QN_XMLNS) {
+ ns_stack_->AddXmlns(STR_EMPTY, attr->Value());
+ } else if (attr->Name().Namespace() == NS_XMLNS) {
+ ns_stack_->AddXmlns(attr->Name().LocalPart(),
+ attr->Value());
+ }
}
// then go through qnames to make sure needed xmlns definitons are added
- std::vector<std::string> newXmlns;
+ std::vector<std::string> new_ns;
std::pair<std::string, bool> prefix;
- prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false);
+ prefix = ns_stack_->AddNewPrefix(element->Name().Namespace(), false);
if (prefix.second) {
- newXmlns.push_back(prefix.first);
- newXmlns.push_back(element->Name().Namespace());
+ new_ns.push_back(prefix.first);
+ new_ns.push_back(element->Name().Namespace());
}
- for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
- prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true);
+ for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+ prefix = ns_stack_->AddNewPrefix(attr->Name().Namespace(), true);
if (prefix.second) {
- newXmlns.push_back(prefix.first);
- newXmlns.push_back(pattr->Name().Namespace());
+ new_ns.push_back(prefix.first);
+ new_ns.push_back(attr->Name().Namespace());
}
}
// print the element name
- *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false);
+ *pout_ << '<' << ns_stack_->FormatQName(element->Name(), false);
// and the attributes
- for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
- *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\"";
- PrintQuotedValue(pattr->Value());
+ for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) {
+ *pout_ << ' ' << ns_stack_->FormatQName(attr->Name(), true) << "=\"";
+ PrintQuotedValue(attr->Value());
*pout_ << '"';
}
// and the extra xmlns declarations
- std::vector<std::string>::iterator i(newXmlns.begin());
- while (i < newXmlns.end()) {
- if (*i == STR_EMPTY)
+ std::vector<std::string>::iterator i(new_ns.begin());
+ while (i < new_ns.end()) {
+ if (*i == STR_EMPTY) {
*pout_ << " xmlns=\"" << *(i + 1) << '"';
- else
+ } else {
*pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"';
+ }
i += 2;
}
// now the children
- const XmlChild * pchild = element->FirstChild();
+ const XmlChild* child = element->FirstChild();
- if (pchild == NULL)
+ if (child == NULL)
*pout_ << "/>";
else {
*pout_ << '>';
- while (pchild) {
- if (pchild->IsText()) {
+ while (child) {
+ if (child->IsText()) {
if (element->IsCDATA()) {
- PrintCDATAText(pchild->AsText()->Text());
+ PrintCDATAText(child->AsText()->Text());
} else {
- PrintBodyText(pchild->AsText()->Text());
+ PrintBodyText(child->AsText()->Text());
}
- } else
- PrintElement(pchild->AsElement());
- pchild = pchild->NextChild();
+ } else {
+ PrintElement(child->AsElement());
+ }
+ child = child->NextChild();
}
- *pout_ << "</" << xmlnsStack_.FormatQName(element->Name(), false) << '>';
+ *pout_ << "</" << ns_stack_->FormatQName(element->Name(), false) << '>';
}
- xmlnsStack_.PopFrame();
+ ns_stack_->PopFrame();
}
-void
-XmlPrinterImpl::PrintQuotedValue(const std::string & text) {
+void XmlPrinterImpl::PrintQuotedValue(const std::string& text) {
size_t safe = 0;
for (;;) {
size_t unsafe = text.find_first_of("<>&\"", safe);
@@ -169,8 +164,7 @@
}
}
-void
-XmlPrinterImpl::PrintBodyText(const std::string & text) {
+void XmlPrinterImpl::PrintBodyText(const std::string& text) {
size_t safe = 0;
for (;;) {
size_t unsafe = text.find_first_of("<>&", safe);
@@ -190,9 +184,8 @@
}
}
-void
-XmlPrinterImpl::PrintCDATAText(const std::string & text) {
+void XmlPrinterImpl::PrintCDATAText(const std::string& text) {
*pout_ << "<![CDATA[" << text << "]]>";
}
-}
+} // namespace buzz
diff --git a/talk/xmllite/xmlprinter.h b/talk/xmllite/xmlprinter.h
index 96900d0..90cc255 100644
--- a/talk/xmllite/xmlprinter.h
+++ b/talk/xmllite/xmlprinter.h
@@ -2,48 +2,48 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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_
+#ifndef TALK_XMLLITE_XMLPRINTER_H_
+#define TALK_XMLLITE_XMLPRINTER_H_
#include <iosfwd>
#include <string>
-#include "talk/base/scoped_ptr.h"
namespace buzz {
class XmlElement;
+class XmlnsStack;
class XmlPrinter {
-public:
- static void PrintXml(std::ostream * pout, const XmlElement * pelt);
+ 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);
+ static void PrintXml(std::ostream* pout, const XmlElement* pelt,
+ XmlnsStack* ns_stack);
};
-}
+} // namespace buzz
-#endif
+#endif // TALK_XMLLITE_XMLPRINTER_H_
diff --git a/talk/xmllite/xmlprinter_unittest.cc b/talk/xmllite/xmlprinter_unittest.cc
new file mode 100644
index 0000000..60b0e42
--- /dev/null
+++ b/talk/xmllite/xmlprinter_unittest.cc
@@ -0,0 +1,62 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmllite/xmlprinter.h"
+
+#include <sstream>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmlnsStack;
+using buzz::XmlPrinter;
+
+TEST(XmlPrinterTest, TestBasicPrinting) {
+ XmlElement elt(QName("google:test", "first"));
+ std::stringstream ss;
+ XmlPrinter::PrintXml(&ss, &elt);
+ EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", ss.str());
+}
+
+TEST(XmlPrinterTest, TestNamespacedPrinting) {
+ XmlElement elt(QName("google:test", "first"));
+ elt.AddElement(new XmlElement(QName("nested:test", "second")));
+ std::stringstream ss;
+
+ XmlnsStack ns_stack;
+ ns_stack.AddXmlns("gg", "google:test");
+ ns_stack.AddXmlns("", "nested:test");
+
+ XmlPrinter::PrintXml(&ss, &elt, &ns_stack);
+ EXPECT_EQ("<gg:first><second/></gg:first>", ss.str());
+}
diff --git a/talk/xmpp/asyncsocket.h b/talk/xmpp/asyncsocket.h
index e4bce7f..fb4ef02 100644
--- a/talk/xmpp/asyncsocket.h
+++ b/talk/xmpp/asyncsocket.h
@@ -69,8 +69,9 @@
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.
+ // We allow matching any passed domain. This allows us to avoid
+ // handling the valuable certificates for logins into proxies. If
+ // both names are passed as empty, we do not require a match.
virtual bool StartTls(const std::string & domainname) = 0;
#endif
diff --git a/talk/xmpp/chatroommodule.h b/talk/xmpp/chatroommodule.h
new file mode 100644
index 0000000..fddbecd
--- /dev/null
+++ b/talk/xmpp/chatroommodule.h
@@ -0,0 +1,258 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _multiuserchatmodule_h_
+#define _multiuserchatmodule_h_
+
+#include "talk/xmpp/module.h"
+#include "talk/xmpp/rostermodule.h"
+
+namespace buzz {
+
+// forward declarations
+class XmppChatroomModule;
+class XmppChatroomHandler;
+class XmppChatroomMember;
+class XmppChatroomMemberEnumerator;
+
+enum XmppChatroomState {
+ XMPP_CHATROOM_STATE_NOT_IN_ROOM = 0,
+ XMPP_CHATROOM_STATE_REQUESTED_ENTER = 1,
+ XMPP_CHATROOM_STATE_IN_ROOM = 2,
+ XMPP_CHATROOM_STATE_REQUESTED_EXIT = 3,
+};
+
+//! Module that encapsulates a chatroom.
+class XmppChatroomModule : public XmppModule {
+public:
+
+ //! Creates a new XmppChatroomModule
+ static XmppChatroomModule* Create();
+ virtual ~XmppChatroomModule() {}
+
+ //! Sets the chatroom handler (callbacks) for the chatroom
+ virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler) = 0;
+
+ //! Gets the chatroom handler for the module
+ virtual XmppChatroomHandler* chatroom_handler() = 0;
+
+ //! Sets the jid of the chatroom.
+ //! Has to be set before entering the chatroom and can't be changed
+ //! while in the chatroom
+ virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid) = 0;
+
+ //! The jid for the chatroom
+ virtual const Jid& chatroom_jid() const = 0;
+
+ //! Sets the nickname of the member
+ //! Has to be set before entering the chatroom and can't be changed
+ //! while in the chatroom
+ virtual XmppReturnStatus set_nickname(const std::string& nickname) = 0;
+
+ //! The nickname of the member in the chatroom
+ virtual const std::string& nickname() const = 0;
+
+ //! Returns the jid of the member (this is the chatroom_jid plus the
+ //! nickname as the resource name)
+ virtual const Jid member_jid() const = 0;
+
+ //! Requests that the user enter a chatroom
+ //! The EnterChatroom callback will be called when the request is complete.
+ //! Password should be empty for a room that doesn't require a password
+ //! If the room doesn't exist, the server will create an "Instant Room" if the
+ //! server policy supports this action.
+ //! There will be different methods for creating/configuring a "Reserved Room"
+ //! Async callback for this method is ChatroomEnteredStatus
+ virtual XmppReturnStatus RequestEnterChatroom(const std::string& password) = 0;
+
+ //! Requests that the user exit a chatroom
+ //! Async callback for this method is ChatroomExitedStatus
+ virtual XmppReturnStatus RequestExitChatroom() = 0;
+
+ //! Requests a status change
+ //! status is the standard XMPP status code
+ //! extended_status is the extended status when status is XMPP_PRESENCE_XA
+ virtual XmppReturnStatus RequestStatusChange(XmppPresenceShow status,
+ const std::string& extended_status) = 0;
+
+ //! Returns the number of members in the room
+ virtual size_t GetChatroomMemberCount() = 0;
+
+ //! Gets an enumerator for the members in the chatroom
+ //! The caller must delete the enumerator when the caller is finished with it.
+ //! The caller must also ensure that the lifetime of the enumerator is
+ //! scoped by the XmppChatRoomModule that created it.
+ virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) = 0;
+
+ //! Gets the subject of the chatroom
+ virtual const std::string& subject() = 0;
+
+ //! Returns the current state of the user with respect to the chatroom
+ virtual XmppChatroomState state() = 0;
+
+ virtual XmppReturnStatus SendMessage(const XmlElement& message) = 0;
+};
+
+//! Class for enumerating participatns
+class XmppChatroomMemberEnumerator {
+public:
+ virtual ~XmppChatroomMemberEnumerator() { }
+ //! Returns the member at the current position
+ //! Returns null if the enumerator is before the beginning
+ //! or after the end of the collection
+ virtual XmppChatroomMember* current() = 0;
+
+ //! Returns whether the enumerator is valid
+ //! This returns true if the collection has changed
+ //! since the enumerator was created
+ virtual bool IsValid() = 0;
+
+ //! Returns whether the enumerator is before the beginning
+ //! This is the initial state of the enumerator
+ virtual bool IsBeforeBeginning() = 0;
+
+ //! Returns whether the enumerator is after the end
+ virtual bool IsAfterEnd() = 0;
+
+ //! Advances the enumerator to the next position
+ //! Returns false is the enumerator is advanced
+ //! off the end of the collection
+ virtual bool Next() = 0;
+
+ //! Advances the enumerator to the previous position
+ //! Returns false is the enumerator is advanced
+ //! off the end of the collection
+ virtual bool Prev() = 0;
+};
+
+
+//! Represents a single member in a chatroom
+class XmppChatroomMember {
+public:
+ virtual ~XmppChatroomMember() { }
+
+ //! The jid for the member in the chatroom
+ virtual const Jid member_jid() const = 0;
+
+ //! The full jid for the member
+ //! This is only available in non-anonymous rooms.
+ //! If the room is anonymous, this returns JID_EMPTY
+ virtual const Jid full_jid() const = 0;
+
+ //! Returns the backing presence for this member
+ virtual const XmppPresence* presence() const = 0;
+
+ //! The nickname for this member
+ virtual const std::string name() const = 0;
+};
+
+//! Status codes for ChatroomEnteredStatus callback
+enum XmppChatroomEnteredStatus
+{
+ //! User successfully entered the room
+ XMPP_CHATROOM_ENTERED_SUCCESS = 0,
+ //! The nickname confliced with somebody already in the room
+ XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT = 1,
+ //! A password is required to enter the room
+ XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED = 2,
+ //! The specified password was incorrect
+ XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT = 3,
+ //! The user is not a member of a member-only room
+ XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER = 4,
+ //! The user cannot enter because the user has been banned
+ XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED = 5,
+ //! The room has the maximum number of users already
+ XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS = 6,
+ //! The room has been locked by an administrator
+ XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED = 7,
+ //! Some other reason
+ XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED = 2000,
+};
+
+//! Status codes for ChatroomExitedStatus callback
+enum XmppChatroomExitedStatus
+{
+ //! The user requested to exit and did so
+ XMPP_CHATROOM_EXITED_REQUESTED = 0,
+ //! The user was banned from the room
+ XMPP_CHATROOM_EXITED_BANNED = 1,
+ //! The user has been kicked out of the room
+ XMPP_CHATROOM_EXITED_KICKED = 2,
+ //! The user has been removed from the room because the
+ //! user is no longer a member of a member-only room
+ //! or the room has changed to membership-only
+ XMPP_CHATROOM_EXITED_NOT_A_MEMBER = 3,
+ //! The system is shutting down
+ XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN = 4,
+ //! For some other reason
+ XMPP_CHATROOM_EXITED_UNSPECIFIED = 5,
+};
+
+//! The XmppChatroomHandler is the interface for callbacks from the
+//! the chatroom
+class XmppChatroomHandler {
+public:
+ //! Indicates the response to RequestEnterChatroom method
+ //! XMPP_CHATROOM_SUCCESS represents success.
+ //! Other status codes are for errors
+ virtual void ChatroomEnteredStatus(XmppChatroomModule* room,
+ XmppChatroomEnteredStatus status) = 0;
+
+
+ //! Indicates that the user has exited the chatroom, either due to
+ //! a call to RequestExitChatroom or for some other reason.
+ //! status indicates the reason the user exited
+ virtual void ChatroomExitedStatus(XmppChatroomModule* room,
+ XmppChatroomExitedStatus status) = 0;
+
+ //! Indicates a member entered the room.
+ virtual void MemberEntered(XmppChatroomModule* room,
+ const XmppChatroomMember* entered_member) = 0;
+
+ //! Indicates that a member exited the room.
+ virtual void MemberExited(XmppChatroomModule* room,
+ const XmppChatroomMember* exited_member) = 0;
+
+ //! Indicates that the data for the member has changed
+ //! (such as the nickname or presence)
+ virtual void MemberChanged(XmppChatroomModule* room,
+ size_t index) = 0;
+
+ //! Indicates a new message has been received
+ //! message is the message -
+ // $TODO - message should be changed
+ //! to a strongly-typed message class that contains info
+ //! such as the sender, message bodies, etc.,
+ virtual void MessageReceived(XmppChatroomModule* room,
+ const XmlElement& message) = 0;
+};
+
+
+}
+
+#endif
+
diff --git a/talk/xmpp/chatroommodule_unittest.cc b/talk/xmpp/chatroommodule_unittest.cc
new file mode 100644
index 0000000..b857551
--- /dev/null
+++ b/talk/xmpp/chatroommodule_unittest.cc
@@ -0,0 +1,295 @@
+/*
+ * libjingle
+ * Copyright 2004, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <sstream>
+#include <iostream>
+#include "common/common.h"
+#include "buzz/xmppengine.h"
+#include "buzz/xmlelement.h"
+#include "buzz/chatroommodule.h"
+#include "buzz/constants.h"
+#include "engine/util_unittest.h"
+#include "test/unittest.h"
+#include "test/unittest-inl.h"
+
+#define TEST_OK(x) TEST_EQ((x),XMPP_RETURN_OK)
+#define TEST_BADARGUMENT(x) TEST_EQ((x),XMPP_RETURN_BADARGUMENT)
+
+namespace buzz {
+
+class MultiUserChatModuleTest;
+
+static void
+WriteEnteredStatus(std::ostream& os, XmppChatroomEnteredStatus status) {
+ switch(status) {
+ case XMPP_CHATROOM_ENTERED_SUCCESS:
+ os<<"success";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT:
+ os<<"failure(nickname conflict)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED:
+ os<<"failure(password required)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_INCORRECT:
+ os<<"failure(password incorrect)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER:
+ os<<"failure(not a member)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED:
+ os<<"failure(member banned)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS:
+ os<<"failure(max users)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED:
+ os<<"failure(room locked)";
+ break;
+ case XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED:
+ os<<"failure(unspecified)";
+ break;
+ default:
+ os<<"unknown";
+ break;
+ }
+}
+
+static void
+WriteExitedStatus(std::ostream& os, XmppChatroomExitedStatus status) {
+ switch (status) {
+ case XMPP_CHATROOM_EXITED_REQUESTED:
+ os<<"requested";
+ break;
+ case XMPP_CHATROOM_EXITED_BANNED:
+ os<<"banned";
+ break;
+ case XMPP_CHATROOM_EXITED_KICKED:
+ os<<"kicked";
+ break;
+ case XMPP_CHATROOM_EXITED_NOT_A_MEMBER:
+ os<<"not member";
+ break;
+ case XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN:
+ os<<"system shutdown";
+ break;
+ case XMPP_CHATROOM_EXITED_UNSPECIFIED:
+ os<<"unspecified";
+ break;
+ default:
+ os<<"unknown";
+ break;
+ }
+}
+
+//! This session handler saves all calls to a string. These are events and
+//! data delivered form the engine to application code.
+class XmppTestChatroomHandler : public XmppChatroomHandler {
+public:
+ XmppTestChatroomHandler() {}
+ virtual ~XmppTestChatroomHandler() {}
+
+ void ChatroomEnteredStatus(XmppChatroomModule* room,
+ XmppChatroomEnteredStatus status) {
+ UNUSED(room);
+ ss_ <<"[ChatroomEnteredStatus status: ";
+ WriteEnteredStatus(ss_, status);
+ ss_ <<"]";
+ }
+
+
+ void ChatroomExitedStatus(XmppChatroomModule* room,
+ XmppChatroomExitedStatus status) {
+ UNUSED(room);
+ ss_ <<"[ChatroomExitedStatus status: ";
+ WriteExitedStatus(ss_, status);
+ ss_ <<"]";
+ }
+
+ void MemberEntered(XmppChatroomModule* room,
+ const XmppChatroomMember* entered_member) {
+ UNUSED(room);
+ ss_ << "[MemberEntered " << entered_member->member_jid().Str() << "]";
+ }
+
+ void MemberExited(XmppChatroomModule* room,
+ const XmppChatroomMember* exited_member) {
+ UNUSED(room);
+ ss_ << "[MemberExited " << exited_member->member_jid().Str() << "]";
+ }
+
+ void MemberChanged(XmppChatroomModule* room, size_t index) {
+ UNUSED2(room, index);
+ }
+
+ virtual void MessageReceived(XmppChatroomModule* room, const XmlElement& message) {
+ UNUSED2(room, message);
+ }
+
+
+ std::string Str() {
+ return ss_.str();
+ }
+
+ std::string StrClear() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+private:
+ std::stringstream ss_;
+};
+
+//! This is the class that holds all of the unit test code for the
+//! roster module
+class XmppChatroomModuleTest : public UnitTest {
+public:
+ XmppChatroomModuleTest() {}
+
+ void TestEnterExitChatroom() {
+ std::stringstream dump;
+
+ // Configure the engine
+ scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+
+ // Configure the module and handler
+ scoped_ptr<XmppChatroomModule> chatroom(XmppChatroomModule::Create());
+
+ // Configure the module handler
+ chatroom->RegisterEngine(engine.get());
+
+ // Set up callbacks
+ engine->SetOutputHandler(&handler);
+ engine->AddStanzaHandler(&handler);
+ engine->SetSessionHandler(&handler);
+
+ // Set up minimal login info
+ engine->SetUser(Jid("david@my-server"));
+ engine->SetPassword("david");
+
+ // Do the whole login handshake
+ RunLogin(this, engine.get(), &handler);
+ TEST_EQ("", handler.OutputActivity());
+
+ // Get the chatroom and set the handler
+ XmppTestChatroomHandler chatroom_handler;
+ chatroom->set_chatroom_handler(static_cast<XmppChatroomHandler*>(&chatroom_handler));
+
+ // try to enter the chatroom
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM);
+ chatroom->set_nickname("thirdwitch");
+ chatroom->set_chatroom_jid(Jid("darkcave@my-server"));
+ chatroom->RequestEnterChatroom("");
+ TEST_EQ(chatroom_handler.StrClear(), "");
+ TEST_EQ(handler.OutputActivity(),
+ "<presence to=\"darkcave@my-server/thirdwitch\">"
+ "<muc:x xmlns:muc=\"http://jabber.org/protocol/muc\"/>"
+ "</presence>");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+ // simulate the server and test the client
+ std::string input;
+ input = "<presence from=\"darkcave@my-server/firstwitch\" to=\"david@my-server\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"owner\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(), "");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+ input = "<presence from=\"darkcave@my-server/secondwitch\" to=\"david@my-server\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"member\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(), "");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+
+ input = "<presence from=\"darkcave@my-server/thirdwitch\" to=\"david@my-server\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"member\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(),
+ "[ChatroomEnteredStatus status: success]");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+ // simulate somebody else entering the room after we entered
+ input = "<presence from=\"darkcave@my-server/fourthwitch\" to=\"david@my-server\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"member\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(), "[MemberEntered darkcave@my-server/fourthwitch]");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+ // simulate somebody else leaving the room after we entered
+ input = "<presence from=\"darkcave@my-server/secondwitch\" to=\"david@my-server\" type=\"unavailable\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"member\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(), "[MemberExited darkcave@my-server/secondwitch]");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_IN_ROOM);
+
+ // try to leave the room
+ chatroom->RequestExitChatroom();
+ TEST_EQ(chatroom_handler.StrClear(), "");
+ TEST_EQ(handler.OutputActivity(),
+ "<presence to=\"darkcave@my-server/thirdwitch\" type=\"unavailable\"/>");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_REQUESTED_EXIT);
+
+ // simulate the server and test the client
+ input = "<presence from=\"darkcave@my-server/thirdwitch\" to=\"david@my-server\" type=\"unavailable\">"
+ "<x xmlns=\"http://jabber.org/protocol/muc#user\">"
+ "<item affiliation=\"member\" role=\"participant\"/>"
+ "</x>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+ TEST_EQ(chatroom_handler.StrClear(),
+ "[ChatroomExitedStatus status: requested]");
+ TEST_EQ(chatroom->state(), XMPP_CHATROOM_STATE_NOT_IN_ROOM);
+ }
+
+};
+
+// A global function that creates the test suite for this set of tests.
+TestBase* ChatroomModuleTest_Create() {
+ TestSuite* suite = new TestSuite("ChatroomModuleTest");
+ ADD_TEST(suite, XmppChatroomModuleTest, TestEnterExitChatroom);
+ return suite;
+}
+
+}
diff --git a/talk/xmpp/chatroommoduleimpl.cc b/talk/xmpp/chatroommoduleimpl.cc
new file mode 100644
index 0000000..0fbbe7e
--- /dev/null
+++ b/talk/xmpp/chatroommoduleimpl.cc
@@ -0,0 +1,676 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <string>
+#include <map>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/moduleimpl.h"
+#include "talk/xmpp/chatroommodule.h"
+
+namespace buzz {
+
+// forward declarations
+class XmppChatroomImpl;
+class XmppChatroomMemberImpl;
+
+//! Module that encapsulates multiple chatrooms.
+//! Each chatroom is represented by an XmppChatroomImpl instance
+class XmppChatroomModuleImpl : public XmppChatroomModule,
+ public XmppModuleImpl, public XmppIqHandler {
+public:
+ IMPLEMENT_XMPPMODULE
+
+ // Creates a chatroom with specified Jid
+ XmppChatroomModuleImpl();
+ ~XmppChatroomModuleImpl();
+
+ // XmppChatroomModule
+ virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler);
+ virtual XmppChatroomHandler* chatroom_handler();
+ virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid);
+ virtual const Jid& chatroom_jid() const;
+ virtual XmppReturnStatus set_nickname(const std::string& nickname);
+ virtual const std::string& nickname() const;
+ virtual const Jid member_jid() const;
+ virtual XmppReturnStatus RequestEnterChatroom(const std::string& password);
+ virtual XmppReturnStatus RequestExitChatroom();
+ virtual XmppReturnStatus RequestStatusChange(XmppPresenceShow status,
+ const std::string& extended_status);
+ virtual size_t GetChatroomMemberCount();
+ virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator);
+ virtual const std::string& subject();
+ virtual XmppChatroomState state() { return chatroom_state_; }
+ virtual XmppReturnStatus SendMessage(const XmlElement& message);
+
+ // XmppModule
+ virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {UNUSED2(cookie, pelStanza);}
+ virtual bool HandleStanza(const XmlElement *);
+
+private:
+ friend class XmppChatroomMemberEnumeratorImpl;
+
+ XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence);
+ XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state);
+ XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer);
+ XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element);
+ XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence);
+ XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence);
+
+ bool CheckEnterChatroomStateOk();
+
+ void FireEnteredStatus(XmppChatroomEnteredStatus status);
+ void FireExitStatus(XmppChatroomExitedStatus status);
+ void FireMessageReceived(const XmlElement& message);
+ void FireMemberEntered(const XmppChatroomMember* entered_member);
+ void FireMemberExited(const XmppChatroomMember* exited_member);
+
+
+ typedef std::map<Jid, XmppChatroomMemberImpl*> JidMemberMap;
+
+ XmppChatroomHandler* chatroom_handler_;
+ Jid chatroom_jid_;
+ std::string nickname_;
+ XmppChatroomState chatroom_state_;
+ JidMemberMap chatroom_jid_members_;
+ int chatroom_jid_members_version_;
+};
+
+
+class XmppChatroomMemberImpl : public XmppChatroomMember {
+public:
+ ~XmppChatroomMemberImpl() {}
+ XmppReturnStatus SetPresence(const XmppPresence* presence);
+
+ // XmppChatroomMember
+ const Jid member_jid() const;
+ const Jid full_jid() const;
+ const std::string name() const;
+ const XmppPresence* presence() const;
+
+private:
+ talk_base::scoped_ptr<XmppPresence> presence_;
+};
+
+class XmppChatroomMemberEnumeratorImpl :
+ public XmppChatroomMemberEnumerator {
+public:
+ XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members,
+ int* map_version);
+
+ // XmppChatroomMemberEnumerator
+ virtual XmppChatroomMember* current();
+ virtual bool Next();
+ virtual bool Prev();
+ virtual bool IsValid();
+ virtual bool IsBeforeBeginning();
+ virtual bool IsAfterEnd();
+
+private:
+ XmppChatroomModuleImpl::JidMemberMap* map_;
+ int map_version_created_;
+ int* map_version_;
+ XmppChatroomModuleImpl::JidMemberMap::iterator iterator_;
+ bool before_beginning_;
+};
+
+
+// XmppChatroomModuleImpl ------------------------------------------------
+XmppChatroomModule *
+XmppChatroomModule::Create() {
+ return new XmppChatroomModuleImpl();
+}
+
+XmppChatroomModuleImpl::XmppChatroomModuleImpl() :
+ chatroom_handler_(NULL),
+ chatroom_jid_(STR_EMPTY),
+ chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM),
+ chatroom_jid_members_version_(0) {
+}
+
+XmppChatroomModuleImpl::~XmppChatroomModuleImpl() {
+ JidMemberMap::iterator iterator = chatroom_jid_members_.begin();
+ while (iterator != chatroom_jid_members_.end()) {
+ delete iterator->second;
+ iterator++;
+ }
+}
+
+
+bool
+XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) {
+ ASSERT(engine() != NULL);
+
+ // we handle stanzas that are for one of our chatrooms
+ Jid from_jid = Jid(stanza->Attr(QN_FROM));
+ // see if it's one of our chatrooms
+ if (chatroom_jid_ != from_jid.BareJid()) {
+ return false; // not one of our chatrooms
+ } else {
+ // handle presence stanza
+ if (stanza->Name() == QN_PRESENCE) {
+ if (from_jid == member_jid()) {
+ ServerChangeMyPresence(*stanza);
+ } else {
+ ServerChangedOtherPresence(*stanza);
+ }
+ } else if (stanza->Name() == QN_MESSAGE) {
+ FireMessageReceived(*stanza);
+ }
+ return true;
+ }
+}
+
+
+XmppReturnStatus
+XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) {
+ // Calling with NULL removes the handler.
+ chatroom_handler_ = handler;
+ return XMPP_RETURN_OK;
+}
+
+
+XmppChatroomHandler*
+XmppChatroomModuleImpl::chatroom_handler() {
+ return chatroom_handler_;
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) {
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
+ return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
+ }
+ if (chatroom_jid != chatroom_jid.BareJid()) {
+ // chatroom_jid must be a bare jid
+ return XMPP_RETURN_BADARGUMENT;
+ }
+
+ chatroom_jid_ = chatroom_jid;
+ return XMPP_RETURN_OK;
+}
+
+const Jid&
+XmppChatroomModuleImpl::chatroom_jid() const {
+ return chatroom_jid_;
+}
+
+ XmppReturnStatus
+ XmppChatroomModuleImpl::set_nickname(const std::string& nickname) {
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
+ return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
+ }
+ nickname_ = nickname;
+ return XMPP_RETURN_OK;
+ }
+
+ const std::string&
+ XmppChatroomModuleImpl::nickname() const {
+ return nickname_;
+ }
+
+const Jid
+XmppChatroomModuleImpl::member_jid() const {
+ return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_);
+}
+
+
+bool
+XmppChatroomModuleImpl::CheckEnterChatroomStateOk() {
+ if (chatroom_jid_.IsValid() == false) {
+ ASSERT(0);
+ return false;
+ }
+ if (nickname_ == STR_EMPTY) {
+ ASSERT(0);
+ return false;
+ }
+ return true;
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestEnterChatroom(const std::string& password) {
+ UNUSED(password);
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM)
+ return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
+
+ if (CheckEnterChatroomStateOk() == false) {
+ return XMPP_RETURN_BADSTATE;
+ }
+
+ // entering a chatroom is a presence request to the server
+ XmlElement element(QN_PRESENCE);
+ element.AddAttr(QN_TO, member_jid().Str());
+ element.AddElement(new XmlElement(QN_MUC_X));
+ XmppReturnStatus status = engine()->SendStanza(&element);
+ if (status == XMPP_RETURN_OK) {
+ return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER);
+ }
+ return status;
+}
+
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestExitChatroom() {
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ // currently, can't leave a room unless you've entered
+ // no way to cancel a pending enter call - is that bad?
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM)
+ return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
+
+ // exiting a chatroom is a presence request to the server
+ XmlElement element(QN_PRESENCE);
+ element.AddAttr(QN_TO, member_jid().Str());
+ element.AddAttr(QN_TYPE, "unavailable");
+ XmppReturnStatus status = engine()->SendStanza(&element);
+ if (status == XMPP_RETURN_OK) {
+ return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT);
+ }
+ return status;
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestStatusChange(XmppPresenceShow status,
+ const std::string& extended_status) {
+ UNUSED2(status, extended_status);
+ return XMPP_RETURN_BADSTATE; //NYI
+}
+
+
+
+size_t
+XmppChatroomModuleImpl::GetChatroomMemberCount() {
+ return chatroom_jid_members_.size();
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) {
+ *enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_);
+ return XMPP_RETURN_OK;
+}
+
+const std::string&
+XmppChatroomModuleImpl::subject() {
+ return STR_EMPTY; //NYI
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::SendMessage(const XmlElement& message) {
+ XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
+
+ // can only send a message if we're in the room
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
+ return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
+ }
+
+ if (message.Name() != QN_MESSAGE) {
+ IFR(XMPP_RETURN_BADARGUMENT);
+ }
+
+ const std::string& type = message.Attr(QN_TYPE);
+ if (type != "groupchat") {
+ IFR(XMPP_RETURN_BADARGUMENT);
+ }
+
+ if (message.HasAttr(QN_FROM)) {
+ IFR(XMPP_RETURN_BADARGUMENT);
+ }
+
+ if (message.Attr(QN_TO) != chatroom_jid_.Str()) {
+ IFR(XMPP_RETURN_BADARGUMENT);
+ }
+
+ IFR(engine()->SendStanza(&message));
+
+ return xmpp_status;
+}
+
+enum TransitionType {
+ TRANSITION_TYPE_NONE = 0,
+ TRANSITION_TYPE_ENTER_SUCCESS = 1,
+ TRANSITION_TYPE_ENTER_FAILURE = 2,
+ TRANSITION_TYPE_EXIT_VOLUNTARILY = 3,
+ TRANSITION_TYPE_EXIT_INVOLUNTARILY = 4,
+};
+
+struct StateTransitionDescription {
+ XmppChatroomState old_state;
+ XmppChatroomState new_state;
+ bool is_valid_server_transition;
+ bool is_valid_client_transition;
+ TransitionType transition_type;
+};
+
+StateTransitionDescription Transitions[] = {
+ { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_ENTER_SUCCESS, },
+ { XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_FAILURE, },
+ { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_SUCCESS, },
+ { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_INVOLUNTARILY, },
+ { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, true, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_VOLUNTARILY, },
+ { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
+ { XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_NONE, },
+};
+
+
+
+void
+XmppChatroomModuleImpl::FireEnteredStatus(XmppChatroomEnteredStatus status) {
+ if (chatroom_handler_)
+ chatroom_handler_->ChatroomEnteredStatus(this, status);
+}
+
+void
+XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) {
+ if (chatroom_handler_)
+ chatroom_handler_->ChatroomExitedStatus(this, status);
+}
+
+void
+XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) {
+ if (chatroom_handler_)
+ chatroom_handler_->MessageReceived(this, message);
+}
+
+void
+XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) {
+ // only fire if we're in the room
+ if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
+ if (chatroom_handler_)
+ chatroom_handler_->MemberEntered(this, entered_member);
+ }
+}
+
+void
+XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
+ // only fire if we're in the room
+ if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
+ if (chatroom_handler_)
+ chatroom_handler_->MemberExited(this, exited_member);
+ }
+}
+
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement&
+ presence_element) {
+ XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
+ talk_base::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
+ IFR(presence->set_raw_xml(&presence_element));
+
+ JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid());
+
+ if (pos == chatroom_jid_members_.end()) {
+ if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
+ XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl();
+ member->SetPresence(presence.get());
+ chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member));
+ chatroom_jid_members_version_++;
+ FireMemberEntered(member);
+ }
+ } else {
+ XmppChatroomMemberImpl* member = pos->second;
+ if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
+ member->SetPresence(presence.get());
+ chatroom_jid_members_version_++;
+ // $TODO - fire change
+ }
+ else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) {
+ chatroom_jid_members_.erase(pos);
+ chatroom_jid_members_version_++;
+ FireMemberExited(member);
+ delete member;
+ }
+ }
+
+ return xmpp_status;
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) {
+ return ChangePresence(new_state, NULL, false);
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) {
+ XmppChatroomState new_state;
+
+ if (presence.HasAttr(QN_TYPE) == false) {
+ new_state = XMPP_CHATROOM_STATE_IN_ROOM;
+ } else {
+ new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM;
+ }
+ return ChangePresence(new_state, &presence, true);
+
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state,
+ const XmlElement* presence,
+ bool isServer) {
+ UNUSED(presence);
+
+ XmppChatroomState old_state = chatroom_state_;
+
+ // do nothing if state hasn't changed
+ if (old_state == new_state)
+ return XMPP_RETURN_OK;
+
+ // find the right transition description
+ StateTransitionDescription* transition_desc = NULL;
+ for (int i=0; i < ARRAY_SIZE(Transitions); i++) {
+ if (Transitions[i].old_state == old_state &&
+ Transitions[i].new_state == new_state) {
+ transition_desc = &Transitions[i];
+ break;
+ }
+ }
+
+ if (transition_desc == NULL) {
+ ASSERT(0);
+ return XMPP_RETURN_BADSTATE;
+ }
+
+ // we assert for any invalid transition states, and we'll
+ if (isServer) {
+ // $TODO send original stanza back to server and log an error?
+ ASSERT(transition_desc->is_valid_server_transition);
+ } else {
+ if (transition_desc->is_valid_client_transition == false) {
+ ASSERT(0);
+ return XMPP_RETURN_BADARGUMENT;
+ }
+ }
+
+ // set the new state and then fire any notifications to the handler
+ chatroom_state_ = new_state;
+
+ switch (transition_desc->transition_type) {
+ case TRANSITION_TYPE_ENTER_SUCCESS:
+ FireEnteredStatus(XMPP_CHATROOM_ENTERED_SUCCESS);
+ break;
+ case TRANSITION_TYPE_ENTER_FAILURE:
+ FireEnteredStatus(GetEnterFailureFromXml(presence));
+ break;
+ case TRANSITION_TYPE_EXIT_INVOLUNTARILY:
+ FireExitStatus(GetExitFailureFromXml(presence));
+ break;
+ case TRANSITION_TYPE_EXIT_VOLUNTARILY:
+ FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED);
+ break;
+ case TRANSITION_TYPE_NONE:
+ break;
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppChatroomEnteredStatus
+XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) {
+ XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED;
+ const XmlElement* error = presence->FirstNamed(QN_ERROR);
+ if (error != NULL && error->HasAttr(QN_CODE)) {
+ int code = atoi(error->Attr(QN_CODE).c_str());
+ switch (code) {
+ case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break;
+ case 403: status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED; break;
+ case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
+ case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
+ case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
+ }
+ }
+ return status;
+}
+
+XmppChatroomExitedStatus
+XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
+ XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
+ const XmlElement* error = presence->FirstNamed(QN_ERROR);
+ if (error != NULL && error->HasAttr(QN_CODE)) {
+ int code = atoi(error->Attr(QN_CODE).c_str());
+ switch (code) {
+ case 307: status = XMPP_CHATROOM_EXITED_KICKED; break;
+ case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break;
+ case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break;
+ }
+ }
+ return status;
+}
+
+XmppReturnStatus
+XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) {
+ ASSERT(presence != NULL);
+
+ // copy presence
+ presence_.reset(XmppPresence::Create());
+ presence_->set_raw_xml(presence->raw_xml());
+ return XMPP_RETURN_OK;
+}
+
+const Jid
+XmppChatroomMemberImpl::member_jid() const {
+ return presence_->jid();
+}
+
+const Jid
+XmppChatroomMemberImpl::full_jid() const {
+ return Jid("");
+}
+
+const std::string
+XmppChatroomMemberImpl::name() const {
+ return member_jid().resource();
+}
+
+const XmppPresence*
+XmppChatroomMemberImpl::presence() const {
+ return presence_.get();
+}
+
+
+// XmppChatroomMemberEnumeratorImpl --------------------------------------
+XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl(
+ XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) {
+ map_ = map;
+ map_version_ = map_version;
+ map_version_created_ = *map_version_;
+ iterator_ = map->begin();
+ before_beginning_ = true;
+}
+
+XmppChatroomMember*
+XmppChatroomMemberEnumeratorImpl::current() {
+ if (IsValid() == false) {
+ return NULL;
+ } else if (IsBeforeBeginning() || IsAfterEnd()) {
+ return NULL;
+ } else {
+ return iterator_->second;
+ }
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::Prev() {
+ if (IsValid() == false) {
+ return false;
+ } else if (IsBeforeBeginning()) {
+ return false;
+ } else if (iterator_ == map_->begin()) {
+ before_beginning_ = true;
+ return false;
+ } else {
+ iterator_--;
+ return current() != NULL;
+ }
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::Next() {
+ if (IsValid() == false) {
+ return false;
+ } else if (IsBeforeBeginning()) {
+ before_beginning_ = false;
+ iterator_ = map_->begin();
+ return current() != NULL;
+ } else if (IsAfterEnd()) {
+ return false;
+ } else {
+ iterator_++;
+ return current() != NULL;
+ }
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::IsValid() {
+ return map_version_created_ == *map_version_;
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() {
+ return before_beginning_;
+}
+
+bool
+XmppChatroomMemberEnumeratorImpl::IsAfterEnd() {
+ return (iterator_ == map_->end());
+}
+
+
+
+} // namespace buzz
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
index 1003f39..421fd8e 100644
--- a/talk/xmpp/constants.cc
+++ b/talk/xmpp/constants.cc
@@ -25,568 +25,532 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/xmpp/constants.h"
+
#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);
+// TODO: Remove static objects of complex types, particularly
+// Jid and QName.
-const std::string & Constants::ns_client() {
- static const std::string ns_client_("jabber:client");
- return ns_client_;
-}
+const char NS_CLIENT[] = "jabber:client";
+const char NS_SERVER[] = "jabber:server";
+const char NS_STREAM[] = "http://etherx.jabber.org/streams";
+const char NS_XSTREAM[] = "urn:ietf:params:xml:ns:xmpp-streams";
+const char NS_TLS[] = "urn:ietf:params:xml:ns:xmpp-tls";
+const char NS_SASL[] = "urn:ietf:params:xml:ns:xmpp-sasl";
+const char NS_BIND[] = "urn:ietf:params:xml:ns:xmpp-bind";
+const char NS_DIALBACK[] = "jabber:server:dialback";
+const char NS_SESSION[] = "urn:ietf:params:xml:ns:xmpp-session";
+const char NS_STANZA[] = "urn:ietf:params:xml:ns:xmpp-stanzas";
+const char NS_PRIVACY[] = "jabber:iq:privacy";
+const char NS_ROSTER[] = "jabber:iq:roster";
+const char NS_VCARD[] = "vcard-temp";
+const char NS_AVATAR_HASH[] = "google:avatar";
+const char NS_VCARD_UPDATE[] = "vcard-temp:x:update";
+const char STR_CLIENT[] = "client";
+const char STR_SERVER[] = "server";
+const char STR_STREAM[] = "stream";
-const std::string & Constants::ns_server() {
- static const std::string ns_server_("jabber:server");
- return ns_server_;
-}
+const char STR_GET[] = "get";
+const char STR_SET[] = "set";
+const char STR_RESULT[] = "result";
+const char STR_ERROR[] = "error";
-const std::string & Constants::ns_stream() {
- static const std::string ns_stream_("http://etherx.jabber.org/streams");
- return ns_stream_;
-}
+const char STR_FORM[] = "form";
+const char STR_SUBMIT[] = "submit";
+const char STR_TEXT_SINGLE[] = "text-single";
+const char STR_LIST_SINGLE[] = "list-single";
+const char STR_LIST_MULTI[] = "list-multi";
+const char STR_HIDDEN[] = "hidden";
+const char STR_FORM_TYPE[] = "FORM_TYPE";
-const std::string & Constants::ns_xstream() {
- static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams");
- return ns_xstream_;
-}
+const char STR_FROM[] = "from";
+const char STR_TO[] = "to";
+const char STR_BOTH[] = "both";
+const char STR_REMOVE[] = "remove";
-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_FORM("form");
-const std::string STR_SUBMIT("submit");
-const std::string STR_TEXT_SINGLE("text-single");
-const std::string STR_LIST_SINGLE("list-single");
-const std::string STR_LIST_MULTI("list-multi");
-const std::string STR_HIDDEN("hidden");
-const std::string STR_FORM_TYPE("FORM_TYPE");
-
-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_TYPE("type");
-const std::string STR_NAME("name");
-const std::string STR_ID("id");
-const std::string STR_JID("jid");
-const std::string STR_SUBSCRIPTION("subscription");
-const std::string STR_ASK("ask");
-const std::string STR_X("x");
-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_XMPP_GOOGLE_COM("xmpp.google.com");
-const std::string STR_XMPPX_L_GOOGLE_COM("xmppx.l.google.com");
+const char STR_TYPE[] = "type";
+const char STR_NAME[] = "name";
+const char STR_ID[] = "id";
+const char STR_JID[] = "jid";
+const char STR_SUBSCRIPTION[] = "subscription";
+const char STR_ASK[] = "ask";
+const char STR_X[] = "x";
+const char STR_GOOGLE_COM[] = "google.com";
+const char STR_GMAIL_COM[] = "gmail.com";
+const char STR_GOOGLEMAIL_COM[] = "googlemail.com";
+const char STR_DEFAULT_DOMAIN[] = "default.talk.google.com";
+const char STR_TALK_GOOGLE_COM[] = "talk.google.com";
+const char STR_TALKX_L_GOOGLE_COM[] = "talkx.l.google.com";
+const char STR_XMPP_GOOGLE_COM[] = "xmpp.google.com";
+const char STR_XMPPX_L_GOOGLE_COM[] = "xmppx.l.google.com";
#ifdef FEATURE_ENABLE_VOICEMAIL
-const std::string STR_VOICEMAIL("voicemail");
-const std::string STR_OUTGOINGVOICEMAIL("outgoingvoicemail");
+const char STR_VOICEMAIL[] = "voicemail";
+const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail";
#endif
-const std::string STR_UNAVAILABLE("unavailable");
+const char STR_UNAVAILABLE[] = "unavailable";
-const Jid JID_GOOGLE_MUC_LOOKUP("lookup.groupchat.google.com");
-const std::string STR_MUC_ROOMCONFIG_ROOMNAME("muc#roomconfig_roomname");
-const std::string STR_MUC_ROOMCONFIG_FEATURES("muc#roomconfig_features");
-const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE("muc_enterprise");
-const std::string STR_MUC_ROOMCONFIG("http://jabber.org/protocol/muc#roomconfig");
+const char STR_GOOGLE_MUC_LOOKUP_JID[] = "lookup.groupchat.google.com";
+const char STR_MUC_ROOMCONFIG_ROOMNAME[] = "muc#roomconfig_roomname";
+const char STR_MUC_ROOMCONFIG_FEATURES[] = "muc#roomconfig_features";
+const char STR_MUC_ROOM_FEATURE_ENTERPRISE[] = "muc_enterprise";
+const char STR_MUC_ROOMCONFIG[] = "http://jabber.org/protocol/muc#roomconfig";
-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 StaticQName QN_STREAM_STREAM = { NS_STREAM, STR_STREAM };
+const StaticQName QN_STREAM_FEATURES = { NS_STREAM, "features" };
+const StaticQName QN_STREAM_ERROR = { 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 StaticQName QN_XSTREAM_BAD_FORMAT = { NS_XSTREAM, "bad-format" };
+const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX =
+ { NS_XSTREAM, "bad-namespace-prefix" };
+const StaticQName QN_XSTREAM_CONFLICT = { NS_XSTREAM, "conflict" };
+const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT =
+ { NS_XSTREAM, "connection-timeout" };
+const StaticQName QN_XSTREAM_HOST_GONE = { NS_XSTREAM, "host-gone" };
+const StaticQName QN_XSTREAM_HOST_UNKNOWN = { NS_XSTREAM, "host-unknown" };
+const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING =
+ { NS_XSTREAM, "improper-addressing" };
+const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR =
+ { NS_XSTREAM, "internal-server-error" };
+const StaticQName QN_XSTREAM_INVALID_FROM = { NS_XSTREAM, "invalid-from" };
+const StaticQName QN_XSTREAM_INVALID_ID = { NS_XSTREAM, "invalid-id" };
+const StaticQName QN_XSTREAM_INVALID_NAMESPACE =
+ { NS_XSTREAM, "invalid-namespace" };
+const StaticQName QN_XSTREAM_INVALID_XML = { NS_XSTREAM, "invalid-xml" };
+const StaticQName QN_XSTREAM_NOT_AUTHORIZED = { NS_XSTREAM, "not-authorized" };
+const StaticQName QN_XSTREAM_POLICY_VIOLATION =
+ { NS_XSTREAM, "policy-violation" };
+const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED =
+ { NS_XSTREAM, "remote-connection-failed" };
+const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT =
+ { NS_XSTREAM, "resource-constraint" };
+const StaticQName QN_XSTREAM_RESTRICTED_XML = { NS_XSTREAM, "restricted-xml" };
+const StaticQName QN_XSTREAM_SEE_OTHER_HOST = { NS_XSTREAM, "see-other-host" };
+const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN =
+ { NS_XSTREAM, "system-shutdown" };
+const StaticQName QN_XSTREAM_UNDEFINED_CONDITION =
+ { NS_XSTREAM, "undefined-condition" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING =
+ { NS_XSTREAM, "unsupported-encoding" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE =
+ { NS_XSTREAM, "unsupported-stanza-type" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION =
+ { NS_XSTREAM, "unsupported-version" };
+const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED =
+ { NS_XSTREAM, "xml-not-well-formed" };
+const StaticQName QN_XSTREAM_TEXT = { 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 StaticQName QN_TLS_STARTTLS = { NS_TLS, "starttls" };
+const StaticQName QN_TLS_REQUIRED = { NS_TLS, "required" };
+const StaticQName QN_TLS_PROCEED = { NS_TLS, "proceed" };
+const StaticQName QN_TLS_FAILURE = { 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 StaticQName QN_SASL_MECHANISMS = { NS_SASL, "mechanisms" };
+const StaticQName QN_SASL_MECHANISM = { NS_SASL, "mechanism" };
+const StaticQName QN_SASL_AUTH = { NS_SASL, "auth" };
+const StaticQName QN_SASL_CHALLENGE = { NS_SASL, "challenge" };
+const StaticQName QN_SASL_RESPONSE = { NS_SASL, "response" };
+const StaticQName QN_SASL_ABORT = { NS_SASL, "abort" };
+const StaticQName QN_SASL_SUCCESS = { NS_SASL, "success" };
+const StaticQName QN_SASL_FAILURE = { NS_SASL, "failure" };
+const StaticQName QN_SASL_ABORTED = { NS_SASL, "aborted" };
+const StaticQName QN_SASL_INCORRECT_ENCODING =
+ { NS_SASL, "incorrect-encoding" };
+const StaticQName QN_SASL_INVALID_AUTHZID = { NS_SASL, "invalid-authzid" };
+const StaticQName QN_SASL_INVALID_MECHANISM = { NS_SASL, "invalid-mechanism" };
+const StaticQName QN_SASL_MECHANISM_TOO_WEAK =
+ { NS_SASL, "mechanism-too-weak" };
+const StaticQName QN_SASL_NOT_AUTHORIZED = { NS_SASL, "not-authorized" };
+const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE =
+ { NS_SASL, "temporary-auth-failure" };
-const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result");
-const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify");
+// These are non-standard.
+const char NS_GOOGLE_AUTH_PROTOCOL[] =
+ "http://www.google.com/talk/protocol/auth";
+const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT =
+ { NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result" };
+const char NS_GOOGLE_AUTH_OLD[] = "google:auth";
+const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN =
+ { NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login" };
-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 StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" };
+const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" };
-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 StaticQName QN_STANZA_BAD_REQUEST = { NS_STANZA, "bad-request" };
+const StaticQName QN_STANZA_CONFLICT = { NS_STANZA, "conflict" };
+const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED =
+ { NS_STANZA, "feature-not-implemented" };
+const StaticQName QN_STANZA_FORBIDDEN = { NS_STANZA, "forbidden" };
+const StaticQName QN_STANZA_GONE = { NS_STANZA, "gone" };
+const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR =
+ { NS_STANZA, "internal-server-error" };
+const StaticQName QN_STANZA_ITEM_NOT_FOUND = { NS_STANZA, "item-not-found" };
+const StaticQName QN_STANZA_JID_MALFORMED = { NS_STANZA, "jid-malformed" };
+const StaticQName QN_STANZA_NOT_ACCEPTABLE = { NS_STANZA, "not-acceptable" };
+const StaticQName QN_STANZA_NOT_ALLOWED = { NS_STANZA, "not-allowed" };
+const StaticQName QN_STANZA_PAYMENT_REQUIRED =
+ { NS_STANZA, "payment-required" };
+const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE =
+ { NS_STANZA, "recipient-unavailable" };
+const StaticQName QN_STANZA_REDIRECT = { NS_STANZA, "redirect" };
+const StaticQName QN_STANZA_REGISTRATION_REQUIRED =
+ { NS_STANZA, "registration-required" };
+const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND =
+ { NS_STANZA, "remote-server-not-found" };
+const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT =
+ { NS_STANZA, "remote-server-timeout" };
+const StaticQName QN_STANZA_RESOURCE_CONSTRAINT =
+ { NS_STANZA, "resource-constraint" };
+const StaticQName QN_STANZA_SERVICE_UNAVAILABLE =
+ { NS_STANZA, "service-unavailable" };
+const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED =
+ { NS_STANZA, "subscription-required" };
+const StaticQName QN_STANZA_UNDEFINED_CONDITION =
+ { NS_STANZA, "undefined-condition" };
+const StaticQName QN_STANZA_UNEXPECTED_REQUEST =
+ { NS_STANZA, "unexpected-request" };
+const StaticQName QN_STANZA_TEXT = { NS_STANZA, "text" };
-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 StaticQName QN_BIND_BIND = { NS_BIND, "bind" };
+const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" };
+const StaticQName QN_BIND_JID = { NS_BIND, "jid" };
-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 StaticQName QN_MESSAGE = { NS_CLIENT, "message" };
+const StaticQName QN_BODY = { NS_CLIENT, "body" };
+const StaticQName QN_SUBJECT = { NS_CLIENT, "subject" };
+const StaticQName QN_THREAD = { NS_CLIENT, "thread" };
+const StaticQName QN_PRESENCE = { NS_CLIENT, "presence" };
+const StaticQName QN_SHOW = { NS_CLIENT, "show" };
+const StaticQName QN_STATUS = { NS_CLIENT, "status" };
+const StaticQName QN_LANG = { NS_CLIENT, "lang" };
+const StaticQName QN_PRIORITY = { NS_CLIENT, "priority" };
+const StaticQName QN_IQ = { NS_CLIENT, "iq" };
+const StaticQName QN_ERROR = { NS_CLIENT, "error" };
-const QName QN_SESSION_SESSION(true, NS_SESSION, "session");
+const StaticQName QN_SERVER_MESSAGE = { NS_SERVER, "message" };
+const StaticQName QN_SERVER_BODY = { NS_SERVER, "body" };
+const StaticQName QN_SERVER_SUBJECT = { NS_SERVER, "subject" };
+const StaticQName QN_SERVER_THREAD = { NS_SERVER, "thread" };
+const StaticQName QN_SERVER_PRESENCE = { NS_SERVER, "presence" };
+const StaticQName QN_SERVER_SHOW = { NS_SERVER, "show" };
+const StaticQName QN_SERVER_STATUS = { NS_SERVER, "status" };
+const StaticQName QN_SERVER_LANG = { NS_SERVER, "lang" };
+const StaticQName QN_SERVER_PRIORITY = { NS_SERVER, "priority" };
+const StaticQName QN_SERVER_IQ = { NS_SERVER, "iq" };
+const StaticQName QN_SERVER_ERROR = { NS_SERVER, "error" };
-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 StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" };
-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 StaticQName QN_PRIVACY_QUERY = { NS_PRIVACY, "query" };
+const StaticQName QN_PRIVACY_ACTIVE = { NS_PRIVACY, "active" };
+const StaticQName QN_PRIVACY_DEFAULT = { NS_PRIVACY, "default" };
+const StaticQName QN_PRIVACY_LIST = { NS_PRIVACY, "list" };
+const StaticQName QN_PRIVACY_ITEM = { NS_PRIVACY, "item" };
+const StaticQName QN_PRIVACY_IQ = { NS_PRIVACY, "iq" };
+const StaticQName QN_PRIVACY_MESSAGE = { NS_PRIVACY, "message" };
+const StaticQName QN_PRIVACY_PRESENCE_IN = { NS_PRIVACY, "presence-in" };
+const StaticQName QN_PRIVACY_PRESENCE_OUT = { NS_PRIVACY, "presence-out" };
-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 StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" };
+const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" };
+const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" };
-const QName QN_NAME(true, STR_EMPTY, "name");
-const QName QN_AFFILIATION(true, STR_EMPTY, "affiliation");
-const QName QN_ROLE(true, STR_EMPTY, "role");
+const StaticQName QN_VCARD = { NS_VCARD, "vCard" };
+const StaticQName QN_VCARD_FN = { NS_VCARD, "FN" };
+const StaticQName QN_VCARD_PHOTO = { NS_VCARD, "PHOTO" };
+const StaticQName QN_VCARD_PHOTO_BINVAL = { NS_VCARD, "BINVAL" };
+const StaticQName QN_VCARD_AVATAR_HASH = { NS_AVATAR_HASH, "hash" };
+const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED =
+ { NS_AVATAR_HASH, "modified" };
+
+const StaticQName QN_NAME = { STR_EMPTY, "name" };
+const StaticQName QN_AFFILIATION = { STR_EMPTY, "affiliation" };
+const StaticQName QN_ROLE = { STR_EMPTY, "role" };
#if defined(FEATURE_ENABLE_PSTN)
-const QName QN_VCARD_TEL(true, NS_VCARD, "TEL");
-const QName QN_VCARD_VOICE(true, NS_VCARD, "VOICE");
-const QName QN_VCARD_HOME(true, NS_VCARD, "HOME");
-const QName QN_VCARD_WORK(true, NS_VCARD, "WORK");
-const QName QN_VCARD_CELL(true, NS_VCARD, "CELL");
-const QName QN_VCARD_NUMBER(true, NS_VCARD, "NUMBER");
+const StaticQName QN_VCARD_TEL = { NS_VCARD, "TEL" };
+const StaticQName QN_VCARD_VOICE = { NS_VCARD, "VOICE" };
+const StaticQName QN_VCARD_HOME = { NS_VCARD, "HOME" };
+const StaticQName QN_VCARD_WORK = { NS_VCARD, "WORK" };
+const StaticQName QN_VCARD_CELL = { NS_VCARD, "CELL" };
+const StaticQName QN_VCARD_NUMBER = { NS_VCARD, "NUMBER" };
#endif
-const QName QN_XML_LANG(true, NS_XML, "lang");
+const StaticQName QN_XML_LANG = { NS_XML, "lang" };
-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 StaticQName QN_ENCODING = { STR_EMPTY, STR_ENCODING };
+const StaticQName QN_VERSION = { STR_EMPTY, STR_VERSION };
+const StaticQName QN_TO = { STR_EMPTY, "to" };
+const StaticQName QN_FROM = { STR_EMPTY, "from" };
+const StaticQName QN_TYPE = { STR_EMPTY, "type" };
+const StaticQName QN_ID = { STR_EMPTY, "id" };
+const StaticQName QN_CODE = { 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_NICK(true, STR_EMPTY, "nick");
-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_TIME(true, STR_EMPTY, "time");
+const StaticQName QN_VALUE = { STR_EMPTY, "value" };
+const StaticQName QN_ACTION = { STR_EMPTY, "action" };
+const StaticQName QN_ORDER = { STR_EMPTY, "order" };
+const StaticQName QN_MECHANISM = { STR_EMPTY, "mechanism" };
+const StaticQName QN_ASK = { STR_EMPTY, "ask" };
+const StaticQName QN_JID = { STR_EMPTY, "jid" };
+const StaticQName QN_NICK = { STR_EMPTY, "nick" };
+const StaticQName QN_SUBSCRIPTION = { STR_EMPTY, "subscription" };
+const StaticQName QN_TITLE1 = { STR_EMPTY, "title1" };
+const StaticQName QN_TITLE2 = { STR_EMPTY, "title2" };
+const StaticQName QN_SOURCE = { STR_EMPTY, "source" };
+const StaticQName QN_TIME = { STR_EMPTY, "time" };
-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);
+const StaticQName QN_XMLNS_CLIENT = { NS_XMLNS, STR_CLIENT };
+const StaticQName QN_XMLNS_SERVER = { NS_XMLNS, STR_SERVER };
+const StaticQName QN_XMLNS_STREAM = { 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");
+const char STR_SHOW_AWAY[] = "away";
+const char STR_SHOW_CHAT[] = "chat";
+const char STR_SHOW_DND[] = "dnd";
+const char STR_SHOW_XA[] = "xa";
+const char 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");
+const char STR_SUBSCRIBE[] = "subscribe";
+const char STR_SUBSCRIBED[] = "subscribed";
+const char STR_UNSUBSCRIBE[] = "unsubscribe";
+const char STR_UNSUBSCRIBED[] = "unsubscribed";
// Google Invite
-const std::string NS_GOOGLE_INVITE("google:subscribe");
-const QName QN_INVITATION(true, NS_GOOGLE_INVITE, "invitation");
-const QName QN_INVITE_NAME(true, NS_GOOGLE_INVITE, "name");
-const QName QN_INVITE_SUBJECT(true, NS_GOOGLE_INVITE, "subject");
-const QName QN_INVITE_MESSAGE(true, NS_GOOGLE_INVITE, "body");
+const char NS_GOOGLE_INVITE[] = "google:subscribe";
+const StaticQName QN_INVITATION = { NS_GOOGLE_INVITE, "invitation" };
+const StaticQName QN_INVITE_NAME = { NS_GOOGLE_INVITE, "name" };
+const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_INVITE, "subject" };
+const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_INVITE, "body" };
// PubSub: http://xmpp.org/extensions/xep-0060.html
-const std::string NS_PUBSUB("http://jabber.org/protocol/pubsub");
-const QName QN_PUBSUB(true, NS_PUBSUB, "pubsub");
-const QName QN_PUBSUB_ITEMS(true, NS_PUBSUB, "items");
-const QName QN_PUBSUB_ITEM(true, NS_PUBSUB, "item");
-const QName QN_PUBSUB_PUBLISH(true, NS_PUBSUB, "publish");
-const QName QN_PUBSUB_RETRACT(true, NS_PUBSUB, "retract");
-const QName QN_ATTR_PUBLISHER(true, STR_EMPTY, "publisher");
+const char NS_PUBSUB[] = "http://jabber.org/protocol/pubsub";
+const StaticQName QN_PUBSUB = { NS_PUBSUB, "pubsub" };
+const StaticQName QN_PUBSUB_ITEMS = { NS_PUBSUB, "items" };
+const StaticQName QN_PUBSUB_ITEM = { NS_PUBSUB, "item" };
+const StaticQName QN_PUBSUB_PUBLISH = { NS_PUBSUB, "publish" };
+const StaticQName QN_PUBSUB_RETRACT = { NS_PUBSUB, "retract" };
+const StaticQName QN_ATTR_PUBLISHER = { STR_EMPTY, "publisher" };
-const std::string NS_PUBSUB_EVENT("http://jabber.org/protocol/pubsub#event");
-const QName QN_NODE(true, STR_EMPTY, "node");
-const QName QN_PUBSUB_EVENT(true, NS_PUBSUB_EVENT, "event");
-const QName QN_PUBSUB_EVENT_ITEMS(true, NS_PUBSUB_EVENT, "items");
-const QName QN_PUBSUB_EVENT_ITEM(true, NS_PUBSUB_EVENT, "item");
-const QName QN_PUBSUB_EVENT_RETRACT(true, NS_PUBSUB_EVENT, "retract");
-const QName QN_NOTIFY(true, STR_EMPTY, "notify");
+const char NS_PUBSUB_EVENT[] = "http://jabber.org/protocol/pubsub#event";
+const StaticQName QN_NODE = { STR_EMPTY, "node" };
+const StaticQName QN_PUBSUB_EVENT = { NS_PUBSUB_EVENT, "event" };
+const StaticQName QN_PUBSUB_EVENT_ITEMS = { NS_PUBSUB_EVENT, "items" };
+const StaticQName QN_PUBSUB_EVENT_ITEM = { NS_PUBSUB_EVENT, "item" };
+const StaticQName QN_PUBSUB_EVENT_RETRACT = { NS_PUBSUB_EVENT, "retract" };
+const StaticQName QN_NOTIFY = { STR_EMPTY, "notify" };
-
-
-const std::string NS_PRESENTER("google:presenter");
-const QName QN_PRESENTER_PRESENTER(true, NS_PRESENTER, "presenter");
-const QName QN_PRESENTER_PRESENTATION_ITEM(
- true, NS_PRESENTER, "presentation-item");
-const QName QN_PRESENTER_PRESENTATION_TYPE(
- true, NS_PRESENTER, "presentation-type");
-const QName QN_PRESENTER_PRESENTATION_ID(true, NS_PRESENTER, "presentation-id");
-
-
+const char NS_PRESENTER[] = "google:presenter";
+const StaticQName QN_PRESENTER_PRESENTER = { NS_PRESENTER, "presenter" };
+const StaticQName QN_PRESENTER_PRESENTATION_ITEM =
+ { NS_PRESENTER, "presentation-item" };
+const StaticQName QN_PRESENTER_PRESENTATION_TYPE =
+ { NS_PRESENTER, "presentation-type" };
+const StaticQName QN_PRESENTER_PRESENTATION_ID =
+ { NS_PRESENTER, "presentation-id" };
// JEP 0030
-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 StaticQName QN_CATEGORY = { STR_EMPTY, "category" };
+const StaticQName QN_VAR = { STR_EMPTY, "var" };
+const char NS_DISCO_INFO[] = "http://jabber.org/protocol/disco#info";
+const char NS_DISCO_ITEMS[] = "http://jabber.org/protocol/disco#items";
+const StaticQName QN_DISCO_INFO_QUERY = { NS_DISCO_INFO, "query" };
+const StaticQName QN_DISCO_IDENTITY = { NS_DISCO_INFO, "identity" };
+const StaticQName QN_DISCO_FEATURE = { NS_DISCO_INFO, "feature" };
-const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query");
-const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item");
+const StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" };
+const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" };
// JEP 0020
-const std::string NS_FEATURE("http://jabber.org/protocol/feature-neg");
-const QName QN_FEATURE_FEATURE(true, NS_FEATURE, "feature");
+const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg";
+const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" };
// JEP 0004
-const std::string NS_XDATA("jabber:x:data");
-const QName QN_XDATA_X(true, NS_XDATA, "x");
-const QName QN_XDATA_INSTRUCTIONS(true, NS_XDATA, "instructions");
-const QName QN_XDATA_TITLE(true, NS_XDATA, "title");
-const QName QN_XDATA_FIELD(true, NS_XDATA, "field");
-const QName QN_XDATA_REPORTED(true, NS_XDATA, "reported");
-const QName QN_XDATA_ITEM(true, NS_XDATA, "item");
-const QName QN_XDATA_DESC(true, NS_XDATA, "desc");
-const QName QN_XDATA_REQUIRED(true, NS_XDATA, "required");
-const QName QN_XDATA_VALUE(true, NS_XDATA, "value");
-const QName QN_XDATA_OPTION(true, NS_XDATA, "option");
+const char NS_XDATA[] = "jabber:x:data";
+const StaticQName QN_XDATA_X = { NS_XDATA, "x" };
+const StaticQName QN_XDATA_INSTRUCTIONS = { NS_XDATA, "instructions" };
+const StaticQName QN_XDATA_TITLE = { NS_XDATA, "title" };
+const StaticQName QN_XDATA_FIELD = { NS_XDATA, "field" };
+const StaticQName QN_XDATA_REPORTED = { NS_XDATA, "reported" };
+const StaticQName QN_XDATA_ITEM = { NS_XDATA, "item" };
+const StaticQName QN_XDATA_DESC = { NS_XDATA, "desc" };
+const StaticQName QN_XDATA_REQUIRED = { NS_XDATA, "required" };
+const StaticQName QN_XDATA_VALUE = { NS_XDATA, "value" };
+const StaticQName QN_XDATA_OPTION = { NS_XDATA, "option" };
// JEP 0045
-const std::string NS_MUC("http://jabber.org/protocol/muc");
-const QName QN_MUC_X(true, NS_MUC, "x");
-const QName QN_MUC_ITEM(true, NS_MUC, "item");
-const QName QN_MUC_AFFILIATION(true, NS_MUC, "affiliation");
-const QName QN_MUC_ROLE(true, NS_MUC, "role");
-const std::string STR_AFFILIATION_NONE("none");
-const std::string STR_ROLE_PARTICIPANT("participant");
+const char NS_MUC[] = "http://jabber.org/protocol/muc";
+const StaticQName QN_MUC_X = { NS_MUC, "x" };
+const StaticQName QN_MUC_ITEM = { NS_MUC, "item" };
+const StaticQName QN_MUC_AFFILIATION = { NS_MUC, "affiliation" };
+const StaticQName QN_MUC_ROLE = { NS_MUC, "role" };
+const char STR_AFFILIATION_NONE[] = "none";
+const char STR_ROLE_PARTICIPANT[] = "participant";
-const std::string NS_MUC_OWNER("http://jabber.org/protocol/muc#owner");
-const QName QN_MUC_OWNER_QUERY(true, NS_MUC_OWNER, "query");
+const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner";
+const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" };
-const std::string NS_MUC_USER("http://jabber.org/protocol/muc#user");
-const QName QN_MUC_USER_CONTINUE(true, NS_MUC_USER, "continue");
-const QName QN_MUC_USER_X(true, NS_MUC_USER, "x");
-const QName QN_MUC_USER_ITEM(true, NS_MUC_USER, "item");
-const QName QN_MUC_USER_STATUS(true, NS_MUC_USER, "status");
-
+const char NS_MUC_USER[] = "http://jabber.org/protocol/muc#user";
+const StaticQName QN_MUC_USER_CONTINUE = { NS_MUC_USER, "continue" };
+const StaticQName QN_MUC_USER_X = { NS_MUC_USER, "x" };
+const StaticQName QN_MUC_USER_ITEM = { NS_MUC_USER, "item" };
+const StaticQName QN_MUC_USER_STATUS = { NS_MUC_USER, "status" };
// JEP 0055 - Jabber Search
-const std::string NS_SEARCH("jabber:iq:search");
-const QName QN_SEARCH_QUERY(true, NS_SEARCH, "query");
-const QName QN_SEARCH_ITEM(true, NS_SEARCH, "item");
-const QName QN_SEARCH_ROOM_NAME(true, NS_SEARCH, "room-name");
-const QName QN_SEARCH_ROOM_DOMAIN(true, NS_SEARCH, "room-domain");
-const QName QN_SEARCH_ROOM_JID(true, NS_SEARCH, "room-jid");
-
+const char NS_SEARCH[] = "jabber:iq:search";
+const StaticQName QN_SEARCH_QUERY = { NS_SEARCH, "query" };
+const StaticQName QN_SEARCH_ITEM = { NS_SEARCH, "item" };
+const StaticQName QN_SEARCH_ROOM_NAME = { NS_SEARCH, "room-name" };
+const StaticQName QN_SEARCH_ROOM_DOMAIN = { NS_SEARCH, "room-domain" };
+const StaticQName QN_SEARCH_ROOM_JID = { NS_SEARCH, "room-jid" };
// 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");
+const char NS_CAPS[] = "http://jabber.org/protocol/caps";
+const StaticQName QN_CAPS_C = { NS_CAPS, "c" };
+const StaticQName QN_VER = { STR_EMPTY, "ver" };
+const StaticQName QN_EXT = { 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");
+const char kNSVCard[] = "vcard-temp:x:update";
+const StaticQName kQnVCardX = { kNSVCard, "x" };
+const StaticQName kQnVCardPhoto = { kNSVCard, "photo" };
// JEP 0172 User Nickname
-const std::string NS_NICKNAME("http://jabber.org/protocol/nick");
-const QName QN_NICKNAME(true, NS_NICKNAME, "nick");
-
+const char NS_NICKNAME[] = "http://jabber.org/protocol/nick";
+const StaticQName QN_NICKNAME = { NS_NICKNAME, "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");
+const char NS_CHATSTATE[] = "http://jabber.org/protocol/chatstates";
+const StaticQName QN_CS_ACTIVE = { NS_CHATSTATE, "active" };
+const StaticQName QN_CS_COMPOSING = { NS_CHATSTATE, "composing" };
+const StaticQName QN_CS_PAUSED = { NS_CHATSTATE, "paused" };
+const StaticQName QN_CS_INACTIVE = { NS_CHATSTATE, "inactive" };
+const StaticQName QN_CS_GONE = { 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");
+const char kNSDelay[] = "jabber:x:delay";
+const StaticQName kQnDelayX = { kNSDelay, "x" };
+const StaticQName kQnStamp = { STR_EMPTY, "stamp" };
// Google time stamping (higher resolution)
-const std::string kNSTimestamp("google:timestamp");
-const QName kQnTime(true, kNSTimestamp, "time");
-const QName kQnMilliseconds(true, STR_EMPTY, "ms");
-
-
-// Event tracking
-#ifdef FEATURE_ENABLE_TRACKING
-const std::string NS_GOOGLE_EVENT_TRACKING("google:client-usability-testing");
-const QName QN_EVENT_TRACKING(true, NS_GOOGLE_EVENT_TRACKING, "usage-stats");
-const QName QN_EVENT_TRACKING_BRANDID(true, NS_GOOGLE_EVENT_TRACKING, "bid");
-const QName QN_EVENT_TRACKING_EVENT(true, NS_GOOGLE_EVENT_TRACKING, "event");
-const QName QN_EVENT_TRACKING_VARIABLE_KEY(true, STR_EMPTY, "key");
-const QName QN_EVENT_TRACKING_VARIABLE_VALUE(true, STR_EMPTY, "value");
-const QName QN_EVENT_TRACKING_VARIABLE_TIME(true, STR_EMPTY, "time");
-const QName QN_EVENT_TRACKING_EVENT_GROUP(true,
- NS_GOOGLE_EVENT_TRACKING, "events");
-#endif
-
+const char kNSTimestamp[] = "google:timestamp";
+const StaticQName kQnTime = { kNSTimestamp, "time" };
+const StaticQName kQnMilliseconds = { 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");
+const char NS_JINGLE_INFO[] = "google:jingleinfo";
+const StaticQName QN_JINGLE_INFO_QUERY = { NS_JINGLE_INFO, "query" };
+const StaticQName QN_JINGLE_INFO_STUN = { NS_JINGLE_INFO, "stun" };
+const StaticQName QN_JINGLE_INFO_RELAY = { NS_JINGLE_INFO, "relay" };
+const StaticQName QN_JINGLE_INFO_SERVER = { NS_JINGLE_INFO, "server" };
+const StaticQName QN_JINGLE_INFO_TOKEN = { NS_JINGLE_INFO, "token" };
+const StaticQName QN_JINGLE_INFO_HOST = { STR_EMPTY, "host" };
+const StaticQName QN_JINGLE_INFO_TCP = { STR_EMPTY, "tcp" };
+const StaticQName QN_JINGLE_INFO_UDP = { STR_EMPTY, "udp" };
+const StaticQName QN_JINGLE_INFO_TCPSSL = { STR_EMPTY, "tcpssl" };
// Call Performance Logging
-const std::string NS_GOOGLE_CALLPERF_STATS("google:call-perf-stats");
-const QName QN_CALLPERF_STATS(true, NS_GOOGLE_CALLPERF_STATS, "callPerfStats");
-const QName QN_CALLPERF_SESSIONID(true, STR_EMPTY, "sessionId");
-const QName QN_CALLPERF_LOCALUSER(true, STR_EMPTY, "localUser");
-const QName QN_CALLPERF_REMOTEUSER(true, STR_EMPTY, "remoteUser");
-const QName QN_CALLPERF_STARTTIME(true, STR_EMPTY, "startTime");
-const QName QN_CALLPERF_CALL_LENGTH(true, STR_EMPTY, "callLength");
-const QName QN_CALLPERF_CALL_ACCEPTED(STR_EMPTY, "callAccepted");
-const QName QN_CALLPERF_CALL_ERROR_CODE(STR_EMPTY, "callErrorCode");
-const QName QN_CALLPERF_TERMINATE_CODE(STR_EMPTY, "terminateCode");
-const QName QN_CALLPERF_DATAPOINT(true, NS_GOOGLE_CALLPERF_STATS, "dataPoint");
-const QName QN_CALLPERF_DATAPOINT_TIME(true, STR_EMPTY, "timeStamp");
-const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST(true, STR_EMPTY, "fraction_lost");
-const QName QN_CALLPERF_DATAPOINT_CUM_LOST(true, STR_EMPTY, "cum_lost");
-const QName QN_CALLPERF_DATAPOINT_EXT_MAX(true, STR_EMPTY, "ext_max");
-const QName QN_CALLPERF_DATAPOINT_JITTER(true, STR_EMPTY, "jitter");
-const QName QN_CALLPERF_DATAPOINT_RTT(true, STR_EMPTY, "RTT");
-const QName QN_CALLPERF_DATAPOINT_BYTES_R(true, STR_EMPTY, "bytesReceived");
-const QName QN_CALLPERF_DATAPOINT_PACKETS_R(true, STR_EMPTY, "packetsReceived");
-const QName QN_CALLPERF_DATAPOINT_BYTES_S(true, STR_EMPTY, "bytesSent");
-const QName QN_CALLPERF_DATAPOINT_PACKETS_S(true, STR_EMPTY, "packetsSent");
-const QName QN_CALLPERF_DATAPOINT_PROCESS_CPU(STR_EMPTY, "processCpu");
-const QName QN_CALLPERF_DATAPOINT_SYSTEM_CPU(STR_EMPTY, "systemCpu");
-const QName QN_CALLPERF_DATAPOINT_CPUS(STR_EMPTY, "cpus");
-const QName QN_CALLPERF_CONNECTION(true, NS_GOOGLE_CALLPERF_STATS, "connection");
-const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS(true, STR_EMPTY, "localAddress");
-const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS(true, STR_EMPTY, "remoteAddress");
-const QName QN_CALLPERF_CONNECTION_FLAGS(STR_EMPTY, "flags");
-const QName QN_CALLPERF_CONNECTION_RTT(STR_EMPTY, "rtt");
-const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S(
- STR_EMPTY, "totalBytesSent");
-const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_S(
- STR_EMPTY, "bytesSecondSent");
-const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R(
- STR_EMPTY, "totalBytesRecv");
-const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_R(
- STR_EMPTY, "bytesSecondRecv");
-const QName QN_CALLPERF_CANDIDATE(NS_GOOGLE_CALLPERF_STATS, "candidate");
-const QName QN_CALLPERF_CANDIDATE_ENDPOINT(STR_EMPTY, "endpoint");
-const QName QN_CALLPERF_CANDIDATE_PROTOCOL(STR_EMPTY, "protocol");
-const QName QN_CALLPERF_CANDIDATE_ADDRESS(STR_EMPTY, "address");
-const QName QN_CALLPERF_MEDIA(NS_GOOGLE_CALLPERF_STATS, "media");
-const QName QN_CALLPERF_MEDIA_DIRECTION(STR_EMPTY, "direction");
-const QName QN_CALLPERF_MEDIA_SSRC(STR_EMPTY, "SSRC");
-const QName QN_CALLPERF_MEDIA_ENERGY(STR_EMPTY, "energy");
-const QName QN_CALLPERF_MEDIA_FIR(STR_EMPTY, "fir");
-const QName QN_CALLPERF_MEDIA_NACK(STR_EMPTY, "nack");
-const QName QN_CALLPERF_MEDIA_FPS(STR_EMPTY, "fps");
-const QName QN_CALLPERF_MEDIA_FPS_NETWORK(STR_EMPTY, "fpsNetwork");
-const QName QN_CALLPERF_MEDIA_FPS_DECODED(STR_EMPTY, "fpsDecoded");
-const QName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE(
- STR_EMPTY, "jitterBufferSize");
-const QName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE(
- STR_EMPTY, "preferredJitterBufferSize");
-const QName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY(
- STR_EMPTY, "totalPlayoutDelay");
+const char NS_GOOGLE_CALLPERF_STATS[] = "google:call-perf-stats";
+const StaticQName QN_CALLPERF_STATS =
+ { NS_GOOGLE_CALLPERF_STATS, "callPerfStats" };
+const StaticQName QN_CALLPERF_SESSIONID = { STR_EMPTY, "sessionId" };
+const StaticQName QN_CALLPERF_LOCALUSER = { STR_EMPTY, "localUser" };
+const StaticQName QN_CALLPERF_REMOTEUSER = { STR_EMPTY, "remoteUser" };
+const StaticQName QN_CALLPERF_STARTTIME = { STR_EMPTY, "startTime" };
+const StaticQName QN_CALLPERF_CALL_LENGTH = { STR_EMPTY, "callLength" };
+const StaticQName QN_CALLPERF_CALL_ACCEPTED = { STR_EMPTY, "callAccepted" };
+const StaticQName QN_CALLPERF_CALL_ERROR_CODE = { STR_EMPTY, "callErrorCode" };
+const StaticQName QN_CALLPERF_TERMINATE_CODE = { STR_EMPTY, "terminateCode" };
+const StaticQName QN_CALLPERF_DATAPOINT =
+ { NS_GOOGLE_CALLPERF_STATS, "dataPoint" };
+const StaticQName QN_CALLPERF_DATAPOINT_TIME = { STR_EMPTY, "timeStamp" };
+const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST =
+ { STR_EMPTY, "fraction_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST = { STR_EMPTY, "cum_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX = { STR_EMPTY, "ext_max" };
+const StaticQName QN_CALLPERF_DATAPOINT_JITTER = { STR_EMPTY, "jitter" };
+const StaticQName QN_CALLPERF_DATAPOINT_RTT = { STR_EMPTY, "RTT" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R =
+ { STR_EMPTY, "bytesReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R =
+ { STR_EMPTY, "packetsReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S = { STR_EMPTY, "bytesSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S =
+ { STR_EMPTY, "packetsSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU =
+ { STR_EMPTY, "processCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU = { STR_EMPTY, "systemCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_CPUS = { STR_EMPTY, "cpus" };
+const StaticQName QN_CALLPERF_CONNECTION =
+ { NS_GOOGLE_CALLPERF_STATS, "connection" };
+const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS =
+ { STR_EMPTY, "localAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS =
+ { STR_EMPTY, "remoteAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_FLAGS = { STR_EMPTY, "flags" };
+const StaticQName QN_CALLPERF_CONNECTION_RTT = { STR_EMPTY, "rtt" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S =
+ { STR_EMPTY, "totalBytesSent" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S =
+ { STR_EMPTY, "bytesSecondSent" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R =
+ { STR_EMPTY, "totalBytesRecv" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R =
+ { STR_EMPTY, "bytesSecondRecv" };
+const StaticQName QN_CALLPERF_CANDIDATE =
+ { NS_GOOGLE_CALLPERF_STATS, "candidate" };
+const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT = { STR_EMPTY, "endpoint" };
+const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL = { STR_EMPTY, "protocol" };
+const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS = { STR_EMPTY, "address" };
+const StaticQName QN_CALLPERF_MEDIA = { NS_GOOGLE_CALLPERF_STATS, "media" };
+const StaticQName QN_CALLPERF_MEDIA_DIRECTION = { STR_EMPTY, "direction" };
+const StaticQName QN_CALLPERF_MEDIA_SSRC = { STR_EMPTY, "SSRC" };
+const StaticQName QN_CALLPERF_MEDIA_ENERGY = { STR_EMPTY, "energy" };
+const StaticQName QN_CALLPERF_MEDIA_FIR = { STR_EMPTY, "fir" };
+const StaticQName QN_CALLPERF_MEDIA_NACK = { STR_EMPTY, "nack" };
+const StaticQName QN_CALLPERF_MEDIA_FPS = { STR_EMPTY, "fps" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK = { STR_EMPTY, "fpsNetwork" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED = { STR_EMPTY, "fpsDecoded" };
+const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE =
+ { STR_EMPTY, "jitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE =
+ { STR_EMPTY, "preferredJitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY =
+ { STR_EMPTY, "totalPlayoutDelay" };
// Muc invites.
-const QName QN_MUC_USER_INVITE(true, NS_MUC_USER, "invite");
+const StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" };
// Multiway audio/video.
-const std::string NS_GOOGLE_MUC_USER("google:muc#user");
-const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA(true, NS_GOOGLE_MUC_USER, "available-media");
-const QName QN_GOOGLE_MUC_USER_ENTRY(true, NS_GOOGLE_MUC_USER, "entry");
-const QName QN_GOOGLE_MUC_USER_MEDIA(true, NS_GOOGLE_MUC_USER, "media");
-const QName QN_GOOGLE_MUC_USER_TYPE(true, NS_GOOGLE_MUC_USER, "type");
-const QName QN_GOOGLE_MUC_USER_SRC_ID(true, NS_GOOGLE_MUC_USER, "src-id");
-const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
-const QName QN_LABEL(true, STR_EMPTY, "label");
+const char NS_GOOGLE_MUC_USER[] = "google:muc#user";
+const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA =
+ { NS_GOOGLE_MUC_USER, "available-media" };
+const StaticQName QN_GOOGLE_MUC_USER_ENTRY = { NS_GOOGLE_MUC_USER, "entry" };
+const StaticQName QN_GOOGLE_MUC_USER_MEDIA = { NS_GOOGLE_MUC_USER, "media" };
+const StaticQName QN_GOOGLE_MUC_USER_TYPE = { NS_GOOGLE_MUC_USER, "type" };
+const StaticQName QN_GOOGLE_MUC_USER_SRC_ID = { NS_GOOGLE_MUC_USER, "src-id" };
+const StaticQName QN_GOOGLE_MUC_USER_STATUS = { NS_GOOGLE_MUC_USER, "status" };
+const StaticQName QN_LABEL = { STR_EMPTY, "label" };
-const std::string NS_GOOGLE_MUC_MEDIA("google:muc#media");
-const QName QN_GOOGLE_MUC_AUDIO_MUTE(
- true, NS_GOOGLE_MUC_MEDIA, "audio-mute");
-const QName QN_GOOGLE_MUC_VIDEO_MUTE(
- true, NS_GOOGLE_MUC_MEDIA, "video-mute");
-const QName QN_GOOGLE_MUC_RECORDING(
- true, NS_GOOGLE_MUC_MEDIA, "recording");
-const QName QN_STATE_ATTR(true, STR_EMPTY, "state");
+const char NS_GOOGLE_MUC_MEDIA[] = "google:muc#media";
+const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE =
+ { NS_GOOGLE_MUC_MEDIA, "audio-mute" };
+const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE =
+ { NS_GOOGLE_MUC_MEDIA, "video-mute" };
+const StaticQName QN_GOOGLE_MUC_RECORDING =
+ { NS_GOOGLE_MUC_MEDIA, "recording" };
+const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK = { NS_GOOGLE_MUC_MEDIA, "block" };
+const StaticQName QN_STATE_ATTR = { STR_EMPTY, "state" };
}
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
index 4409283..ad7d829 100644
--- a/talk/xmpp/constants.h
+++ b/talk/xmpp/constants.h
@@ -32,500 +32,472 @@
#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;
+extern const char NS_CLIENT[];
+extern const char NS_SERVER[];
+extern const char NS_STREAM[];
+extern const char NS_XSTREAM[];
+extern const char NS_TLS[];
+extern const char NS_SASL[];
+extern const char NS_BIND[];
+extern const char NS_DIALBACK[];
+extern const char NS_SESSION[];
+extern const char NS_STANZA[];
+extern const char NS_PRIVACY[];
+extern const char NS_ROSTER[];
+extern const char NS_VCARD[];
+extern const char NS_AVATAR_HASH[];
+extern const char NS_VCARD_UPDATE[];
+extern const char STR_CLIENT[];
+extern const char STR_SERVER[];
+extern const char STR_STREAM[];
-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();
+extern const char STR_GET[];
+extern const char STR_SET[];
+extern const char STR_RESULT[];
+extern const char STR_ERROR[];
- static const std::string & str_client();
- static const std::string & str_server();
- static const std::string & str_stream();
-};
+extern const char STR_FORM[];
+extern const char STR_SUBMIT[];
+extern const char STR_TEXT_SINGLE[];
+extern const char STR_LIST_SINGLE[];
+extern const char STR_LIST_MULTI[];
+extern const char STR_HIDDEN[];
+extern const char STR_FORM_TYPE[];
-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 char STR_FROM[];
+extern const char STR_TO[];
+extern const char STR_BOTH[];
+extern const char STR_REMOVE[];
-extern const std::string STR_FORM;
-extern const std::string STR_SUBMIT;
-extern const std::string STR_TEXT_SINGLE;
-extern const std::string STR_LIST_SINGLE;
-extern const std::string STR_LIST_MULTI;
-extern const std::string STR_HIDDEN;
-extern const std::string STR_FORM_TYPE;
-
-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_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;
-extern const std::string STR_XMPP_GOOGLE_COM;
-extern const std::string STR_XMPPX_L_GOOGLE_COM;
+extern const char STR_TYPE[];
+extern const char STR_NAME[];
+extern const char STR_ID[];
+extern const char STR_JID[];
+extern const char STR_SUBSCRIPTION[];
+extern const char STR_ASK[];
+extern const char STR_X[];
+extern const char STR_GOOGLE_COM[];
+extern const char STR_GMAIL_COM[];
+extern const char STR_GOOGLEMAIL_COM[];
+extern const char STR_DEFAULT_DOMAIN[];
+extern const char STR_TALK_GOOGLE_COM[];
+extern const char STR_TALKX_L_GOOGLE_COM[];
+extern const char STR_XMPP_GOOGLE_COM[];
+extern const char STR_XMPPX_L_GOOGLE_COM[];
#ifdef FEATURE_ENABLE_VOICEMAIL
-extern const std::string STR_VOICEMAIL;
-extern const std::string STR_OUTGOINGVOICEMAIL;
+extern const char STR_VOICEMAIL[];
+extern const char STR_OUTGOINGVOICEMAIL[];
#endif
-extern const std::string STR_UNAVAILABLE;
+extern const char STR_UNAVAILABLE[];
-extern const Jid JID_GOOGLE_MUC_LOOKUP;
-extern const std::string STR_MUC_ROOMCONFIG_ROOMNAME;
-extern const std::string STR_MUC_ROOMCONFIG_FEATURES;
-extern const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE;
+extern const char STR_GOOGLE_MUC_LOOKUP_JID[];
+extern const char STR_MUC_ROOMCONFIG_ROOMNAME[];
+extern const char STR_MUC_ROOMCONFIG_FEATURES[];
+extern const char STR_MUC_ROOM_FEATURE_ENTERPRISE[];
+extern const char STR_MUC_ROOMCONFIG[];
-extern const QName QN_STREAM_STREAM;
-extern const QName QN_STREAM_FEATURES;
-extern const QName QN_STREAM_ERROR;
+extern const StaticQName QN_STREAM_STREAM;
+extern const StaticQName QN_STREAM_FEATURES;
+extern const StaticQName 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 StaticQName QN_XSTREAM_BAD_FORMAT;
+extern const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
+extern const StaticQName QN_XSTREAM_CONFLICT;
+extern const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT;
+extern const StaticQName QN_XSTREAM_HOST_GONE;
+extern const StaticQName QN_XSTREAM_HOST_UNKNOWN;
+extern const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING;
+extern const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_XSTREAM_INVALID_FROM;
+extern const StaticQName QN_XSTREAM_INVALID_ID;
+extern const StaticQName QN_XSTREAM_INVALID_NAMESPACE;
+extern const StaticQName QN_XSTREAM_INVALID_XML;
+extern const StaticQName QN_XSTREAM_NOT_AUTHORIZED;
+extern const StaticQName QN_XSTREAM_POLICY_VIOLATION;
+extern const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
+extern const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_XSTREAM_RESTRICTED_XML;
+extern const StaticQName QN_XSTREAM_SEE_OTHER_HOST;
+extern const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN;
+extern const StaticQName QN_XSTREAM_UNDEFINED_CONDITION;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION;
+extern const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED;
+extern const StaticQName 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 StaticQName QN_TLS_STARTTLS;
+extern const StaticQName QN_TLS_REQUIRED;
+extern const StaticQName QN_TLS_PROCEED;
+extern const StaticQName 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 StaticQName QN_SASL_MECHANISMS;
+extern const StaticQName QN_SASL_MECHANISM;
+extern const StaticQName QN_SASL_AUTH;
+extern const StaticQName QN_SASL_CHALLENGE;
+extern const StaticQName QN_SASL_RESPONSE;
+extern const StaticQName QN_SASL_ABORT;
+extern const StaticQName QN_SASL_SUCCESS;
+extern const StaticQName QN_SASL_FAILURE;
+extern const StaticQName QN_SASL_ABORTED;
+extern const StaticQName QN_SASL_INCORRECT_ENCODING;
+extern const StaticQName QN_SASL_INVALID_AUTHZID;
+extern const StaticQName QN_SASL_INVALID_MECHANISM;
+extern const StaticQName QN_SASL_MECHANISM_TOO_WEAK;
+extern const StaticQName QN_SASL_NOT_AUTHORIZED;
+extern const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE;
-extern const QName QN_DIALBACK_RESULT;
-extern const QName QN_DIALBACK_VERIFY;
+// These are non-standard.
+extern const char NS_GOOGLE_AUTH[];
+extern const char NS_GOOGLE_AUTH_PROTOCOL[];
+extern const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT;
+extern const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN;
-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 StaticQName QN_DIALBACK_RESULT;
+extern const StaticQName QN_DIALBACK_VERIFY;
-extern const QName QN_BIND_BIND;
-extern const QName QN_BIND_RESOURCE;
-extern const QName QN_BIND_JID;
+extern const StaticQName QN_STANZA_BAD_REQUEST;
+extern const StaticQName QN_STANZA_CONFLICT;
+extern const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
+extern const StaticQName QN_STANZA_FORBIDDEN;
+extern const StaticQName QN_STANZA_GONE;
+extern const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_STANZA_ITEM_NOT_FOUND;
+extern const StaticQName QN_STANZA_JID_MALFORMED;
+extern const StaticQName QN_STANZA_NOT_ACCEPTABLE;
+extern const StaticQName QN_STANZA_NOT_ALLOWED;
+extern const StaticQName QN_STANZA_PAYMENT_REQUIRED;
+extern const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE;
+extern const StaticQName QN_STANZA_REDIRECT;
+extern const StaticQName QN_STANZA_REGISTRATION_REQUIRED;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT;
+extern const StaticQName QN_STANZA_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_STANZA_SERVICE_UNAVAILABLE;
+extern const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED;
+extern const StaticQName QN_STANZA_UNDEFINED_CONDITION;
+extern const StaticQName QN_STANZA_UNEXPECTED_REQUEST;
+extern const StaticQName QN_STANZA_TEXT;
-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 StaticQName QN_BIND_BIND;
+extern const StaticQName QN_BIND_RESOURCE;
+extern const StaticQName QN_BIND_JID;
-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 StaticQName QN_MESSAGE;
+extern const StaticQName QN_BODY;
+extern const StaticQName QN_SUBJECT;
+extern const StaticQName QN_THREAD;
+extern const StaticQName QN_PRESENCE;
+extern const StaticQName QN_SHOW;
+extern const StaticQName QN_STATUS;
+extern const StaticQName QN_LANG;
+extern const StaticQName QN_PRIORITY;
+extern const StaticQName QN_IQ;
+extern const StaticQName QN_ERROR;
-extern const QName QN_SESSION_SESSION;
+extern const StaticQName QN_SERVER_MESSAGE;
+extern const StaticQName QN_SERVER_BODY;
+extern const StaticQName QN_SERVER_SUBJECT;
+extern const StaticQName QN_SERVER_THREAD;
+extern const StaticQName QN_SERVER_PRESENCE;
+extern const StaticQName QN_SERVER_SHOW;
+extern const StaticQName QN_SERVER_STATUS;
+extern const StaticQName QN_SERVER_LANG;
+extern const StaticQName QN_SERVER_PRIORITY;
+extern const StaticQName QN_SERVER_IQ;
+extern const StaticQName QN_SERVER_ERROR;
-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 StaticQName QN_SESSION_SESSION;
-extern const QName QN_ROSTER_QUERY;
-extern const QName QN_ROSTER_ITEM;
-extern const QName QN_ROSTER_GROUP;
+extern const StaticQName QN_PRIVACY_QUERY;
+extern const StaticQName QN_PRIVACY_ACTIVE;
+extern const StaticQName QN_PRIVACY_DEFAULT;
+extern const StaticQName QN_PRIVACY_LIST;
+extern const StaticQName QN_PRIVACY_ITEM;
+extern const StaticQName QN_PRIVACY_IQ;
+extern const StaticQName QN_PRIVACY_MESSAGE;
+extern const StaticQName QN_PRIVACY_PRESENCE_IN;
+extern const StaticQName QN_PRIVACY_PRESENCE_OUT;
-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 StaticQName QN_ROSTER_QUERY;
+extern const StaticQName QN_ROSTER_ITEM;
+extern const StaticQName QN_ROSTER_GROUP;
+
+extern const StaticQName QN_VCARD;
+extern const StaticQName QN_VCARD_FN;
+extern const StaticQName QN_VCARD_PHOTO;
+extern const StaticQName QN_VCARD_PHOTO_BINVAL;
+extern const StaticQName QN_VCARD_AVATAR_HASH;
+extern const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED;
#if defined(FEATURE_ENABLE_PSTN)
-extern const QName QN_VCARD_TEL;
-extern const QName QN_VCARD_VOICE;
-extern const QName QN_VCARD_HOME;
-extern const QName QN_VCARD_WORK;
-extern const QName QN_VCARD_CELL;
-extern const QName QN_VCARD_NUMBER;
+extern const StaticQName QN_VCARD_TEL;
+extern const StaticQName QN_VCARD_VOICE;
+extern const StaticQName QN_VCARD_HOME;
+extern const StaticQName QN_VCARD_WORK;
+extern const StaticQName QN_VCARD_CELL;
+extern const StaticQName QN_VCARD_NUMBER;
#endif
#if defined(FEATURE_ENABLE_RICHPROFILES)
-extern const QName QN_USER_PROFILE_QUERY;
-extern const QName QN_USER_PROFILE_URL;
+extern const StaticQName QN_USER_PROFILE_QUERY;
+extern const StaticQName QN_USER_PROFILE_URL;
-extern const QName QN_ATOM_FEED;
-extern const QName QN_ATOM_ENTRY;
-extern const QName QN_ATOM_TITLE;
-extern const QName QN_ATOM_ID;
-extern const QName QN_ATOM_MODIFIED;
-extern const QName QN_ATOM_IMAGE;
-extern const QName QN_ATOM_LINK;
-extern const QName QN_ATOM_HREF;
+extern const StaticQName QN_ATOM_FEED;
+extern const StaticQName QN_ATOM_ENTRY;
+extern const StaticQName QN_ATOM_TITLE;
+extern const StaticQName QN_ATOM_ID;
+extern const StaticQName QN_ATOM_MODIFIED;
+extern const StaticQName QN_ATOM_IMAGE;
+extern const StaticQName QN_ATOM_LINK;
+extern const StaticQName QN_ATOM_HREF;
#endif
-extern const QName QN_XML_LANG;
+extern const StaticQName 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_NICK;
-extern const QName QN_SUBSCRIPTION;
-extern const QName QN_TITLE1;
-extern const QName QN_TITLE2;
-extern const QName QN_AFFILIATION;
-extern const QName QN_ROLE;
-extern const QName QN_TIME;
+extern const StaticQName QN_ENCODING;
+extern const StaticQName QN_VERSION;
+extern const StaticQName QN_TO;
+extern const StaticQName QN_FROM;
+extern const StaticQName QN_TYPE;
+extern const StaticQName QN_ID;
+extern const StaticQName QN_CODE;
+extern const StaticQName QN_NAME;
+extern const StaticQName QN_VALUE;
+extern const StaticQName QN_ACTION;
+extern const StaticQName QN_ORDER;
+extern const StaticQName QN_MECHANISM;
+extern const StaticQName QN_ASK;
+extern const StaticQName QN_JID;
+extern const StaticQName QN_NICK;
+extern const StaticQName QN_SUBSCRIPTION;
+extern const StaticQName QN_TITLE1;
+extern const StaticQName QN_TITLE2;
+extern const StaticQName QN_AFFILIATION;
+extern const StaticQName QN_ROLE;
+extern const StaticQName QN_TIME;
-
-extern const QName QN_XMLNS_CLIENT;
-extern const QName QN_XMLNS_SERVER;
-extern const QName QN_XMLNS_STREAM;
+extern const StaticQName QN_XMLNS_CLIENT;
+extern const StaticQName QN_XMLNS_SERVER;
+extern const StaticQName 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;
+extern const char STR_SHOW_AWAY[];
+extern const char STR_SHOW_CHAT[];
+extern const char STR_SHOW_DND[];
+extern const char STR_SHOW_XA[];
+extern const char 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;
+extern const char STR_SUBSCRIBE[];
+extern const char STR_SUBSCRIBED[];
+extern const char STR_UNSUBSCRIBE[];
+extern const char STR_UNSUBSCRIBED[];
// Google Invite
-extern const std::string NS_GOOGLE_SUBSCRIBE;
-extern const QName QN_INVITATION;
-extern const QName QN_INVITE_NAME;
-extern const QName QN_INVITE_SUBJECT;
-extern const QName QN_INVITE_MESSAGE;
+extern const char NS_GOOGLE_SUBSCRIBE[];
+extern const StaticQName QN_INVITATION;
+extern const StaticQName QN_INVITE_NAME;
+extern const StaticQName QN_INVITE_SUBJECT;
+extern const StaticQName QN_INVITE_MESSAGE;
// PubSub: http://xmpp.org/extensions/xep-0060.html
-extern const std::string NS_PUBSUB;
-extern const QName QN_PUBSUB;
-extern const QName QN_PUBSUB_ITEMS;
-extern const QName QN_PUBSUB_ITEM;
-extern const QName QN_PUBSUB_PUBLISH;
-extern const QName QN_PUBSUB_RETRACT;
-extern const QName QN_ATTR_PUBLISHER;
+extern const char NS_PUBSUB[];
+extern const StaticQName QN_PUBSUB;
+extern const StaticQName QN_PUBSUB_ITEMS;
+extern const StaticQName QN_PUBSUB_ITEM;
+extern const StaticQName QN_PUBSUB_PUBLISH;
+extern const StaticQName QN_PUBSUB_RETRACT;
+extern const StaticQName QN_ATTR_PUBLISHER;
-extern const std::string NS_PUBSUB_EVENT;
-extern const QName QN_NODE;
-extern const QName QN_PUBSUB_EVENT;
-extern const QName QN_PUBSUB_EVENT_ITEMS;
-extern const QName QN_PUBSUB_EVENT_ITEM;
-extern const QName QN_PUBSUB_EVENT_RETRACT;
-extern const QName QN_NOTIFY;
+extern const char NS_PUBSUB_EVENT[];
+extern const StaticQName QN_NODE;
+extern const StaticQName QN_PUBSUB_EVENT;
+extern const StaticQName QN_PUBSUB_EVENT_ITEMS;
+extern const StaticQName QN_PUBSUB_EVENT_ITEM;
+extern const StaticQName QN_PUBSUB_EVENT_RETRACT;
+extern const StaticQName QN_NOTIFY;
-
-extern const std::string NS_PRESENTER;
-extern const QName QN_PRESENTER_PRESENTER;
-extern const QName QN_PRESENTER_PRESENTATION_ITEM;
-extern const QName QN_PRESENTER_PRESENTATION_TYPE;
-extern const QName QN_PRESENTER_PRESENTATION_ID;
-
-
-
+extern const char NS_PRESENTER[];
+extern const StaticQName QN_PRESENTER_PRESENTER;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ITEM;
+extern const StaticQName QN_PRESENTER_PRESENTATION_TYPE;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ID;
// JEP 0030
-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 StaticQName QN_CATEGORY;
+extern const StaticQName QN_VAR;
+extern const char NS_DISCO_INFO[];
+extern const char NS_DISCO_ITEMS[];
-extern const QName QN_DISCO_INFO_QUERY;
-extern const QName QN_DISCO_IDENTITY;
-extern const QName QN_DISCO_FEATURE;
+extern const StaticQName QN_DISCO_INFO_QUERY;
+extern const StaticQName QN_DISCO_IDENTITY;
+extern const StaticQName QN_DISCO_FEATURE;
-extern const QName QN_DISCO_ITEMS_QUERY;
-extern const QName QN_DISCO_ITEM;
+extern const StaticQName QN_DISCO_ITEMS_QUERY;
+extern const StaticQName QN_DISCO_ITEM;
// JEP 0020
-extern const std::string NS_FEATURE;
-extern const QName QN_FEATURE_FEATURE;
+extern const char NS_FEATURE[];
+extern const StaticQName QN_FEATURE_FEATURE;
// JEP 0004
-extern const std::string NS_XDATA;
-extern const QName QN_XDATA_X;
-extern const QName QN_XDATA_INSTRUCTIONS;
-extern const QName QN_XDATA_TITLE;
-extern const QName QN_XDATA_FIELD;
-extern const QName QN_XDATA_REPORTED;
-extern const QName QN_XDATA_ITEM;
-extern const QName QN_XDATA_DESC;
-extern const QName QN_XDATA_REQUIRED;
-extern const QName QN_XDATA_VALUE;
-extern const QName QN_XDATA_OPTION;
+extern const char NS_XDATA[];
+extern const StaticQName QN_XDATA_X;
+extern const StaticQName QN_XDATA_INSTRUCTIONS;
+extern const StaticQName QN_XDATA_TITLE;
+extern const StaticQName QN_XDATA_FIELD;
+extern const StaticQName QN_XDATA_REPORTED;
+extern const StaticQName QN_XDATA_ITEM;
+extern const StaticQName QN_XDATA_DESC;
+extern const StaticQName QN_XDATA_REQUIRED;
+extern const StaticQName QN_XDATA_VALUE;
+extern const StaticQName QN_XDATA_OPTION;
// JEP 0045
-extern const std::string NS_MUC;
-extern const QName QN_MUC_X;
-extern const QName QN_MUC_ITEM;
-extern const QName QN_MUC_AFFILIATION;
-extern const QName QN_MUC_ROLE;
-extern const std::string STR_AFFILIATION_NONE;
-extern const std::string STR_ROLE_PARTICIPANT;
+extern const char NS_MUC[];
+extern const StaticQName QN_MUC_X;
+extern const StaticQName QN_MUC_ITEM;
+extern const StaticQName QN_MUC_AFFILIATION;
+extern const StaticQName QN_MUC_ROLE;
+extern const char STR_AFFILIATION_NONE[];
+extern const char STR_ROLE_PARTICIPANT[];
-extern const std::string NS_MUC_OWNER;
-extern const QName QN_MUC_OWNER_QUERY;
+extern const char NS_MUC_OWNER[];
+extern const StaticQName QN_MUC_OWNER_QUERY;
-extern const std::string NS_MUC_USER;
-extern const QName QN_MUC_USER_CONTINUE;
-extern const QName QN_MUC_USER_X;
-extern const QName QN_MUC_USER_ITEM;
-extern const QName QN_MUC_USER_STATUS;
-
+extern const char NS_MUC_USER[];
+extern const StaticQName QN_MUC_USER_CONTINUE;
+extern const StaticQName QN_MUC_USER_X;
+extern const StaticQName QN_MUC_USER_ITEM;
+extern const StaticQName QN_MUC_USER_STATUS;
// JEP 0055 - Jabber Search
-extern const std::string NS_SEARCH;
-extern const QName QN_SEARCH_QUERY;
-extern const QName QN_SEARCH_ITEM;
-extern const QName QN_SEARCH_ROOM_NAME;
-extern const QName QN_SEARCH_ROOM_JID;
-extern const QName QN_SEARCH_ROOM_DOMAIN;
-
+extern const char NS_SEARCH[];
+extern const StaticQName QN_SEARCH_QUERY;
+extern const StaticQName QN_SEARCH_ITEM;
+extern const StaticQName QN_SEARCH_ROOM_NAME;
+extern const StaticQName QN_SEARCH_ROOM_JID;
+extern const StaticQName QN_SEARCH_ROOM_DOMAIN;
// JEP 0115
-extern const std::string NS_CAPS;
-extern const QName QN_CAPS_C;
-extern const QName QN_VER;
-extern const QName QN_EXT;
+extern const char NS_CAPS[];
+extern const StaticQName QN_CAPS_C;
+extern const StaticQName QN_VER;
+extern const StaticQName QN_EXT;
// Avatar - JEP 0153
-extern const std::string kNSVCard;
-extern const QName kQnVCardX;
-extern const QName kQnVCardPhoto;
+extern const char kNSVCard[];
+extern const StaticQName kQnVCardX;
+extern const StaticQName kQnVCardPhoto;
// JEP 0172 User Nickname
-extern const std::string NS_NICKNAME;
-extern const QName QN_NICKNAME;
-
+extern const char NS_NICKNAME[];
+extern const StaticQName QN_NICKNAME;
// 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;
+extern const char NS_CHATSTATE[];
+extern const StaticQName QN_CS_ACTIVE;
+extern const StaticQName QN_CS_COMPOSING;
+extern const StaticQName QN_CS_PAUSED;
+extern const StaticQName QN_CS_INACTIVE;
+extern const StaticQName QN_CS_GONE;
// JEP 0091 Delayed Delivery
-extern const std::string kNSDelay;
-extern const QName kQnDelayX;
-extern const QName kQnStamp;
+extern const char kNSDelay[];
+extern const StaticQName kQnDelayX;
+extern const StaticQName kQnStamp;
// Google time stamping (higher resolution)
-extern const std::string kNSTimestamp;
-extern const QName kQnTime;
-extern const QName kQnMilliseconds;
+extern const char kNSTimestamp[];
+extern const StaticQName kQnTime;
+extern const StaticQName kQnMilliseconds;
+extern const char NS_JINGLE_INFO[];
+extern const StaticQName QN_JINGLE_INFO_QUERY;
+extern const StaticQName QN_JINGLE_INFO_STUN;
+extern const StaticQName QN_JINGLE_INFO_RELAY;
+extern const StaticQName QN_JINGLE_INFO_SERVER;
+extern const StaticQName QN_JINGLE_INFO_TOKEN;
+extern const StaticQName QN_JINGLE_INFO_HOST;
+extern const StaticQName QN_JINGLE_INFO_TCP;
+extern const StaticQName QN_JINGLE_INFO_UDP;
+extern const StaticQName QN_JINGLE_INFO_TCPSSL;
-extern const std::string NS_JINGLE_INFO;
-extern const QName QN_JINGLE_INFO_QUERY;
-extern const QName QN_JINGLE_INFO_STUN;
-extern const QName QN_JINGLE_INFO_RELAY;
-extern const QName QN_JINGLE_INFO_SERVER;
-extern const QName QN_JINGLE_INFO_TOKEN;
-extern const QName QN_JINGLE_INFO_HOST;
-extern const QName QN_JINGLE_INFO_TCP;
-extern const QName QN_JINGLE_INFO_UDP;
-extern const QName QN_JINGLE_INFO_TCPSSL;
-
-extern const std::string NS_GOOGLE_CALLPERF_STATS;
-extern const QName QN_CALLPERF_STATS;
-extern const QName QN_CALLPERF_SESSIONID;
-extern const QName QN_CALLPERF_LOCALUSER;
-extern const QName QN_CALLPERF_REMOTEUSER;
-extern const QName QN_CALLPERF_STARTTIME;
-extern const QName QN_CALLPERF_CALL_LENGTH;
-extern const QName QN_CALLPERF_CALL_ACCEPTED;
-extern const QName QN_CALLPERF_CALL_ERROR_CODE;
-extern const QName QN_CALLPERF_TERMINATE_CODE;
-extern const QName QN_CALLPERF_DATAPOINT;
-extern const QName QN_CALLPERF_DATAPOINT_TIME;
-extern const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
-extern const QName QN_CALLPERF_DATAPOINT_CUM_LOST;
-extern const QName QN_CALLPERF_DATAPOINT_EXT_MAX;
-extern const QName QN_CALLPERF_DATAPOINT_JITTER;
-extern const QName QN_CALLPERF_DATAPOINT_RTT;
-extern const QName QN_CALLPERF_DATAPOINT_BYTES_R;
-extern const QName QN_CALLPERF_DATAPOINT_PACKETS_R;
-extern const QName QN_CALLPERF_DATAPOINT_BYTES_S;
-extern const QName QN_CALLPERF_DATAPOINT_PACKETS_S;
-extern const QName QN_CALLPERF_DATAPOINT_PROCESS_CPU;
-extern const QName QN_CALLPERF_DATAPOINT_SYSTEM_CPU;
-extern const QName QN_CALLPERF_DATAPOINT_CPUS;
-extern const QName QN_CALLPERF_CONNECTION;
-extern const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
-extern const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
-extern const QName QN_CALLPERF_CONNECTION_FLAGS;
-extern const QName QN_CALLPERF_CONNECTION_RTT;
-extern const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S;
-extern const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_S;
-extern const QName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R;
-extern const QName QN_CALLPERF_CONNECTION_BYTES_SECOND_R;
-extern const QName QN_CALLPERF_CANDIDATE;
-extern const QName QN_CALLPERF_CANDIDATE_ENDPOINT;
-extern const QName QN_CALLPERF_CANDIDATE_PROTOCOL;
-extern const QName QN_CALLPERF_CANDIDATE_ADDRESS;
-extern const QName QN_CALLPERF_MEDIA;
-extern const QName QN_CALLPERF_MEDIA_DIRECTION;
-extern const QName QN_CALLPERF_MEDIA_SSRC;
-extern const QName QN_CALLPERF_MEDIA_ENERGY;
-extern const QName QN_CALLPERF_MEDIA_FIR;
-extern const QName QN_CALLPERF_MEDIA_NACK;
-extern const QName QN_CALLPERF_MEDIA_FPS;
-extern const QName QN_CALLPERF_MEDIA_FPS_NETWORK;
-extern const QName QN_CALLPERF_MEDIA_FPS_DECODED;
-extern const QName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE;
-extern const QName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE;
-extern const QName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY;
+extern const char NS_GOOGLE_CALLPERF_STATS[];
+extern const StaticQName QN_CALLPERF_STATS;
+extern const StaticQName QN_CALLPERF_SESSIONID;
+extern const StaticQName QN_CALLPERF_LOCALUSER;
+extern const StaticQName QN_CALLPERF_REMOTEUSER;
+extern const StaticQName QN_CALLPERF_STARTTIME;
+extern const StaticQName QN_CALLPERF_CALL_LENGTH;
+extern const StaticQName QN_CALLPERF_CALL_ACCEPTED;
+extern const StaticQName QN_CALLPERF_CALL_ERROR_CODE;
+extern const StaticQName QN_CALLPERF_TERMINATE_CODE;
+extern const StaticQName QN_CALLPERF_DATAPOINT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_TIME;
+extern const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX;
+extern const StaticQName QN_CALLPERF_DATAPOINT_JITTER;
+extern const StaticQName QN_CALLPERF_DATAPOINT_RTT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CPUS;
+extern const StaticQName QN_CALLPERF_CONNECTION;
+extern const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_FLAGS;
+extern const StaticQName QN_CALLPERF_CONNECTION_RTT;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R;
+extern const StaticQName QN_CALLPERF_CANDIDATE;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT;
+extern const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS;
+extern const StaticQName QN_CALLPERF_MEDIA;
+extern const StaticQName QN_CALLPERF_MEDIA_DIRECTION;
+extern const StaticQName QN_CALLPERF_MEDIA_SSRC;
+extern const StaticQName QN_CALLPERF_MEDIA_ENERGY;
+extern const StaticQName QN_CALLPERF_MEDIA_FIR;
+extern const StaticQName QN_CALLPERF_MEDIA_NACK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED;
+extern const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY;
// Muc invites.
-extern const QName QN_MUC_USER_INVITE;
+extern const StaticQName QN_MUC_USER_INVITE;
// Multiway audio/video.
-extern const std::string NS_GOOGLE_MUC_USER;
-extern const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
-extern const QName QN_GOOGLE_MUC_USER_ENTRY;
-extern const QName QN_GOOGLE_MUC_USER_MEDIA;
-extern const QName QN_GOOGLE_MUC_USER_TYPE;
-extern const QName QN_GOOGLE_MUC_USER_SRC_ID;
-extern const QName QN_GOOGLE_MUC_USER_STATUS;
-extern const QName QN_LABEL;
+extern const char NS_GOOGLE_MUC_USER[];
+extern const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_ENTRY;
+extern const StaticQName QN_GOOGLE_MUC_USER_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_TYPE;
+extern const StaticQName QN_GOOGLE_MUC_USER_SRC_ID;
+extern const StaticQName QN_GOOGLE_MUC_USER_STATUS;
+extern const StaticQName QN_LABEL;
-extern const std::string NS_GOOGLE_MUC_MEDIA;
-extern const QName QN_GOOGLE_MUC_AUDIO_MUTE;
-extern const QName QN_GOOGLE_MUC_VIDEO_MUTE;
-extern const QName QN_GOOGLE_MUC_RECORDING;
-extern const QName QN_STATE_ATTR;
+extern const char NS_GOOGLE_MUC_MEDIA[];
+extern const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_RECORDING;
+extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK;
+extern const StaticQName QN_STATE_ATTR;
} // namespace buzz
diff --git a/talk/xmpp/fakexmppclient.h b/talk/xmpp/fakexmppclient.h
new file mode 100644
index 0000000..83b8e82
--- /dev/null
+++ b/talk/xmpp/fakexmppclient.h
@@ -0,0 +1,123 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A fake XmppClient for use in unit tests.
+
+#ifndef TALK_XMPP_FAKEXMPPCLIENT_H_
+#define TALK_XMPP_FAKEXMPPCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class XmlElement;
+
+class FakeXmppClient : public XmppTaskParentInterface,
+ public XmppClientInterface {
+ public:
+ explicit FakeXmppClient(talk_base::TaskParent* parent)
+ : XmppTaskParentInterface(parent) {
+ }
+
+ // As XmppTaskParentInterface
+ virtual XmppClientInterface* GetClient() {
+ return this;
+ }
+
+ virtual int ProcessStart() {
+ return STATE_RESPONSE;
+ }
+
+ // As XmppClientInterface
+ virtual XmppEngine::State GetState() const {
+ return XmppEngine::STATE_OPEN;
+ }
+
+ virtual const Jid& jid() const {
+ return jid_;
+ }
+
+ virtual std::string NextId() {
+ // Implement if needed for tests.
+ return "0";
+ }
+
+ virtual XmppReturnStatus SendStanza(const XmlElement* stanza) {
+ sent_stanzas_.push_back(stanza);
+ return XMPP_RETURN_OK;
+ }
+
+ const std::vector<const XmlElement*>& sent_stanzas() {
+ return sent_stanzas_;
+ }
+
+ virtual XmppReturnStatus SendStanzaError(
+ const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text) {
+ // Implement if needed for tests.
+ return XMPP_RETURN_OK;
+ }
+
+ virtual void AddXmppTask(XmppTask* task,
+ XmppEngine::HandlerLevel level) {
+ tasks_.push_back(task);
+ }
+
+ virtual void RemoveXmppTask(XmppTask* task) {
+ std::remove(tasks_.begin(), tasks_.end(), task);
+ }
+
+ // As FakeXmppClient
+ void set_jid(const Jid& jid) {
+ jid_ = jid;
+ }
+
+ // Takes ownership of stanza.
+ void HandleStanza(XmlElement* stanza) {
+ for (std::vector<XmppTask*>::iterator task = tasks_.begin();
+ task != tasks_.end(); ++task) {
+ if ((*task)->HandleStanza(stanza)) {
+ delete stanza;
+ return;
+ }
+ }
+ delete stanza;
+ }
+
+ private:
+ Jid jid_;
+ std::vector<XmppTask*> tasks_;
+ std::vector<const XmlElement*> sent_stanzas_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_FAKEXMPPCLIENT_H_
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
index ea5534d..8db2d45 100644
--- a/talk/xmpp/hangoutpubsubclient.cc
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -40,8 +40,18 @@
namespace buzz {
namespace {
-const std::string kPresenting = "s";
-const std::string kNotPresenting = "o";
+const char kPresenting[] = "s";
+const char kNotPresenting[] = "o";
+const char kEmpty[] = "";
+
+const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem) {
+ if (item_elem == NULL) {
+ return "";
+ }
+
+ return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
+}
+
} // namespace
@@ -49,10 +59,39 @@
template <typename C>
class PubSubStateSerializer {
public:
+ virtual ~PubSubStateSerializer() {}
virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
virtual C Parse(const XmlElement* state_elem) = 0;
};
+// Knows how to create "keys" for states, which determines their
+// uniqueness. Most states are per-nick, but block is
+// per-blocker-and-blockee. This is independent of itemid, especially
+// in the case of presenter state.
+class PubSubStateKeySerializer {
+ public:
+ virtual ~PubSubStateKeySerializer() {}
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick) = 0;
+};
+
+class PublishedNickKeySerializer : public PubSubStateKeySerializer {
+ public:
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick) {
+ return published_nick;
+ }
+};
+
+class PublisherAndPublishedNicksKeySerializer
+ : public PubSubStateKeySerializer {
+ public:
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick) {
+ return publisher_nick + ":" + published_nick;
+ }
+};
+
// A simple serialiazer where presence of item => true, lack of item
// => false.
class BoolStateSerializer : public PubSubStateSerializer<bool> {
@@ -77,15 +116,19 @@
template <typename C>
class PubSubStateClient : public sigslot::has_slots<> {
public:
- // Gets ownership of the serializer, but not the client.
- PubSubStateClient(PubSubClient* client,
+ // Gets ownership of the serializers, but not the client.
+ PubSubStateClient(const std::string& publisher_nick,
+ PubSubClient* client,
const QName& state_name,
C default_state,
- PubSubStateSerializer<C>* serializer)
- : client_(client),
+ PubSubStateKeySerializer* key_serializer,
+ PubSubStateSerializer<C>* state_serializer)
+ : publisher_nick_(publisher_nick),
+ client_(client),
state_name_(state_name),
default_state_(default_state) {
- serializer_.reset(serializer);
+ key_serializer_.reset(key_serializer);
+ state_serializer_.reset(state_serializer);
client_->SignalItems.connect(
this, &PubSubStateClient<C>::OnItems);
client_->SignalPublishResult.connect(
@@ -100,16 +143,16 @@
virtual ~PubSubStateClient() {}
- virtual void Publish(const std::string& key, const C& state,
+ virtual void Publish(const std::string& published_nick,
+ const C& state,
std::string* task_id_out) {
- const std::string& nick = key;
-
- std::string itemid = state_name_.LocalPart() + ":" + nick;
+ std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
+ std::string itemid = state_name_.LocalPart() + ":" + key;
if (StatesEqual(state, default_state_)) {
client_->RetractItem(itemid, task_id_out);
} else {
- XmlElement* state_elem = serializer_->Write(state_name_, state);
- state_elem->AddAttr(QN_NICK, nick);
+ XmlElement* state_elem = state_serializer_->Write(state_name_, state);
+ state_elem->AddAttr(QN_NICK, published_nick);
client_->PublishItem(itemid, state_elem, task_id_out);
}
};
@@ -124,17 +167,18 @@
const XmlElement*> SignalPublishError;
protected:
- // return false if retracted item (no state given)
- virtual bool ParseState(const PubSubItem& item,
- std::string* key_out,
- bool* state_out) {
+ // return false if retracted item (no info or state given)
+ virtual bool ParseStateItem(const PubSubItem& item,
+ StateItemInfo* info_out,
+ bool* state_out) {
const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
if (state_elem == NULL) {
return false;
}
- *key_out = state_elem->Attr(QN_NICK);
- *state_out = serializer_->Parse(state_elem);
+ info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
+ info_out->published_nick = state_elem->Attr(QN_NICK);
+ *state_out = state_serializer_->Parse(state_elem);
return true;
};
@@ -155,27 +199,30 @@
void OnItem(const PubSubItem& item) {
const std::string& itemid = item.itemid;
-
- std::string key;
+ StateItemInfo info;
C new_state;
- bool retracted = !ParseState(item, &key, &new_state);
+
+ bool retracted = !ParseStateItem(item, &info, &new_state);
if (retracted) {
- bool known_itemid = (key_by_itemid_.find(itemid) != key_by_itemid_.end());
+ bool known_itemid =
+ (info_by_itemid_.find(itemid) != info_by_itemid_.end());
if (!known_itemid) {
// Nothing to retract, and nothing to publish.
// Probably a different state type.
return;
} else {
- key = key_by_itemid_[itemid];
- key_by_itemid_.erase(itemid);
+ info = info_by_itemid_[itemid];
+ info_by_itemid_.erase(itemid);
new_state = default_state_;
}
} else {
- // TODO: Assert parsed key matches the known key when
- // not retracted. It shouldn't change!
- key_by_itemid_[itemid] = key;
+ // TODO: Assert new key matches the known key. It
+ // shouldn't change!
+ info_by_itemid_[itemid] = info;
}
+ std::string key = key_serializer_->GetKey(
+ info.publisher_nick, info.published_nick);
bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
const C& old_state = has_old_state ? state_by_key_[key] : default_state_;
if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
@@ -191,11 +238,10 @@
}
PubSubStateChange<C> change;
- change.key = key;
+ change.publisher_nick = info.publisher_nick;
+ change.published_nick = info.published_nick;
change.old_state = old_state;
change.new_state = new_state;
- change.publisher_nick =
- Jid(item.elem->Attr(QN_ATTR_PUBLISHER)).resource();
SignalStateChange(change);
}
@@ -231,33 +277,36 @@
SignalPublishError(task_id, item, stanza);
}
+ std::string publisher_nick_;
PubSubClient* client_;
- const QName& state_name_;
+ const QName state_name_;
C default_state_;
- talk_base::scoped_ptr<PubSubStateSerializer<C> > serializer_;
+ talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
+ talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
// key => state
std::map<std::string, C> state_by_key_;
- // itemid => key
- std::map<std::string, std::string> key_by_itemid_;
+ // itemid => StateItemInfo
+ std::map<std::string, StateItemInfo> info_by_itemid_;
};
class PresenterStateClient : public PubSubStateClient<bool> {
public:
- PresenterStateClient(PubSubClient* client,
+ PresenterStateClient(const std::string& publisher_nick,
+ PubSubClient* client,
const QName& state_name,
- bool default_state,
- PubSubStateSerializer<bool>* serializer = NULL)
- : PubSubStateClient<bool>(client, state_name, default_state, serializer) {
+ bool default_state)
+ : PubSubStateClient<bool>(
+ publisher_nick, client, state_name, default_state,
+ new PublishedNickKeySerializer(), NULL) {
}
- virtual void Publish(const std::string& key, const bool& state,
+ virtual void Publish(const std::string& published_nick,
+ const bool& state,
std::string* task_id_out) {
- const std::string& nick = key;
-
XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
// There's a dummy value, not used, but required.
presenter_elem->AddAttr(QN_JID, "dummy@value.net");
- presenter_elem->AddAttr(QN_NICK, nick);
+ presenter_elem->AddAttr(QN_NICK, published_nick);
XmlElement* presentation_item_elem =
new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
@@ -267,7 +316,7 @@
// The Presenter state is kind of dumb in that it doesn't use
// retracts. It relies on setting the "type" to a special value.
- std::string itemid = nick;
+ std::string itemid = published_nick;
std::vector<XmlElement*> children;
children.push_back(presenter_elem);
children.push_back(presentation_item_elem);
@@ -275,9 +324,9 @@
}
protected:
- virtual bool ParseState(const PubSubItem& item,
- std::string* key_out,
- bool* state_out) {
+ virtual bool ParseStateItem(const PubSubItem& item,
+ StateItemInfo* info_out,
+ bool* state_out) {
const XmlElement* presenter_elem =
item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
const XmlElement* presentation_item_elem =
@@ -286,11 +335,16 @@
return false;
}
- *key_out = presenter_elem->Attr(QN_NICK);
+ info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
+ info_out->published_nick = presenter_elem->Attr(QN_NICK);
*state_out = (presentation_item_elem->Attr(
QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
return true;
}
+
+ virtual bool StatesEqual(bool state1, bool state2) {
+ return false; // Make every item trigger an event, even if state doesn't change.
+ }
};
HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
@@ -307,7 +361,7 @@
this, &HangoutPubSubClient::OnMediaRequestError);
presenter_state_client_.reset(new PresenterStateClient(
- presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+ nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
presenter_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnPresenterStateChange);
presenter_state_client_->SignalPublishResult.connect(
@@ -316,8 +370,8 @@
this, &HangoutPubSubClient::OnPresenterPublishError);
audio_mute_state_client_.reset(new PubSubStateClient<bool>(
- media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
- new BoolStateSerializer()));
+ nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
// Can't just repeat because we need to watch for remote mutes.
audio_mute_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnAudioMuteStateChange);
@@ -327,14 +381,25 @@
this, &HangoutPubSubClient::OnAudioMutePublishError);
recording_state_client_.reset(new PubSubStateClient<bool>(
- media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
- new BoolStateSerializer()));
+ nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
recording_state_client_->SignalStateChange.connect(
this, &HangoutPubSubClient::OnRecordingStateChange);
recording_state_client_->SignalPublishResult.connect(
this, &HangoutPubSubClient::OnRecordingPublishResult);
recording_state_client_->SignalPublishError.connect(
this, &HangoutPubSubClient::OnRecordingPublishError);
+
+ media_block_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
+ new PublisherAndPublishedNicksKeySerializer(),
+ new BoolStateSerializer()));
+ media_block_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnMediaBlockStateChange);
+ media_block_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnMediaBlockPublishResult);
+ media_block_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnMediaBlockPublishError);
}
HangoutPubSubClient::~HangoutPubSubClient() {
@@ -376,10 +441,17 @@
audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
}
+// Block media is accomplished by setting another client's block
+// state, kind of like remote mute.
+void HangoutPubSubClient::BlockMedia(
+ const std::string& blockee_nick, std::string* task_id_out) {
+ media_block_state_client_->Publish(blockee_nick, true, task_id_out);
+}
+
void HangoutPubSubClient::OnPresenterStateChange(
const PubSubStateChange<bool>& change) {
- const std::string& nick = change.key;
- SignalPresenterStateChange(nick, change.old_state, change.new_state);
+ SignalPresenterStateChange(
+ change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnPresenterPublishResult(
@@ -398,23 +470,21 @@
// ourselves. Note that we never remote un-mute, though.
void HangoutPubSubClient::OnAudioMuteStateChange(
const PubSubStateChange<bool>& change) {
- const std::string& nick = change.key;
-
bool was_muted = change.old_state;
bool is_muted = change.new_state;
- bool remote_action =
- !change.publisher_nick.empty() && (change.publisher_nick != nick);
+ bool remote_action = (!change.publisher_nick.empty() &&
+ (change.publisher_nick != change.published_nick));
if (is_muted && remote_action) {
- const std::string& mutee_nick = nick;
+ const std::string& mutee_nick = change.published_nick;
const std::string& muter_nick = change.publisher_nick;
bool should_mute_locally = (mutee_nick == nick_);
SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
} else {
- SignalAudioMuteStateChange(nick, was_muted, is_muted);
+ SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
}
}
-const std::string& GetAudioMuteNickFromItem(const XmlElement* item) {
+const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
if (item != NULL) {
const XmlElement* audio_mute_state =
item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
@@ -422,7 +492,18 @@
return audio_mute_state->Attr(QN_NICK);
}
}
- return STR_EMPTY;
+ return std::string();
+}
+
+const std::string GetBlockeeNickFromItem(const XmlElement* item) {
+ if (item != NULL) {
+ const XmlElement* media_block_state =
+ item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
+ if (media_block_state != NULL) {
+ return media_block_state->Attr(QN_NICK);
+ }
+ }
+ return std::string();
}
void HangoutPubSubClient::OnAudioMutePublishResult(
@@ -448,8 +529,8 @@
void HangoutPubSubClient::OnRecordingStateChange(
const PubSubStateChange<bool>& change) {
- const std::string& nick = change.key;
- SignalRecordingStateChange(nick, change.old_state, change.new_state);
+ SignalRecordingStateChange(
+ change.published_nick, change.old_state, change.new_state);
}
void HangoutPubSubClient::OnRecordingPublishResult(
@@ -463,4 +544,32 @@
SignalPublishRecordingError(task_id, stanza);
}
+void HangoutPubSubClient::OnMediaBlockStateChange(
+ const PubSubStateChange<bool>& change) {
+ const std::string& blockee_nick = change.published_nick;
+ const std::string& blocker_nick = change.publisher_nick;
+
+ bool was_blockee = change.old_state;
+ bool is_blockee = change.new_state;
+ if (!was_blockee && is_blockee) {
+ SignalMediaBlock(blockee_nick, blocker_nick);
+ }
+ // TODO: Should we bother signaling unblock? Currently
+ // it isn't allowed, but it might happen when a participant leaves
+ // the room and the item is retracted.
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+ SignalMediaBlockResult(task_id, blockee_nick);
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+ SignalMediaBlockError(task_id, blockee_nick, stanza);
+}
+
} // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h
index e740738..ae9dc57 100644
--- a/talk/xmpp/hangoutpubsubclient.h
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -47,14 +47,26 @@
class XmlElement;
class XmppTaskParentInterface;
+// To handle retracts correctly, we need to remember certain details
+// about an item. We could just cache the entire XML element, but
+// that would take more memory and require re-parsing.
+struct StateItemInfo {
+ std::string published_nick;
+ std::string publisher_nick;
+};
+
// Represents a PubSub state change. Usually, the key is the nick,
-// but not always. It's a per-state-type thing.
+// but not always. It's a per-state-type thing. Currently documented
+// at https://docs.google.com/a/google.com/document/d/
+// 1QyHu_ufyVdf0VICdfc_DtJbrOdrdIUm4eM73RZqnivI/edit?hl=en_US
template <typename C>
struct PubSubStateChange {
- std::string key;
+ // The nick of the user changing the state.
+ std::string publisher_nick;
+ // The nick of the user whose state is changing.
+ std::string published_nick;
C old_state;
C new_state;
- std::string publisher_nick;
};
template <typename C> class PubSubStateClient;
@@ -80,10 +92,12 @@
sigslot::signal3<const std::string&, bool, bool> SignalAudioMuteStateChange;
// Signal (nick, was_recording, is_recording)
sigslot::signal3<const std::string&, bool, bool> SignalRecordingStateChange;
- // Signal (muter_nick, mutee_nick, should_mute_locally)
+ // Signal (mutee_nick, muter_nick, should_mute_locally)
sigslot::signal3<const std::string&,
const std::string&,
bool> SignalRemoteMute;
+ // Signal (blockee_nick, blocker_nick)
+ sigslot::signal2<const std::string&, const std::string&> SignalMediaBlock;
// Signal (node, error stanza)
sigslot::signal2<const std::string&, const XmlElement*> SignalRequestError;
@@ -98,6 +112,8 @@
bool recording, std::string* task_id_out = NULL);
void RemoteMute(
const std::string& mutee_nick, std::string* task_id_out = NULL);
+ void BlockMedia(
+ const std::string& blockee_nick, std::string* task_id_out = NULL);
// Signal task_id
sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
@@ -106,6 +122,9 @@
// Signal (task_id, mutee_nick)
sigslot::signal2<const std::string&,
const std::string&> SignalRemoteMuteResult;
+ // Signal (task_id, blockee_nick)
+ sigslot::signal2<const std::string&,
+ const std::string&> SignalMediaBlockResult;
// Signal (task_id, error stanza)
sigslot::signal2<const std::string&,
@@ -114,10 +133,17 @@
const XmlElement*> SignalPublishPresenterError;
sigslot::signal2<const std::string&,
const XmlElement*> SignalPublishRecordingError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishMediaBlockError;
// Signal (task_id, mutee_nick, error stanza)
sigslot::signal3<const std::string&,
const std::string&,
const XmlElement*> SignalRemoteMuteError;
+ // Signal (task_id, blockee_nick, error stanza)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ const XmlElement*> SignalMediaBlockError;
+
private:
void OnPresenterRequestError(PubSubClient* client,
@@ -143,6 +169,12 @@
void OnRecordingPublishError(const std::string& task_id,
const XmlElement* item,
const XmlElement* stanza);
+ void OnMediaBlockStateChange(const PubSubStateChange<bool>& change);
+ void OnMediaBlockPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnMediaBlockPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
Jid mucjid_;
std::string nick_;
talk_base::scoped_ptr<PubSubClient> media_client_;
@@ -150,6 +182,7 @@
talk_base::scoped_ptr<PubSubStateClient<bool> > presenter_state_client_;
talk_base::scoped_ptr<PubSubStateClient<bool> > audio_mute_state_client_;
talk_base::scoped_ptr<PubSubStateClient<bool> > recording_state_client_;
+ talk_base::scoped_ptr<PubSubStateClient<bool> > media_block_state_client_;
};
} // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc
new file mode 100644
index 0000000..a0c2439
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient_unittest.cc
@@ -0,0 +1,625 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
+
+class TestHangoutPubSubListener : public sigslot::has_slots<> {
+ public:
+ TestHangoutPubSubListener() :
+ request_error_count(0),
+ publish_audio_mute_error_count(0),
+ publish_presenter_error_count(0),
+ publish_recording_error_count(0),
+ remote_mute_error_count(0) {
+ }
+
+ void OnPresenterStateChange(
+ const std::string& nick, bool was_presenting, bool is_presenting) {
+ last_presenter_nick = nick;
+ last_was_presenting = was_presenting;
+ last_is_presenting = is_presenting;
+ }
+
+ void OnAudioMuteStateChange(
+ const std::string& nick, bool was_muted, bool is_muted) {
+ last_audio_muted_nick = nick;
+ last_was_audio_muted = was_muted;
+ last_is_audio_muted = is_muted;
+ }
+
+ void OnRecordingStateChange(
+ const std::string& nick, bool was_recording, bool is_recording) {
+ last_recording_nick = nick;
+ last_was_recording = was_recording;
+ last_is_recording = is_recording;
+ }
+
+ void OnRemoteMute(
+ const std::string& mutee_nick,
+ const std::string& muter_nick,
+ bool should_mute_locally) {
+ last_mutee_nick = mutee_nick;
+ last_muter_nick = muter_nick;
+ last_should_mute = should_mute_locally;
+ }
+
+ void OnMediaBlock(
+ const std::string& blockee_nick,
+ const std::string& blocker_nick) {
+ last_blockee_nick = blockee_nick;
+ last_blocker_nick = blocker_nick;
+ }
+
+ void OnRequestError(const std::string& node, const buzz::XmlElement* stanza) {
+ ++request_error_count;
+ request_error_node = node;
+ }
+
+ void OnPublishAudioMuteError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_audio_mute_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishPresenterError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_presenter_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishRecordingError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_recording_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnRemoteMuteResult(const std::string& task_id,
+ const std::string& mutee_nick) {
+ result_task_id = task_id;
+ remote_mute_mutee_nick = mutee_nick;
+ }
+
+ void OnRemoteMuteError(const std::string& task_id,
+ const std::string& mutee_nick,
+ const buzz::XmlElement* stanza) {
+ ++remote_mute_error_count;
+ error_task_id = task_id;
+ remote_mute_mutee_nick = mutee_nick;
+ }
+
+ void OnMediaBlockResult(const std::string& task_id,
+ const std::string& blockee_nick) {
+ result_task_id = task_id;
+ media_blockee_nick = blockee_nick;
+ }
+
+ void OnMediaBlockError(const std::string& task_id,
+ const std::string& blockee_nick,
+ const buzz::XmlElement* stanza) {
+ ++media_block_error_count;
+ error_task_id = task_id;
+ media_blockee_nick = blockee_nick;
+ }
+
+ std::string last_presenter_nick;
+ bool last_is_presenting;
+ bool last_was_presenting;
+ std::string last_audio_muted_nick;
+ bool last_is_audio_muted;
+ bool last_was_audio_muted;
+ std::string last_recording_nick;
+ bool last_is_recording;
+ bool last_was_recording;
+ std::string last_mutee_nick;
+ std::string last_muter_nick;
+ bool last_should_mute;
+ std::string last_blockee_nick;
+ std::string last_blocker_nick;
+
+ int request_error_count;
+ std::string request_error_node;
+ int publish_audio_mute_error_count;
+ int publish_presenter_error_count;
+ int publish_recording_error_count;
+ int remote_mute_error_count;
+ std::string result_task_id;
+ std::string error_task_id;
+ std::string remote_mute_mutee_nick;
+ int media_block_error_count;
+ std::string media_blockee_nick;
+};
+
+class HangoutPubSubClientTest : public testing::Test {
+ public:
+ HangoutPubSubClientTest() :
+ pubsubjid("room@domain.com"),
+ nick("me") {
+
+ runner.reset(new talk_base::FakeTaskRunner());
+ xmpp_client = new buzz::FakeXmppClient(runner.get());
+ client.reset(new buzz::HangoutPubSubClient(xmpp_client, pubsubjid, nick));
+ listener.reset(new TestHangoutPubSubListener());
+ client->SignalPresenterStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPresenterStateChange);
+ client->SignalAudioMuteStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnAudioMuteStateChange);
+ client->SignalRecordingStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRecordingStateChange);
+ client->SignalRemoteMute.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMute);
+ client->SignalMediaBlock.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlock);
+ client->SignalRequestError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRequestError);
+ client->SignalPublishAudioMuteError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishAudioMuteError);
+ client->SignalPublishPresenterError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishPresenterError);
+ client->SignalPublishRecordingError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishRecordingError);
+ client->SignalRemoteMuteResult.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMuteResult);
+ client->SignalRemoteMuteError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMuteError);
+ client->SignalMediaBlockResult.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlockResult);
+ client->SignalMediaBlockError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlockError);
+ }
+
+ talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+ // xmpp_client deleted by deleting runner.
+ buzz::FakeXmppClient* xmpp_client;
+ talk_base::scoped_ptr<buzz::HangoutPubSubClient> client;
+ talk_base::scoped_ptr<TestHangoutPubSubListener> listener;
+ buzz::Jid pubsubjid;
+ std::string nick;
+};
+
+TEST_F(HangoutPubSubClientTest, TestRequest) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ client->RequestAll();
+ std::string expected_presenter_request =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"google:presenter\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ std::string expected_media_request =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"google:muc#media\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_request, xmpp_client->sent_stanzas()[0]->Str());
+ EXPECT_EQ(expected_media_request, xmpp_client->sent_stanzas()[1]->Str());
+
+ std::string presenter_response =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='google:presenter'>"
+ " <item id='12345'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='o'/>"
+ " </item>"
+ // Some clients are "bad" in that they'll jam multiple states in
+ // all at once. We have to deal with it.
+ " <item id='12346'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " <item id='12347'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_FALSE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ std::string media_response =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick'>"
+ " <audio-mute nick='muted-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='recording:recording-nick'>"
+ " <recording nick='recording-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(media_response));
+ EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+ EXPECT_FALSE(listener->last_was_audio_muted);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+ EXPECT_EQ("recording-nick", listener->last_recording_nick);
+ EXPECT_FALSE(listener->last_was_recording);
+ EXPECT_TRUE(listener->last_is_recording);
+
+ std::string incoming_presenter_resets_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <item id='12348'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='o'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_resets_message));
+ EXPECT_EQ("presenting-nick", listener->last_presenter_nick);
+ //EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_FALSE(listener->last_is_presenting);
+
+ std::string incoming_presenter_retracts_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <retract id='12347'/>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_retracts_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_FALSE(listener->last_is_presenting);
+
+ std::string incoming_media_retracts_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick'>"
+ " </item>"
+ " <retract id='recording:recording-nick'/>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_retracts_message));
+ EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+ EXPECT_TRUE(listener->last_was_audio_muted);
+ EXPECT_FALSE(listener->last_is_audio_muted);
+ EXPECT_EQ("recording-nick", listener->last_recording_nick);
+ EXPECT_TRUE(listener->last_was_recording);
+ EXPECT_FALSE(listener->last_is_recording);
+
+ std::string incoming_presenter_changes_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <item id='presenting-nick2'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_FALSE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ std::string incoming_media_changes_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick2'>"
+ " <audio-mute nick='muted-nick2' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='recording:recording-nick2'>"
+ " <recording nick='recording-nick2' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_changes_message));
+ EXPECT_EQ("muted-nick2", listener->last_audio_muted_nick);
+ EXPECT_FALSE(listener->last_was_audio_muted);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+ EXPECT_EQ("recording-nick2", listener->last_recording_nick);
+ EXPECT_FALSE(listener->last_was_recording);
+ EXPECT_TRUE(listener->last_is_recording);
+
+ std::string incoming_remote_mute_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:mutee' publisher='room@domain.com/muter'>"
+ " <audio-mute nick='mutee' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_remote_mute_message));
+ EXPECT_EQ("mutee", listener->last_mutee_nick);
+ EXPECT_EQ("muter", listener->last_muter_nick);
+ EXPECT_FALSE(listener->last_should_mute);
+
+ std::string incoming_remote_mute_me_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:me' publisher='room@domain.com/muter'>"
+ " <audio-mute nick='me' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_remote_mute_me_message));
+ EXPECT_EQ("me", listener->last_mutee_nick);
+ EXPECT_EQ("muter", listener->last_muter_nick);
+ EXPECT_TRUE(listener->last_should_mute);
+
+ std::string incoming_media_block_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='block:blocker:blockee'"
+ " publisher='room@domain.com/blocker'>"
+ " <block nick='blockee' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_block_message));
+ EXPECT_EQ("blockee", listener->last_blockee_nick);
+ EXPECT_EQ("blocker", listener->last_blocker_nick);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRequestError) {
+ client->RequestAll();
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->request_error_count);
+ EXPECT_EQ("google:presenter", listener->request_error_node);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublish) {
+ client->PublishPresenterState(true);
+ std::string expected_presenter_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:presenter\">"
+ "<item id=\"me\">"
+ "<presenter xmlns=\"google:presenter\""
+ " jid=\"dummy@value.net\" nick=\"me\"/>"
+ "<pre:presentation-item"
+ " pre:presentation-type=\"s\" xmlns:pre=\"google:presenter\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_iq,
+ xmpp_client->sent_stanzas()[0]->Str());
+
+ client->PublishAudioMuteState(true);
+ std::string expected_audio_mute_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"audio-mute:me\">"
+ "<audio-mute xmlns=\"google:muc#media\" nick=\"me\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_audio_mute_iq, xmpp_client->sent_stanzas()[1]->Str());
+
+ client->PublishRecordingState(true);
+ std::string expected_recording_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"recording:me\">"
+ "<recording xmlns=\"google:muc#media\" nick=\"me\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(3U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[2]->Str());
+
+ client->RemoteMute("mutee");
+ std::string expected_remote_mute_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"audio-mute:mutee\">"
+ "<audio-mute xmlns=\"google:muc#media\" nick=\"mutee\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(4U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[3]->Str());
+
+ client->PublishPresenterState(false);
+ std::string expected_presenter_retract_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:presenter\">"
+ "<item id=\"me\">"
+ "<presenter xmlns=\"google:presenter\""
+ " jid=\"dummy@value.net\" nick=\"me\"/>"
+ "<pre:presentation-item"
+ " pre:presentation-type=\"o\" xmlns:pre=\"google:presenter\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(5U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_retract_iq,
+ xmpp_client->sent_stanzas()[4]->Str());
+
+ client->PublishAudioMuteState(false);
+ std::string expected_audio_mute_retract_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"google:muc#media\" notify=\"true\">"
+ "<item id=\"audio-mute:me\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(6U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_audio_mute_retract_iq,
+ xmpp_client->sent_stanzas()[5]->Str());
+
+ client->BlockMedia("blockee");
+ std::string expected_media_block_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"block:me:blockee\">"
+ "<block xmlns=\"google:muc#media\" nick=\"blockee\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(7U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[6]->Str());
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishPresenterError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishPresenterState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_presenter_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+
+TEST_F(HangoutPubSubClientTest, TestPublishAudioMuteError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishAudioMuteState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_audio_mute_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRecordingError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishRecordingState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_recording_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRemoteMuteResult) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->RemoteMute("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+ EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRemoteMuteError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->RemoteMute("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->remote_mute_error_count);
+ EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishMediaBlockResult) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->BlockMedia("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ("joe", listener->media_blockee_nick);
+ EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestMediaBlockError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->BlockMedia("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->remote_mute_error_count);
+ EXPECT_EQ("joe", listener->media_blockee_nick);
+ EXPECT_EQ("0", listener->error_task_id);
+}
diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc
index 01a025f..ae01e5d 100644
--- a/talk/xmpp/jid.cc
+++ b/talk/xmpp/jid.cc
@@ -2,26 +2,26 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@@ -38,106 +38,59 @@
namespace buzz {
-Jid::Jid() : data_(NULL) {
+Jid::Jid() {
}
-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;
+Jid::Jid(const std::string& jid_string) {
+ if (jid_string.empty())
return;
- }
- // First find the slash and slice of that part
+ // First find the slash and slice off that part
size_t slash = jid_string.find('/');
- std::string resource_name = (slash == std::string::npos ? STR_EMPTY :
+ 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);
+ 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);
+ size_t domain_length = (slash == std::string::npos) ?
+ (jid_string.length() - domain_begin) : (slash - domain_begin);
+ domain_name_ = jid_string.substr(domain_begin, domain_length);
- // 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);
+ ValidateOrReset();
}
-Jid::Jid(const std::string & node_name,
- const std::string & domain_name,
- const std::string & resource_name) {
- if (domain_name.empty()) {
- data_ = NULL;
- return;
- }
+Jid::Jid(const std::string& node_name,
+ const std::string& domain_name,
+ const std::string& resource_name)
+ : node_name_(node_name),
+ domain_name_(domain_name),
+ resource_name_(resource_name) {
+ ValidateOrReset();
+}
+void Jid::ValidateOrReset() {
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);
+
+ node_name_ = PrepNode(node_name_, &valid_node);
+ domain_name_ = PrepDomain(domain_name_, &valid_domain);
+ resource_name_ = PrepResource(resource_name_, &valid_resource);
if (!valid_node || !valid_domain || !valid_resource) {
- data_ = NULL;
- return;
+ node_name_.clear();
+ domain_name_.clear();
+ resource_name_.clear();
}
-
- data_ = new Data(validated_node, validated_domain, validated_resource);
}
std::string Jid::Str() const {
@@ -146,143 +99,85 @@
std::string ret;
- if (!data_->node_name_.empty())
- ret = data_->node_name_ + "@";
+ if (!node_name_.empty())
+ ret = node_name_ + "@";
- ASSERT(data_->domain_name_ != STR_EMPTY);
- ret += data_->domain_name_;
+ ASSERT(domain_name_ != STR_EMPTY);
+ ret += domain_name_;
- if (!data_->resource_name_.empty())
- ret += "/" + data_->resource_name_;
+ if (!resource_name_.empty())
+ ret += "/" + resource_name_;
return ret;
}
-bool
-Jid::IsValid() const {
- return data_ != NULL && !data_->domain_name_.empty();
+Jid::~Jid() {
}
-bool
-Jid::IsBare() const {
- if (Compare(JID_EMPTY) == 0) {
- LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid";
+bool Jid::IsEmpty() const {
+ return (node_name_.empty() && domain_name_.empty() &&
+ resource_name_.empty());
+}
+
+bool Jid::IsValid() const {
+ return !domain_name_.empty();
+}
+
+bool Jid::IsBare() const {
+ if (IsEmpty()) {
+ LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid.";
return true;
}
- return IsValid() &&
- data_->resource_name_.empty();
+ return IsValid() && resource_name_.empty();
}
-bool
-Jid::IsFull() const {
- return IsValid() &&
- !data_->resource_name_.empty();
+bool Jid::IsFull() const {
+ return IsValid() && !resource_name_.empty();
}
-Jid
-Jid::BareJid() const {
+Jid Jid::BareJid() const {
if (!IsValid())
return Jid();
if (!IsFull())
return *this;
- return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY);
+ return Jid(node_name_, 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::BareEquals(const Jid& other) const {
+ return other.node_name_ == node_name_ &&
+ other.domain_name_ == 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_));
+bool Jid::operator==(const Jid& other) const {
+ return other.node_name_ == node_name_ &&
+ other.domain_name_ == domain_name_ &&
+ other.resource_name_ == 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 Jid::Compare(const Jid& other) const {
int compare_result;
- compare_result = data_->node_name_.compare(other.data_->node_name_);
+ compare_result = node_name_.compare(other.node_name_);
if (0 != compare_result)
return compare_result;
- compare_result = data_->domain_name_.compare(other.data_->domain_name_);
+ compare_result = domain_name_.compare(other.domain_name_);
if (0 != compare_result)
return compare_result;
- compare_result = data_->resource_name_.compare(other.data_->resource_name_);
+ compare_result = resource_name_.compare(other.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) {
+std::string Jid::PrepNode(const std::string& node, bool* valid) {
*valid = false;
std::string result;
- for (std::string::const_iterator i = start; i < end; i++) {
+ for (std::string::const_iterator i = node.begin(); i < node.end(); ++i) {
bool char_valid = true;
unsigned char ch = *i;
if (ch <= 0x7F) {
- result += prepNodeAscii(ch, &char_valid);
+ result += PrepNodeAscii(ch, &char_valid);
}
else {
// TODO: implement the correct stringprep protocol for these
@@ -302,8 +197,7 @@
// Returns the appropriate mapping for an ASCII character in a node.
-char
-Jid::prepNodeAscii(char ch, bool *valid) {
+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':
@@ -329,17 +223,16 @@
// 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) {
+std::string Jid::PrepResource(const std::string& resource, bool* valid) {
*valid = false;
std::string result;
- for (std::string::const_iterator i = start; i < end; i++) {
+ for (std::string::const_iterator i = resource.begin();
+ i < resource.end(); ++i) {
bool char_valid = true;
unsigned char ch = *i;
if (ch <= 0x7F) {
- result += prepResourceAscii(ch, &char_valid);
+ result += PrepResourceAscii(ch, &char_valid);
}
else {
// TODO: implement the correct stringprep protocol for these
@@ -355,8 +248,7 @@
}
// Returns the appropriate mapping for an ASCII character in a resource.
-char
-Jid::prepResourceAscii(char ch, bool *valid) {
+char Jid::PrepResourceAscii(char ch, bool* valid) {
*valid = true;
switch (ch) {
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
@@ -373,15 +265,13 @@
}
// 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) {
+std::string Jid::PrepDomain(const std::string& domain, 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);
+ PrepDomain(domain, &result, valid);
if (!*valid) {
return STR_EMPTY;
}
@@ -395,12 +285,10 @@
// 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) {
+void Jid::PrepDomain(const std::string& domain, std::string* buf, bool* valid) {
*valid = false;
- std::string::const_iterator last = start;
- for (std::string::const_iterator i = start; i < end; i++) {
+ std::string::const_iterator last = domain.begin();
+ for (std::string::const_iterator i = domain.begin(); i < domain.end(); ++i) {
bool label_valid = true;
char ch = *i;
switch (ch) {
@@ -410,7 +298,7 @@
case 0xFF0E:
case 0xFF61:
#endif
- prepDomainLabel(str, last, i, buf, &label_valid);
+ PrepDomainLabel(last, i, buf, &label_valid);
*buf += '.';
last = i + 1;
break;
@@ -419,21 +307,21 @@
return;
}
}
- prepDomainLabel(str, last, end, buf, valid);
+ PrepDomainLabel(last, domain.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) {
+void Jid::PrepDomainLabel(
+ 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++) {
+ int start_len = 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);
+ *buf += PrepDomainLabelAscii(ch, &char_valid);
}
else {
// TODO: implement ToASCII for these
@@ -444,7 +332,7 @@
}
}
- int count = buf->length() - startLen;
+ int count = buf->length() - start_len;
if (count == 0) {
return;
}
@@ -452,8 +340,8 @@
return;
}
- // Is this check needed? See comment in prepDomainLabelAscii.
- if ((*buf)[startLen] == '-') {
+ // Is this check needed? See comment in PrepDomainLabelAscii.
+ if ((*buf)[start_len] == '-') {
return;
}
if ((*buf)[buf->length() - 1] == '-') {
@@ -464,8 +352,7 @@
// Returns the appropriate mapping for an ASCII character in a domain label.
-char
-Jid::prepDomainLabelAscii(char ch, bool *valid) {
+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
@@ -500,4 +387,4 @@
}
}
-}
+} // namespace buzz
diff --git a/talk/xmpp/jid.h b/talk/xmpp/jid.h
index 6831bda..9daadb0 100644
--- a/talk/xmpp/jid.h
+++ b/talk/xmpp/jid.h
@@ -24,8 +24,9 @@
* 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_
+
+#ifndef TALK_XMPP_JID_H_
+#define TALK_XMPP_JID_H_
#include <string>
#include "talk/base/basictypes.h"
@@ -33,116 +34,65 @@
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.
+// The Jid class encapsulates and provides parsing help for Jids. A Jid
+// consists of three parts: the node, the domain and the resource, e.g.:
+//
+// 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();
- }
- }
-
+ explicit Jid(const std::string& jid_string);
+ explicit Jid(const std::string& node_name,
+ const std::string& domain_name,
+ const std::string& resource_name);
+ ~Jid();
- 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);
+ const std::string & node() const { return node_name_; }
+ const std::string & domain() const { return domain_name_; }
+ const std::string & resource() const { return resource_name_; }
std::string Str() const;
Jid BareJid() const;
+ bool IsEmpty() const;
bool IsValid() const;
bool IsBare() const;
bool IsFull() const;
- bool BareEquals(const Jid & other) 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;
+ 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; };
-
+ 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:
+ void ValidateOrReset();
- 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);
+ static std::string PrepNode(const std::string& node, bool* valid);
+ static char PrepNodeAscii(char ch, bool* valid);
+ static std::string PrepResource(const std::string& start, bool* valid);
+ static char PrepResourceAscii(char ch, bool* valid);
+ static std::string PrepDomain(const std::string& domain, bool* valid);
+ static void PrepDomain(const std::string& domain,
+ std::string* buf, bool* valid);
+ static void PrepDomainLabel(
+ 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_;
+ std::string node_name_;
+ std::string domain_name_;
+ std::string resource_name_;
};
}
-
-
-#endif
+#endif // TALK_XMPP_JID_H_
diff --git a/talk/xmpp/jid_unittest.cc b/talk/xmpp/jid_unittest.cc
new file mode 100644
index 0000000..b9597da
--- /dev/null
+++ b/talk/xmpp/jid_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include "talk/base/gunit.h"
+#include "talk/xmpp/jid.h"
+
+using buzz::Jid;
+
+TEST(JidTest, TestDomain) {
+ Jid jid("dude");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("dude", jid.Str());
+ EXPECT_EQ("dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomain) {
+ Jid jid("walter@dude");
+ EXPECT_EQ("walter", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("walter@dude", jid.Str());
+ EXPECT_EQ("walter@dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestDomainResource) {
+ Jid jid("dude/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("bowlingalley", jid.resource());
+ EXPECT_EQ("dude/bowlingalley", jid.Str());
+ EXPECT_EQ("dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomainResource) {
+ Jid jid("walter@dude/bowlingalley");
+ EXPECT_EQ("walter", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("bowlingalley", jid.resource());
+ EXPECT_EQ("walter@dude/bowlingalley", jid.Str());
+ EXPECT_EQ("walter@dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNode) {
+ Jid jid("walter@");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestResource) {
+ Jid jid("/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeResource) {
+ Jid jid("walter@/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky) {
+ Jid jid("bowling@muchat/walter@dude");
+ EXPECT_EQ("bowling", jid.node());
+ EXPECT_EQ("muchat", jid.domain());
+ EXPECT_EQ("walter@dude", jid.resource());
+ EXPECT_EQ("bowling@muchat/walter@dude", jid.Str());
+ EXPECT_EQ("bowling@muchat", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky2) {
+ Jid jid("muchat/walter@dude");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("muchat", jid.domain());
+ EXPECT_EQ("walter@dude", jid.resource());
+ EXPECT_EQ("muchat/walter@dude", jid.Str());
+ EXPECT_EQ("muchat", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
diff --git a/talk/xmpp/module.h b/talk/xmpp/module.h
new file mode 100644
index 0000000..75a190d
--- /dev/null
+++ b/talk/xmpp/module.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 _module_h_
+#define _module_h_
+
+namespace buzz {
+
+class XmppEngine;
+enum XmppReturnStatus;
+
+//! This is the base class for extension modules.
+//! An engine is registered with the module and the module then hooks the
+//! appropriate parts of the engine to implement that set of features. It is
+//! important to unregister modules before destructing the engine.
+class XmppModule {
+public:
+ virtual ~XmppModule() {}
+
+ //! Register the engine with the module. Only one engine can be associated
+ //! with a module at a time. This method will return an error if there is
+ //! already an engine registered.
+ virtual XmppReturnStatus RegisterEngine(XmppEngine* engine) = 0;
+};
+
+}
+#endif
diff --git a/talk/xmpp/moduleimpl.cc b/talk/xmpp/moduleimpl.cc
new file mode 100644
index 0000000..b23ca29
--- /dev/null
+++ b/talk/xmpp/moduleimpl.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 "talk/base/common.h"
+#include "talk/xmpp/moduleimpl.h"
+
+namespace buzz {
+
+XmppModuleImpl::XmppModuleImpl() :
+ engine_(NULL),
+ stanza_handler_(this) {
+}
+
+XmppModuleImpl::~XmppModuleImpl()
+{
+ if (engine_ != NULL) {
+ engine_->RemoveStanzaHandler(&stanza_handler_);
+ engine_ = NULL;
+ }
+}
+
+XmppReturnStatus
+XmppModuleImpl::RegisterEngine(XmppEngine* engine)
+{
+ if (NULL == engine || NULL != engine_)
+ return XMPP_RETURN_BADARGUMENT;
+
+ engine->AddStanzaHandler(&stanza_handler_);
+ engine_ = engine;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppEngine*
+XmppModuleImpl::engine() {
+ ASSERT(NULL != engine_);
+ return engine_;
+}
+
+}
+
diff --git a/talk/xmpp/moduleimpl.h b/talk/xmpp/moduleimpl.h
new file mode 100644
index 0000000..085c83a
--- /dev/null
+++ b/talk/xmpp/moduleimpl.h
@@ -0,0 +1,93 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _moduleimpl_h_
+#define _moduleimpl_h_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/module.h"
+
+namespace buzz {
+
+//! This is the base implementation class for extension modules.
+//! An engine is registered with the module and the module then hooks the
+//! appropriate parts of the engine to implement that set of features. It is
+//! important to unregister modules before destructing the engine.
+class XmppModuleImpl {
+protected:
+ XmppModuleImpl();
+ virtual ~XmppModuleImpl();
+
+ //! Register the engine with the module. Only one engine can be associated
+ //! with a module at a time. This method will return an error if there is
+ //! already an engine registered.
+ XmppReturnStatus RegisterEngine(XmppEngine* engine);
+
+ //! Gets the engine that this module is attached to.
+ XmppEngine* engine();
+
+ //! Process the given stanza.
+ //! The module 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 *) { return false; };
+
+private:
+
+ //! The ModuleSessionHelper nested class allows the Module
+ //! to hook into and get stanzas and events from the engine.
+ class ModuleStanzaHandler : public XmppStanzaHandler {
+ friend class XmppModuleImpl;
+
+ ModuleStanzaHandler(XmppModuleImpl* module) :
+ module_(module) {
+ }
+
+ bool HandleStanza(const XmlElement* stanza) {
+ return module_->HandleStanza(stanza);
+ }
+
+ XmppModuleImpl* module_;
+ };
+
+ friend class ModuleStanzaHandler;
+
+ XmppEngine* engine_;
+ ModuleStanzaHandler stanza_handler_;
+};
+
+
+// This macro will implement the XmppModule interface for a class
+// that derives from both XmppModuleImpl and XmppModule
+#define IMPLEMENT_XMPPMODULE \
+ XmppReturnStatus RegisterEngine(XmppEngine* engine) { \
+ return XmppModuleImpl::RegisterEngine(engine); \
+ }
+
+}
+
+#endif
diff --git a/talk/xmpp/mucroomconfigtask_unittest.cc b/talk/xmpp/mucroomconfigtask_unittest.cc
new file mode 100644
index 0000000..e0a8aca
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask_unittest.cc
@@ -0,0 +1,144 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/mucroomconfigtask.h"
+
+class MucRoomConfigListener : public sigslot::has_slots<> {
+ public:
+ MucRoomConfigListener() : result_count(0), error_count(0) {}
+
+ void OnResult(buzz::MucRoomConfigTask*) {
+ ++result_count;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ int result_count;
+ int error_count;
+};
+
+class MucRoomConfigTaskTest : public testing::Test {
+ public:
+ MucRoomConfigTaskTest() :
+ room_jid("muc-jid-ponies@domain.com"),
+ room_name("ponies") {
+ }
+
+ virtual void SetUp() {
+ runner = new talk_base::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomConfigListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ talk_base::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomConfigListener* listener;
+ buzz::Jid room_jid;
+ std::string room_name;
+};
+
+TEST_F(MucRoomConfigTaskTest, TestConfigEnterprise) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ std::vector<std::string> room_features;
+ room_features.push_back("feature1");
+ room_features.push_back("feature2");
+ buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+ xmpp_client, room_jid, "ponies", room_features);
+ EXPECT_EQ(room_jid, task->room_jid());
+
+ task->SignalResult.connect(listener, &MucRoomConfigListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"muc-jid-ponies@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"http://jabber.org/protocol/muc#owner\">"
+ "<x xmlns=\"jabber:x:data\" type=\"form\">"
+ "<field var=\"muc#roomconfig_roomname\" type=\"text-single\">"
+ "<value>ponies</value>"
+ "</field>"
+ "<field var=\"muc#roomconfig_features\" type=\"list-multi\">"
+ "<value>feature1</value>"
+ "<value>feature2</value>"
+ "</field>"
+ "</x>"
+ "</query>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' id='0' type='result'"
+ " from='muc-jid-ponies@domain.com'>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomConfigTaskTest, TestError) {
+ std::vector<std::string> room_features;
+ buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+ xmpp_client, room_jid, "ponies", room_features);
+ task->SignalError.connect(listener, &MucRoomConfigListener::OnError);
+ task->Start();
+
+ std::string error_iq =
+ "<iq xmlns='jabber:client' id='0' type='error'"
+ " from='muc-jid-ponies@domain.com'>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
diff --git a/talk/xmpp/mucroomhistorytask.cc b/talk/xmpp/mucroomhistorytask.cc
new file mode 100644
index 0000000..08c907a
--- /dev/null
+++ b/talk/xmpp/mucroomhistorytask.cc
@@ -0,0 +1,146 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/mucroomhistorytask.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+// TODO: Move these to xmpp/constants.cc once it's publicly
+// viewable.
+const char NS_GOOGLE_SETTING[] = "google:setting";
+const StaticQName QN_MEETING_HISTORY = { NS_GOOGLE_SETTING, "meetinghistory" };
+const StaticQName QN_MEETING_ITEM = { NS_GOOGLE_SETTING, "item" };
+
+MucRoomHistoryGetTask::MucRoomHistoryGetTask(XmppTaskParentInterface* parent,
+ const buzz::Jid& user_jid)
+ : IqTask(parent, STR_GET, user_jid, MakeRequest()) {
+}
+
+// <iq type='get' id='user-setting-1'>
+// <meetinghistory xmlns='google:setting' />
+// </iq>
+XmlElement* MucRoomHistoryGetTask::MakeRequest() {
+ XmlElement* history = new XmlElement(QN_MEETING_HISTORY, true);
+ return history;
+}
+
+// <iq type='result'
+// to='romeo@gmail.com/orchard'
+// id='user-setting-1'>
+// <meetinghistory xmlns='google:setting'>
+// <item jid=’private-muc-abc-...@groupchat.google.com’ name=’Hangout'
+// time=’2011-01-31T13:20:00Z’ />
+// <item jid=’private-muc-abc-...@groupchat.google.com’ name=’Planning’
+// time=’2011-02-11T 14:20:00Z />
+// <item jid=’private-muc-abc-...@groupchat.google.com’ name=’TGIF’
+// time=’2011-03-21T15:20:00Z’ />
+// </meetinghistory>
+// </iq>
+void MucRoomHistoryGetTask::HandleResult(const XmlElement* stanza) {
+ std::vector<MucRoomHistoryInfo> meeting_rooms;
+
+ const XmlElement* history = stanza->FirstNamed(QN_MEETING_HISTORY);
+ if (history == NULL) {
+ SignalResult(meeting_rooms);
+ return;
+ }
+
+ const XmlElement* history_item = history->FirstNamed(QN_MEETING_ITEM);
+ while (history_item != NULL) {
+ MucRoomHistoryInfo room;
+ room.room_name = history_item->Attr(QN_NAME);
+ room.last_enter_time = history_item->Attr(QN_TIME);
+ meeting_rooms.push_back(room);
+
+ history_item = history_item->NextNamed(QN_MEETING_ITEM);
+ }
+
+ SignalResult(meeting_rooms);
+}
+
+MucRoomHistorySetTask::MucRoomHistorySetTask(XmppTaskParentInterface* parent,
+ MucRoomHistoryTaskCommand command,
+ const buzz::Jid& user_jid,
+ const std::string& room_name)
+ : IqTask(parent, STR_SET, user_jid, MakeRequest(command, room_name)) {
+}
+
+// ADD
+// <iq type='set'
+// to='romeo@gmail.com'
+// id='user-setting-3'>
+// <meetinghistory xmlns='google:setting'>
+// <item jid=’private-muc-abc@groupchat.google.com’
+// name=’Hangout' action=’add’ />
+// </meetinghistory>
+// </iq>
+
+// DELETE
+// <iq type='set'
+// to='romeo@gmail.com'
+// id='user-setting-3'>
+// <meetinghistory xmlns='google:setting'>
+// <item jid=’private-muc-abc@groupchat.google.com’
+// name=’Hangout' action=’remove’ />
+// </meetinghistory>
+// </iq>
+
+// CLEAR ALL
+// <iq type='set'
+// from='romeo@gmail.com/orchard'
+// to='romeo@gmail.com'
+// id='user-setting-3'>
+// <meetinghistory xmlns='google:setting' />
+// </iq>
+XmlElement* MucRoomHistorySetTask::MakeRequest(
+ MucRoomHistoryTaskCommand command, const std::string& room_name) {
+ XmlElement* history = new XmlElement(QN_MEETING_HISTORY, true);
+ if (command != HT_CLEAR_ALL) {
+ XmlElement* item = new XmlElement(QN_MEETING_ITEM, false);
+ // TODO - remove QN_JID attribute when 21446107 is submitted &
+ // deployed. no harm to leave in after the change is made.
+ item->AddAttr(QN_JID, room_name);
+ item->AddAttr(QN_NAME, room_name);
+ item->AddAttr(QN_ACTION, command == HT_ADD ? "add" : "remove");
+
+ history->AddElement(item);
+ }
+
+ return history;
+}
+
+// MODIFY & CLEAR
+// <iq type='result'
+// to='romeo@gmail.com/orchard'
+// from='romeo@gmail.com'
+// id='user-setting-3' />
+void MucRoomHistorySetTask::HandleResult(const XmlElement* stanza) {
+ SignalResult();
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/mucroomhistorytask.h b/talk/xmpp/mucroomhistorytask.h
new file mode 100644
index 0000000..79d1bfa
--- /dev/null
+++ b/talk/xmpp/mucroomhistorytask.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// https://docs.google.com/a/google.com/document/d/1PObnyJbdEWDOrgDZJFIH1peMvRoUkSTv6382zq3hbuM/edit?hl=en#
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct MucRoomHistoryInfo {
+ std::string room_name;
+ std::string last_enter_time;
+};
+
+enum MucRoomHistoryTaskCommand {
+ HT_ADD = 0,
+ HT_CLEAR_ALL = 1,
+ HT_DELETE = 2,
+};
+
+class MucRoomHistoryGetTask : public IqTask {
+ public:
+ MucRoomHistoryGetTask(XmppTaskParentInterface* parent,
+ const buzz::Jid& user_jid);
+
+ sigslot::signal1<const std::vector<MucRoomHistoryInfo>&> SignalResult;
+
+ protected:
+ static XmlElement* MakeRequest();
+ virtual void HandleResult(const XmlElement* stanza);
+};
+
+class MucRoomHistorySetTask : public IqTask {
+ public:
+ MucRoomHistorySetTask(XmppTaskParentInterface* parent,
+ MucRoomHistoryTaskCommand command,
+ const buzz::Jid& user_jid,
+ const std::string& room_name);
+
+ sigslot::signal0<> SignalResult;
+
+ protected:
+ static XmlElement* MakeRequest(MucRoomHistoryTaskCommand command,
+ const std::string& room_name);
+ virtual void HandleResult(const XmlElement* stanza);
+};
+
+} // namespace buzz
diff --git a/talk/xmpp/mucroomlookuptask_unittest.cc b/talk/xmpp/mucroomlookuptask_unittest.cc
new file mode 100644
index 0000000..99a78c7
--- /dev/null
+++ b/talk/xmpp/mucroomlookuptask_unittest.cc
@@ -0,0 +1,162 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string>
+#include <vector>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/mucroomlookuptask.h"
+
+class MucRoomLookupListener : public sigslot::has_slots<> {
+ public:
+ MucRoomLookupListener() : error_count(0) {}
+
+ void OnResult(buzz::MucRoomLookupTask* task,
+ const buzz::MucRoomInfo& room) {
+ last_room = room;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ buzz::MucRoomInfo last_room;
+ int error_count;
+};
+
+class MucRoomLookupTaskTest : public testing::Test {
+ public:
+ MucRoomLookupTaskTest() :
+ lookup_server_jid("lookup@domain.com"),
+ room_jid("muc-jid-ponies@domain.com"),
+ room_name("ponies"),
+ room_domain("domain.com"),
+ room_full_name("ponies@domain.com") {
+ }
+
+ virtual void SetUp() {
+ runner = new talk_base::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomLookupListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ talk_base::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomLookupListener* listener;
+ buzz::Jid lookup_server_jid;
+ buzz::Jid room_jid;
+ std::string room_name;
+ std::string room_domain;
+ std::string room_full_name;
+};
+
+TEST_F(MucRoomLookupTaskTest, TestLookupName) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"lookup@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:search\">"
+ "<room-name>ponies</room-name>"
+ "<room-domain>domain.com</room-domain>"
+ "</query>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ("", listener->last_room.name);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ " <query xmlns='jabber:iq:search'>"
+ " <item jid='muc-jid-ponies@domain.com'>"
+ " <room-name>ponies</room-name>"
+ " <room-domain>domain.com</room-domain>"
+ " </item>"
+ " </query>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(room_name, listener->last_room.name);
+ EXPECT_EQ(room_domain, listener->last_room.domain);
+ EXPECT_EQ(room_jid, listener->last_room.jid);
+ EXPECT_EQ(room_full_name, listener->last_room.full_name());
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestError) {
+ buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+ task->Start();
+
+ std::string error_iq =
+ "<iq xmlns='jabber:client' id='0' type='error'"
+ " from='lookup@domain.com'>"
+ "</iq>";
+
+ EXPECT_EQ(0, listener->error_count);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestBadJid) {
+ buzz::MucRoomLookupTask* task = new buzz::MucRoomLookupTask(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+ task->Start();
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ " <query xmlns='jabber:iq:search'>"
+ " <item/>"
+ " </query>"
+ "</iq>";
+
+ EXPECT_EQ(0, listener->error_count);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
diff --git a/talk/xmpp/pubsub_task.cc b/talk/xmpp/pubsub_task.cc
new file mode 100644
index 0000000..97e0d4d
--- /dev/null
+++ b/talk/xmpp/pubsub_task.cc
@@ -0,0 +1,217 @@
+/*
+ * 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/pubsub_task.h"
+
+#include <map>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+PubsubTask::PubsubTask(XmppTaskParentInterface* parent,
+ const buzz::Jid& pubsub_node_jid)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_SENDER),
+ pubsub_node_jid_(pubsub_node_jid) {
+}
+
+PubsubTask::~PubsubTask() {
+}
+
+// Checks for pubsub publish events as well as responses to get IQs.
+bool PubsubTask::HandleStanza(const buzz::XmlElement* stanza) {
+ const buzz::QName& stanza_name(stanza->Name());
+ if (stanza_name == buzz::QN_MESSAGE) {
+ if (MatchStanzaFrom(stanza, pubsub_node_jid_)) {
+ const buzz::XmlElement* pubsub_event_item =
+ stanza->FirstNamed(QN_PUBSUB_EVENT);
+ if (pubsub_event_item != NULL) {
+ QueueStanza(pubsub_event_item);
+ return true;
+ }
+ }
+ } else if (stanza_name == buzz::QN_IQ) {
+ if (MatchResponseIq(stanza, pubsub_node_jid_, task_id())) {
+ const buzz::XmlElement* pubsub_item = stanza->FirstNamed(QN_PUBSUB);
+ if (pubsub_item != NULL) {
+ QueueStanza(pubsub_item);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+int PubsubTask::ProcessResponse() {
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+
+ if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
+ OnPubsubError(stanza->FirstNamed(buzz::QN_ERROR));
+ return STATE_RESPONSE;
+ }
+
+ const buzz::QName& stanza_name(stanza->Name());
+ if (stanza_name == QN_PUBSUB_EVENT) {
+ HandlePubsubEventMessage(stanza);
+ } else if (stanza_name == QN_PUBSUB) {
+ HandlePubsubIqGetResponse(stanza);
+ }
+
+ return STATE_RESPONSE;
+}
+
+// Registers a function pointer to be called when the value of the pubsub
+// node changes.
+// Note that this does not actually change the XMPP pubsub
+// subscription. All publish events are always received by everyone in the
+// MUC. This function just controls whether the handle function will get
+// called when the event is received.
+bool PubsubTask::SubscribeToNode(const std::string& pubsub_node,
+ NodeHandler handler) {
+ subscribed_nodes_[pubsub_node] = handler;
+ talk_base::scoped_ptr<buzz::XmlElement> get_iq_request(
+ MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id()));
+ if (get_iq_request.get() == NULL) {
+ return false;
+ }
+ buzz::XmlElement* pubsub_element = new buzz::XmlElement(QN_PUBSUB, true);
+ buzz::XmlElement* items_element = new buzz::XmlElement(QN_PUBSUB_ITEMS, true);
+
+ items_element->AddAttr(buzz::QN_NODE, pubsub_node);
+ pubsub_element->AddElement(items_element);
+ get_iq_request->AddElement(pubsub_element);
+
+ if (SendStanza(get_iq_request.get()) != buzz::XMPP_RETURN_OK) {
+ return false;
+ }
+
+ return true;
+}
+
+void PubsubTask::UnsubscribeFromNode(const std::string& pubsub_node) {
+ subscribed_nodes_.erase(pubsub_node);
+}
+
+void PubsubTask::OnPubsubError(const buzz::XmlElement* error_stanza) {
+}
+
+// Checks for a pubsub event message like the following:
+//
+// <message from="muvc-private-chat-some-id@groupchat.google.com"
+// to="john@site.com/gcomm582B14C9">
+// <event xmlns:"http://jabber.org/protocol/pubsub#event">
+// <items node="node-name">
+// <item id="some-id">
+// <payload/>
+// </item>
+// </items>
+// </event>
+// </message>
+//
+// It also checks for retraction event messages like the following:
+//
+// <message from="muvc-private-chat-some-id@groupchat.google.com"
+// to="john@site.com/gcomm582B14C9">
+// <event xmlns:"http://jabber.org/protocol/pubsub#event">
+// <items node="node-name">
+// <retract id="some-id"/>
+// </items>
+// </event>
+// </message>
+void PubsubTask::HandlePubsubEventMessage(
+ const buzz::XmlElement* pubsub_event) {
+ ASSERT(pubsub_event->Name() == QN_PUBSUB_EVENT);
+ for (const buzz::XmlChild* child = pubsub_event->FirstChild();
+ child != NULL;
+ child = child->NextChild()) {
+ const buzz::XmlElement* child_element = child->AsElement();
+ const buzz::QName& child_name(child_element->Name());
+ if (child_name == QN_PUBSUB_EVENT_ITEMS) {
+ HandlePubsubItems(child_element);
+ }
+ }
+}
+
+// Checks for a response to an pubsub IQ get like the following:
+//
+// <iq from="muvc-private-chat-some-id@groupchat.google.com"
+// to="john@site.com/gcomm582B14C9"
+// type="result">
+// <pubsub xmlns:"http://jabber.org/protocol/pubsub">
+// <items node="node-name">
+// <item id="some-id">
+// <payload/>
+// </item>
+// </items>
+// </event>
+// </message>
+void PubsubTask::HandlePubsubIqGetResponse(
+ const buzz::XmlElement* pubsub_iq_response) {
+ ASSERT(pubsub_iq_response->Name() == QN_PUBSUB);
+ for (const buzz::XmlChild* child = pubsub_iq_response->FirstChild();
+ child != NULL;
+ child = child->NextChild()) {
+ const buzz::XmlElement* child_element = child->AsElement();
+ const buzz::QName& child_name(child_element->Name());
+ if (child_name == QN_PUBSUB_ITEMS) {
+ HandlePubsubItems(child_element);
+ }
+ }
+}
+
+// Calls registered handlers in response to pubsub event or response to
+// IQ pubsub get.
+// 'items' is the child of a pubsub#event:event node or pubsub:pubsub node.
+void PubsubTask::HandlePubsubItems(const buzz::XmlElement* items) {
+ ASSERT(items->HasAttr(QN_NODE));
+ const std::string& node_name(items->Attr(QN_NODE));
+ NodeSubscriptions::iterator iter = subscribed_nodes_.find(node_name);
+ if (iter != subscribed_nodes_.end()) {
+ NodeHandler handler = iter->second;
+ const buzz::XmlElement* item = items->FirstElement();
+ while (item != NULL) {
+ const buzz::QName& item_name(item->Name());
+ if (item_name != QN_PUBSUB_EVENT_ITEM &&
+ item_name != QN_PUBSUB_EVENT_RETRACT &&
+ item_name != QN_PUBSUB_ITEM) {
+ continue;
+ }
+
+ (this->*handler)(item);
+ item = item->NextElement();
+ }
+ return;
+ }
+}
+
+}
diff --git a/talk/xmpp/pubsub_task.h b/talk/xmpp/pubsub_task.h
new file mode 100644
index 0000000..45a7462
--- /dev/null
+++ b/talk/xmpp/pubsub_task.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_PUBSUB_TASK_H_
+#define TALK_XMPP_PUBSUB_TASK_H_
+
+#include <map>
+#include <string>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// Base class to help write pubsub tasks.
+// In ProcessStart call SubscribeNode with namespaces of interest along with
+// NodeHandlers.
+// When pubsub notifications arrive and matches the namespace, the NodeHandlers
+// will be called back.
+class PubsubTask : public buzz::XmppTask {
+ public:
+ virtual ~PubsubTask();
+
+ protected:
+ typedef void (PubsubTask::*NodeHandler)(const buzz::XmlElement* node);
+
+ PubsubTask(XmppTaskParentInterface* parent, const buzz::Jid& pubsub_node_jid);
+
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+ virtual int ProcessResponse();
+
+ bool SubscribeToNode(const std::string& pubsub_node, NodeHandler handler);
+ void UnsubscribeFromNode(const std::string& pubsub_node);
+
+ // Called when there is an error. Derived class can do what it needs to.
+ virtual void OnPubsubError(const buzz::XmlElement* error_stanza);
+
+ private:
+ typedef std::map<std::string, NodeHandler> NodeSubscriptions;
+
+ void HandlePubsubIqGetResponse(const buzz::XmlElement* pubsub_iq_response);
+ void HandlePubsubEventMessage(const buzz::XmlElement* pubsub_event_message);
+ void HandlePubsubItems(const buzz::XmlElement* items);
+
+ buzz::Jid pubsub_node_jid_;
+ NodeSubscriptions subscribed_nodes_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_PUBSUB_TASK_H_
diff --git a/talk/xmpp/pubsubclient_unittest.cc b/talk/xmpp/pubsubclient_unittest.cc
new file mode 100644
index 0000000..2e4c511
--- /dev/null
+++ b/talk/xmpp/pubsubclient_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+
+struct HandledPubSubItem {
+ std::string itemid;
+ std::string payload;
+};
+
+class TestPubSubItemsListener : public sigslot::has_slots<> {
+ public:
+ TestPubSubItemsListener() : error_count(0) {}
+
+ void OnItems(buzz::PubSubClient*,
+ const std::vector<buzz::PubSubItem>& items) {
+ for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ HandledPubSubItem handled_item;
+ handled_item.itemid = item->itemid;
+ if (item->elem->FirstElement() != NULL) {
+ handled_item.payload = item->elem->FirstElement()->Str();
+ }
+ this->items.push_back(handled_item);
+ }
+ }
+
+ void OnRequestError(buzz::PubSubClient* client,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ }
+
+ void OnPublishResult(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* item) {
+ result_task_id = task_id;
+ }
+
+ void OnPublishError(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* item,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ error_task_id = task_id;
+ }
+
+ void OnRetractResult(buzz::PubSubClient* client,
+ const std::string& task_id) {
+ result_task_id = task_id;
+ }
+
+ void OnRetractError(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ error_task_id = task_id;
+ }
+
+ std::vector<HandledPubSubItem> items;
+ int error_count;
+ std::string error_task_id;
+ std::string result_task_id;
+};
+
+class PubSubClientTest : public testing::Test {
+ public:
+ PubSubClientTest() :
+ pubsubjid("room@domain.com"),
+ node("topic"),
+ itemid("key") {
+ runner.reset(new talk_base::FakeTaskRunner());
+ xmpp_client = new buzz::FakeXmppClient(runner.get());
+ client.reset(new buzz::PubSubClient(xmpp_client, pubsubjid, node));
+ listener.reset(new TestPubSubItemsListener());
+ client->SignalItems.connect(
+ listener.get(), &TestPubSubItemsListener::OnItems);
+ client->SignalRequestError.connect(
+ listener.get(), &TestPubSubItemsListener::OnRequestError);
+ client->SignalPublishResult.connect(
+ listener.get(), &TestPubSubItemsListener::OnPublishResult);
+ client->SignalPublishError.connect(
+ listener.get(), &TestPubSubItemsListener::OnPublishError);
+ client->SignalRetractResult.connect(
+ listener.get(), &TestPubSubItemsListener::OnRetractResult);
+ client->SignalRetractError.connect(
+ listener.get(), &TestPubSubItemsListener::OnRetractError);
+ }
+
+ talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+ // xmpp_client deleted by deleting runner.
+ buzz::FakeXmppClient* xmpp_client;
+ talk_base::scoped_ptr<buzz::PubSubClient> client;
+ talk_base::scoped_ptr<TestPubSubItemsListener> listener;
+ buzz::Jid pubsubjid;
+ std::string node;
+ std::string itemid;
+};
+
+TEST_F(PubSubClientTest, TestRequest) {
+ client->RequestItems();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"topic\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0a/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1a/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ("<pub:value0a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ("<pub:value1a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[1].payload);
+
+ std::string items_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0b/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1b/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+ ASSERT_EQ(4U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[2].itemid);
+ EXPECT_EQ("<eve:value0b"
+ " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[2].payload);
+ EXPECT_EQ("key1", listener->items[3].itemid);
+ EXPECT_EQ("<eve:value1b"
+ " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[3].payload);
+}
+
+TEST_F(PubSubClientTest, TestRequestError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ client->RequestItems();
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubClientTest, TestPublish) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::string task_id;
+ client->PublishItem(itemid, payload, &task_id);
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"topic\">"
+ "<item id=\"key\">"
+ "<value/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestPublishError) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::string task_id;
+ client->PublishItem(itemid, payload, &task_id);
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+ EXPECT_EQ(task_id, listener->error_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetract) {
+ std::string task_id;
+ client->RetractItem(itemid, &task_id);
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"topic\" notify=\"true\">"
+ "<item id=\"key\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetractError) {
+ std::string task_id;
+ client->RetractItem(itemid, &task_id);
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+ EXPECT_EQ(task_id, listener->error_task_id);
+}
diff --git a/talk/xmpp/pubsubtasks_unittest.cc b/talk/xmpp/pubsubtasks_unittest.cc
new file mode 100644
index 0000000..67fc306
--- /dev/null
+++ b/talk/xmpp/pubsubtasks_unittest.cc
@@ -0,0 +1,273 @@
+// Copyright 2011 Google Inc. All Rights Reserved
+
+
+#include <string>
+
+#include "talk/base/faketaskrunner.h"
+#include "talk/base/gunit.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/iqtask.h"
+#include "talk/xmpp/fakexmppclient.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+struct HandledPubSubItem {
+ std::string itemid;
+ std::string payload;
+};
+
+class TestPubSubTasksListener : public sigslot::has_slots<> {
+ public:
+ TestPubSubTasksListener() : result_count(0), error_count(0) {}
+
+ void OnReceiveUpdate(buzz::PubSubReceiveTask* task,
+ const std::vector<buzz::PubSubItem>& items) {
+ OnItems(items);
+ }
+
+ void OnRequestResult(buzz::PubSubRequestTask* task,
+ const std::vector<buzz::PubSubItem>& items) {
+ OnItems(items);
+ }
+
+ void OnItems(const std::vector<buzz::PubSubItem>& items) {
+ for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ HandledPubSubItem handled_item;
+ handled_item.itemid = item->itemid;
+ if (item->elem->FirstElement() != NULL) {
+ handled_item.payload = item->elem->FirstElement()->Str();
+ }
+ this->items.push_back(handled_item);
+ }
+ }
+
+ void OnPublishResult(buzz::PubSubPublishTask* task) {
+ ++result_count;
+ }
+
+ void OnRetractResult(buzz::PubSubRetractTask* task) {
+ ++result_count;
+ }
+
+ void OnError(buzz::IqTask* task, const buzz::XmlElement* stanza) {
+ ++error_count;
+ }
+
+ std::vector<HandledPubSubItem> items;
+ int result_count;
+ int error_count;
+};
+
+class PubSubTasksTest : public testing::Test {
+ public:
+ PubSubTasksTest() :
+ pubsubjid("room@domain.com"),
+ node("topic"),
+ itemid("key") {
+ runner.reset(new talk_base::FakeTaskRunner());
+ client = new buzz::FakeXmppClient(runner.get());
+ listener.reset(new TestPubSubTasksListener());
+ }
+
+ talk_base::scoped_ptr<talk_base::FakeTaskRunner> runner;
+ // Client deleted by deleting runner.
+ buzz::FakeXmppClient* client;
+ talk_base::scoped_ptr<TestPubSubTasksListener> listener;
+ buzz::Jid pubsubjid;
+ std::string node;
+ std::string itemid;
+};
+
+TEST_F(PubSubTasksTest, TestRequest) {
+ buzz::PubSubRequestTask* task =
+ new buzz::PubSubRequestTask(client, pubsubjid, node);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRequestResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"topic\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ("<pub:value0 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ("<pub:value1 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestRequestError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ buzz::PubSubRequestTask* task =
+ new buzz::PubSubRequestTask(client, pubsubjid, node);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRequestResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestReceive) {
+ std::string items_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ buzz::PubSubReceiveTask* task =
+ new buzz::PubSubReceiveTask(client, pubsubjid, node);
+ task->SignalUpdate.connect(
+ listener.get(), &TestPubSubTasksListener::OnReceiveUpdate);
+ task->Start();
+ client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ(
+ "<eve:value0 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ(
+ "<eve:value1 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestPublish) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"topic\">"
+ "<item id=\"key\">"
+ "<value/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ std::vector<buzz::XmlElement*> children;
+ children.push_back(payload);
+ buzz::PubSubPublishTask* task =
+ new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnPublishResult);
+ task->Start();
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestPublishError) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::vector<buzz::XmlElement*> children;
+ children.push_back(payload);
+ buzz::PubSubPublishTask* task =
+ new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnPublishResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestRetract) {
+ buzz::PubSubRetractTask* task =
+ new buzz::PubSubRetractTask(client, pubsubjid, node, itemid);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRetractResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"topic\" notify=\"true\">"
+ "<item id=\"key\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
diff --git a/talk/xmpp/ratelimitmanager.h b/talk/xmpp/ratelimitmanager.h
index 1a7fc82..288084f 100644
--- a/talk/xmpp/ratelimitmanager.h
+++ b/talk/xmpp/ratelimitmanager.h
@@ -28,7 +28,7 @@
#ifndef _RATELIMITMANAGER_H_
#define _RATELIMITMANAGER_H_
-#include "talk/base/time.h"
+#include "talk/base/timeutils.h"
#include "talk/base/taskrunner.h"
#include <map>
diff --git a/talk/xmpp/rostermodule.h b/talk/xmpp/rostermodule.h
new file mode 100644
index 0000000..e81c4f0
--- /dev/null
+++ b/talk/xmpp/rostermodule.h
@@ -0,0 +1,325 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _rostermodule_h_
+#define _rostermodule_h_
+
+#include "talk/xmpp/module.h"
+
+namespace buzz {
+
+class XmppRosterModule;
+
+// The main way you initialize and use the module would be like this:
+// XmppRosterModule *roster_module = XmppRosterModule::Create();
+// roster_module->RegisterEngine(engine);
+// roster_module->BroadcastPresence();
+// roster_module->RequestRosterUpdate();
+
+//! This enum captures the valid values for the show attribute in a presence
+//! stanza
+enum XmppPresenceShow
+{
+ XMPP_PRESENCE_CHAT = 0,
+ XMPP_PRESENCE_DEFAULT = 1,
+ XMPP_PRESENCE_AWAY = 2,
+ XMPP_PRESENCE_XA = 3,
+ XMPP_PRESENCE_DND = 4,
+};
+
+//! These are the valid subscription states in a roster contact. This
+//! represents the combination of the subscription and ask attributes
+enum XmppSubscriptionState
+{
+ XMPP_SUBSCRIPTION_NONE = 0,
+ XMPP_SUBSCRIPTION_NONE_ASKED = 1,
+ XMPP_SUBSCRIPTION_TO = 2,
+ XMPP_SUBSCRIPTION_FROM = 3,
+ XMPP_SUBSCRIPTION_FROM_ASKED = 4,
+ XMPP_SUBSCRIPTION_BOTH = 5,
+};
+
+//! These represent the valid types of presence stanzas for managing
+//! subscriptions
+enum XmppSubscriptionRequestType
+{
+ XMPP_REQUEST_SUBSCRIBE = 0,
+ XMPP_REQUEST_UNSUBSCRIBE = 1,
+ XMPP_REQUEST_SUBSCRIBED = 2,
+ XMPP_REQUEST_UNSUBSCRIBED = 3,
+};
+
+enum XmppPresenceAvailable {
+ XMPP_PRESENCE_UNAVAILABLE = 0,
+ XMPP_PRESENCE_AVAILABLE = 1,
+ XMPP_PRESENCE_ERROR = 2,
+};
+
+//! Presence Information
+//! This class stores both presence information for outgoing presence and is
+//! returned by methods in XmppRosterModule to represent recieved incoming
+//! presence information. When this class is writeable (non-const) then each
+//! update to any property will set the inner xml. Setting the raw_xml will
+//! rederive all of the other properties.
+class XmppPresence {
+public:
+ virtual ~XmppPresence() {}
+
+ //! Create a new Presence
+ //! This is typically only used when sending a directed presence
+ static XmppPresence* Create();
+
+ //! The Jid of for the presence information.
+ //! Typically this will be a full Jid with resource specified.
+ virtual const Jid jid() const = 0;
+
+ //! Is the contact available?
+ virtual XmppPresenceAvailable available() const = 0;
+
+ //! Sets if the user is available or not
+ virtual XmppReturnStatus set_available(XmppPresenceAvailable available) = 0;
+
+ //! The show value of the presence info
+ virtual XmppPresenceShow presence_show() const = 0;
+
+ //! Set the presence show value
+ virtual XmppReturnStatus set_presence_show(XmppPresenceShow show) = 0;
+
+ //! The Priority of the presence info
+ virtual int priority() const = 0;
+
+ //! Set the priority of the presence
+ virtual XmppReturnStatus set_priority(int priority) = 0;
+
+ //! The plain text status of the presence info.
+ //! If there are multiple status because of language, this will either be a
+ //! status that is not tagged for language or the first available
+ virtual const std::string& status() const = 0;
+
+ //! Sets the status for the presence info.
+ //! If there is more than one status present already then this will remove
+ //! them all and replace it with one status element we no specified language
+ virtual XmppReturnStatus set_status(const std::string& status) = 0;
+
+ //! The raw xml of the presence update
+ virtual const XmlElement* raw_xml() const = 0;
+
+ //! Sets the raw presence stanza for the presence update
+ //! This will cause all other data items in this structure to be rederived
+ virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0;
+};
+
+//! A contact as given by the server
+class XmppRosterContact {
+public:
+ virtual ~XmppRosterContact() {}
+
+ //! Create a new roster contact
+ //! This is typically only used when doing a roster update/add
+ static XmppRosterContact* Create();
+
+ //! The jid for the contact.
+ //! Typically this will be a bare Jid.
+ virtual const Jid jid() const = 0;
+
+ //! Sets the jid for the roster contact update
+ virtual XmppReturnStatus set_jid(const Jid& jid) = 0;
+
+ //! The name (nickname) stored for this contact
+ virtual const std::string& name() const = 0;
+
+ //! Sets the name
+ virtual XmppReturnStatus set_name(const std::string& name) = 0;
+
+ //! The Presence subscription state stored on the server for this contact
+ //! This is never settable and will be ignored when generating a roster
+ //! add/update request
+ virtual XmppSubscriptionState subscription_state() const = 0;
+
+ //! The number of Groups applied to this contact
+ virtual size_t GetGroupCount() const = 0;
+
+ //! Gets a Group applied to the contact based on index.
+ //! range
+ virtual const std::string& GetGroup(size_t index) const = 0;
+
+ //! Adds a group to this contact.
+ //! This will return a bad argument error if the group is already there.
+ virtual XmppReturnStatus AddGroup(const std::string& group) = 0;
+
+ //! Removes a group from the contact.
+ //! This will return an error if the group cannot be found in the group list.
+ virtual XmppReturnStatus RemoveGroup(const std::string& group) = 0;
+
+ //! The raw xml for this roster contact
+ virtual const XmlElement* raw_xml() const = 0;
+
+ //! Sets the raw presence stanza for the contact update/add
+ //! This will cause all other data items in this structure to be rederived
+ virtual XmppReturnStatus set_raw_xml(const XmlElement * xml) = 0;
+};
+
+//! The XmppRosterHandler is an interface for callbacks from the module
+class XmppRosterHandler {
+public:
+ //! A request for a subscription has come in.
+ //! Typically, the UI will ask the user if it is okay to let the requester
+ //! get presence notifications for the user. The response is send back
+ //! by calling ApproveSubscriber or CancelSubscriber.
+ virtual void SubscriptionRequest(XmppRosterModule* roster,
+ const Jid& requesting_jid,
+ XmppSubscriptionRequestType type,
+ const XmlElement* raw_xml) = 0;
+
+ //! Some type of presence error has occured
+ virtual void SubscriptionError(XmppRosterModule* roster,
+ const Jid& from,
+ const XmlElement* raw_xml) = 0;
+
+ virtual void RosterError(XmppRosterModule* roster,
+ const XmlElement* raw_xml) = 0;
+
+ //! New presence information has come in
+ //! The user is notified with the presence object directly. This info is also
+ //! added to the store accessable from the engine.
+ virtual void IncomingPresenceChanged(XmppRosterModule* roster,
+ const XmppPresence* presence) = 0;
+
+ //! A contact has changed
+ //! This indicates that the data for a contact may have changed. No
+ //! contacts have been added or removed.
+ virtual void ContactChanged(XmppRosterModule* roster,
+ const XmppRosterContact* old_contact,
+ size_t index) = 0;
+
+ //! A set of contacts have been added
+ //! These contacts may have been added in response to the original roster
+ //! request or due to a "roster push" from the server.
+ virtual void ContactsAdded(XmppRosterModule* roster,
+ size_t index, size_t number) = 0;
+
+ //! A contact has been removed
+ //! This contact has been removed form the list.
+ virtual void ContactRemoved(XmppRosterModule* roster,
+ const XmppRosterContact* removed_contact,
+ size_t index) = 0;
+
+};
+
+//! An XmppModule for handle roster and presence functionality
+class XmppRosterModule : public XmppModule {
+public:
+ //! Creates a new XmppRosterModule
+ static XmppRosterModule * Create();
+ virtual ~XmppRosterModule() {}
+
+ //! Sets the roster handler (callbacks) for the module
+ virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler) = 0;
+
+ //! Gets the roster handler for the module
+ virtual XmppRosterHandler* roster_handler() = 0;
+
+ // USER PRESENCE STATE -------------------------------------------------------
+
+ //! Gets the aggregate outgoing presence
+ //! This object is non-const and be edited directly. No update is sent
+ //! to the server until a Broadcast is sent
+ virtual XmppPresence* outgoing_presence() = 0;
+
+ //! Broadcasts that the user is available.
+ //! Nothing with respect to presence is sent until this is called.
+ virtual XmppReturnStatus BroadcastPresence() = 0;
+
+ //! Sends a directed presence to a Jid
+ //! Note that the client doesn't store where directed presence notifications
+ //! have been sent. The server can keep the appropriate state
+ virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence,
+ const Jid& to_jid) = 0;
+
+ // INCOMING PRESENCE STATUS --------------------------------------------------
+
+ //! Returns the number of incoming presence data recorded
+ virtual size_t GetIncomingPresenceCount() = 0;
+
+ //! Returns an incoming presence datum based on index
+ virtual const XmppPresence* GetIncomingPresence(size_t index) = 0;
+
+ //! Gets the number of presence data for a bare Jid
+ //! There may be a datum per resource
+ virtual size_t GetIncomingPresenceForJidCount(const Jid& jid) = 0;
+
+ //! Returns a single presence data for a Jid based on index
+ virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid,
+ size_t index) = 0;
+
+ // ROSTER MANAGEMENT ---------------------------------------------------------
+
+ //! Requests an update of the roster from the server
+ //! This must be called to initialize the client side cache of the roster
+ //! After this is sent the server should keep this module apprised of any
+ //! changes.
+ virtual XmppReturnStatus RequestRosterUpdate() = 0;
+
+ //! Returns the number of contacts in the roster
+ virtual size_t GetRosterContactCount() = 0;
+
+ //! Returns a contact by index
+ virtual const XmppRosterContact* GetRosterContact(size_t index) = 0;
+
+ //! Finds a contact by Jid
+ virtual const XmppRosterContact* FindRosterContact(const Jid& jid) = 0;
+
+ //! Send a request to the server to add a contact
+ //! Note that the contact won't show up in the roster until the server can
+ //! respond. This happens async when the socket is being serviced
+ virtual XmppReturnStatus RequestRosterChange(
+ const XmppRosterContact* contact) = 0;
+
+ //! Request that the server remove a contact
+ //! The jabber protocol specifies that the server should also cancel any
+ //! subscriptions when this is done. Like adding, this contact won't be
+ //! removed until the server responds.
+ virtual XmppReturnStatus RequestRosterRemove(const Jid& jid) = 0;
+
+ // SUBSCRIPTION MANAGEMENT ---------------------------------------------------
+
+ //! Request a subscription to presence notifications form a Jid
+ virtual XmppReturnStatus RequestSubscription(const Jid& jid) = 0;
+
+ //! Cancel a subscription to presence notifications from a Jid
+ virtual XmppReturnStatus CancelSubscription(const Jid& jid) = 0;
+
+ //! Approve a request to deliver presence notifications to a jid
+ virtual XmppReturnStatus ApproveSubscriber(const Jid& jid) = 0;
+
+ //! Deny or cancel presence notification deliver to a jid
+ virtual XmppReturnStatus CancelSubscriber(const Jid& jid) = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/rostermoduleimpl.cc b/talk/xmpp/rostermoduleimpl.cc
new file mode 100644
index 0000000..c5da5a9
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.cc
@@ -0,0 +1,1041 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <string>
+#include <map>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/rostermoduleimpl.h"
+
+namespace buzz {
+
+// enum prase and persist helpers ----------------------------------------------
+static bool
+StringToPresenceShow(const std::string& input, XmppPresenceShow* show)
+{
+ // If this becomes a perf issue we can use a hash or a map here
+ if (STR_SHOW_AWAY == input)
+ *show = XMPP_PRESENCE_AWAY;
+ else if (STR_SHOW_DND == input)
+ *show = XMPP_PRESENCE_DND;
+ else if (STR_SHOW_XA == input)
+ *show = XMPP_PRESENCE_XA;
+ else if (STR_SHOW_CHAT == input)
+ *show = XMPP_PRESENCE_CHAT;
+ else if (STR_EMPTY == input)
+ *show = XMPP_PRESENCE_DEFAULT;
+ else
+ return false;
+
+ return true;
+}
+
+static bool
+PresenceShowToString(XmppPresenceShow show, const std::string ** output)
+{
+ switch(show) {
+ case XMPP_PRESENCE_AWAY:
+ *output = &STR_SHOW_AWAY;
+ return true;
+ case XMPP_PRESENCE_CHAT:
+ *output = &STR_SHOW_CHAT;
+ return true;
+ case XMPP_PRESENCE_XA:
+ *output = &STR_SHOW_XA;
+ return true;
+ case XMPP_PRESENCE_DND:
+ *output = &STR_SHOW_DND;
+ return true;
+ case XMPP_PRESENCE_DEFAULT:
+ *output = &STR_EMPTY;
+ return true;
+ }
+
+ *output = &STR_EMPTY;
+ return false;
+}
+
+static bool
+StringToSubscriptionState(const std::string& subscription,
+ const std::string& ask,
+ XmppSubscriptionState* state)
+{
+ if (ask == "subscribe")
+ {
+ if (subscription == "none") {
+ *state = XMPP_SUBSCRIPTION_NONE_ASKED;
+ return true;
+ }
+ if (subscription == "from") {
+ *state = XMPP_SUBSCRIPTION_FROM_ASKED;
+ return true;
+ }
+ } else if (ask == STR_EMPTY)
+ {
+ if (subscription == "none") {
+ *state = XMPP_SUBSCRIPTION_NONE;
+ return true;
+ }
+ if (subscription == "from") {
+ *state = XMPP_SUBSCRIPTION_FROM;
+ return true;
+ }
+ if (subscription == "to") {
+ *state = XMPP_SUBSCRIPTION_TO;
+ return true;
+ }
+ if (subscription == "both") {
+ *state = XMPP_SUBSCRIPTION_BOTH;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+StringToSubscriptionRequestType(const std::string& string,
+ XmppSubscriptionRequestType* type)
+{
+ if (string == "subscribe")
+ *type = XMPP_REQUEST_SUBSCRIBE;
+ else if (string == "unsubscribe")
+ *type = XMPP_REQUEST_UNSUBSCRIBE;
+ else if (string == "subscribed")
+ *type = XMPP_REQUEST_SUBSCRIBED;
+ else if (string == "unsubscribed")
+ *type = XMPP_REQUEST_UNSUBSCRIBED;
+ else
+ return false;
+ return true;
+}
+
+// XmppPresenceImpl class ------------------------------------------------------
+XmppPresence*
+XmppPresence::Create() {
+ return new XmppPresenceImpl();
+}
+
+XmppPresenceImpl::XmppPresenceImpl() {
+}
+
+const Jid
+XmppPresenceImpl::jid() const {
+ if (!raw_xml_.get())
+ return JID_EMPTY;
+
+ return Jid(raw_xml_->Attr(QN_FROM));
+}
+
+XmppPresenceAvailable
+XmppPresenceImpl::available() const {
+ if (!raw_xml_.get())
+ return XMPP_PRESENCE_UNAVAILABLE;
+
+ if (raw_xml_->Attr(QN_TYPE) == "unavailable")
+ return XMPP_PRESENCE_UNAVAILABLE;
+ else if (raw_xml_->Attr(QN_TYPE) == "error")
+ return XMPP_PRESENCE_ERROR;
+ else
+ return XMPP_PRESENCE_AVAILABLE;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_available(XmppPresenceAvailable available) {
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ if (available == XMPP_PRESENCE_AVAILABLE)
+ raw_xml_->ClearAttr(QN_TYPE);
+ else if (available == XMPP_PRESENCE_UNAVAILABLE)
+ raw_xml_->SetAttr(QN_TYPE, "unavailable");
+ else if (available == XMPP_PRESENCE_ERROR)
+ raw_xml_->SetAttr(QN_TYPE, "error");
+ return XMPP_RETURN_OK;
+}
+
+XmppPresenceShow
+XmppPresenceImpl::presence_show() const {
+ if (!raw_xml_.get())
+ return XMPP_PRESENCE_DEFAULT;
+
+ XmppPresenceShow show;
+
+ if (StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show))
+ return show;
+
+ return XMPP_PRESENCE_DEFAULT;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_presence_show(XmppPresenceShow show) {
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ const std::string* show_string;
+
+ if(!PresenceShowToString(show, &show_string))
+ return XMPP_RETURN_BADARGUMENT;
+
+ raw_xml_->ClearNamedChildren(QN_SHOW);
+
+ if (show!=XMPP_PRESENCE_DEFAULT) {
+ raw_xml_->AddElement(new XmlElement(QN_SHOW));
+ raw_xml_->AddText(*show_string, 1);
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+int
+XmppPresenceImpl::priority() const {
+ if (!raw_xml_.get())
+ return 0;
+
+ int raw_priority;
+ if (!talk_base::FromString(raw_xml_->TextNamed(QN_PRIORITY), &raw_priority))
+ raw_priority = 0;
+ if (raw_priority < -128)
+ raw_priority = -128;
+ if (raw_priority > 127)
+ raw_priority = 127;
+
+ return raw_priority;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_priority(int priority) {
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ if (priority < -128 || priority > 127)
+ return XMPP_RETURN_BADARGUMENT;
+
+ raw_xml_->ClearNamedChildren(QN_PRIORITY);
+ if (0 != priority) {
+ std::string priority_string;
+ if (talk_base::ToString(priority, &priority_string)) {
+ raw_xml_->AddElement(new XmlElement(QN_PRIORITY));
+ raw_xml_->AddText(priority_string, 1);
+ }
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+const std::string&
+XmppPresenceImpl::status() const {
+ if (!raw_xml_.get())
+ return STR_EMPTY;
+
+ XmlElement* status_element;
+ XmlElement* element;
+
+ // Search for a status element with no xml:lang attribute on it. if we can't
+ // find that then just return the first status element in the stanza.
+ for (status_element = element = raw_xml_->FirstNamed(QN_STATUS);
+ element;
+ element = element->NextNamed(QN_STATUS)) {
+ if (!element->HasAttr(QN_XML_LANG)) {
+ status_element = element;
+ break;
+ }
+ }
+
+ if (status_element) {
+ return status_element->BodyText();
+ }
+
+ return STR_EMPTY;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_status(const std::string& status) {
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ raw_xml_->ClearNamedChildren(QN_STATUS);
+
+ if (status != STR_EMPTY) {
+ raw_xml_->AddElement(new XmlElement(QN_STATUS));
+ raw_xml_->AddText(status, 1);
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+const XmlElement*
+XmppPresenceImpl::raw_xml() const {
+ if (!raw_xml_.get())
+ const_cast<XmppPresenceImpl*>(this)->CreateRawXmlSkeleton();
+ return raw_xml_.get();
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_raw_xml(const XmlElement * xml) {
+ if (!xml ||
+ xml->Name() != QN_PRESENCE)
+ return XMPP_RETURN_BADARGUMENT;
+
+ const std::string& type = xml->Attr(QN_TYPE);
+ if (type != STR_EMPTY && type != "unavailable")
+ return XMPP_RETURN_BADARGUMENT;
+
+ raw_xml_.reset(new XmlElement(*xml));
+
+ return XMPP_RETURN_OK;
+}
+
+void
+XmppPresenceImpl::CreateRawXmlSkeleton() {
+ raw_xml_.reset(new XmlElement(QN_PRESENCE));
+}
+
+// XmppRosterContactImpl -------------------------------------------------------
+XmppRosterContact*
+XmppRosterContact::Create() {
+ return new XmppRosterContactImpl();
+}
+
+XmppRosterContactImpl::XmppRosterContactImpl() {
+ ResetGroupCache();
+}
+
+void
+XmppRosterContactImpl::SetXmlFromWire(const XmlElement* xml) {
+ ResetGroupCache();
+ if (xml)
+ raw_xml_.reset(new XmlElement(*xml));
+ else
+ raw_xml_.reset(NULL);
+}
+
+void
+XmppRosterContactImpl::ResetGroupCache() {
+ group_count_ = -1;
+ group_index_returned_ = -1;
+ group_returned_ = NULL;
+}
+
+const Jid
+XmppRosterContactImpl::jid() const {
+ return Jid(raw_xml_->Attr(QN_JID));
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_jid(const Jid& jid)
+{
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ if (!jid.IsValid())
+ return XMPP_RETURN_BADARGUMENT;
+
+ raw_xml_->SetAttr(QN_JID, jid.Str());
+
+ return XMPP_RETURN_OK;
+}
+
+const std::string&
+XmppRosterContactImpl::name() const {
+ return raw_xml_->Attr(QN_NAME);
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_name(const std::string& name) {
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ if (name == STR_EMPTY)
+ raw_xml_->ClearAttr(QN_NAME);
+ else
+ raw_xml_->SetAttr(QN_NAME, name);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppSubscriptionState
+XmppRosterContactImpl::subscription_state() const {
+ if (!raw_xml_.get())
+ return XMPP_SUBSCRIPTION_NONE;
+
+ XmppSubscriptionState state;
+
+ if (StringToSubscriptionState(raw_xml_->Attr(QN_SUBSCRIPTION),
+ raw_xml_->Attr(QN_ASK),
+ &state))
+ return state;
+
+ return XMPP_SUBSCRIPTION_NONE;
+}
+
+size_t
+XmppRosterContactImpl::GetGroupCount() const {
+ if (!raw_xml_.get())
+ return 0;
+
+ if (-1 == group_count_) {
+ XmlElement *group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP);
+ int group_count = 0;
+ while(group_element) {
+ group_count++;
+ group_element = group_element->NextNamed(QN_ROSTER_GROUP);
+ }
+
+ ASSERT(group_count > 0); // protect the cast
+ XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+ me->group_count_ = group_count;
+ }
+
+ return group_count_;
+}
+
+const std::string&
+XmppRosterContactImpl::GetGroup(size_t index) const {
+ if (index >= GetGroupCount())
+ return STR_EMPTY;
+
+ // We cache the last group index and element that we returned. This way
+ // going through the groups in order is order n and not n^2. This could be
+ // enhanced if necessary by starting at the cached value if the index asked
+ // is after the cached one.
+ if (group_index_returned_ >= 0 &&
+ index == static_cast<size_t>(group_index_returned_) + 1)
+ {
+ XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+ me->group_returned_ = group_returned_->NextNamed(QN_ROSTER_GROUP);
+ ASSERT(group_returned_ != NULL);
+ me->group_index_returned_++;
+ } else if (group_index_returned_ < 0 ||
+ static_cast<size_t>(group_index_returned_) != index) {
+ XmlElement * group_element = raw_xml_->FirstNamed(QN_ROSTER_GROUP);
+ size_t group_index = 0;
+ while(group_index < index) {
+ ASSERT(group_element != NULL);
+ group_index++;
+ group_element = group_element->NextNamed(QN_ROSTER_GROUP);
+ }
+
+ XmppRosterContactImpl * me = const_cast<XmppRosterContactImpl*>(this);
+ me->group_index_returned_ = static_cast<int>(group_index);
+ me->group_returned_ = group_element;
+ }
+
+ return group_returned_->BodyText();
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::AddGroup(const std::string& group) {
+ if (group == STR_EMPTY)
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!raw_xml_.get())
+ CreateRawXmlSkeleton();
+
+ if (FindGroup(group, NULL, NULL))
+ return XMPP_RETURN_OK;
+
+ raw_xml_->AddElement(new XmlElement(QN_ROSTER_GROUP));
+ raw_xml_->AddText(group, 1);
+ ++group_count_;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::RemoveGroup(const std::string& group) {
+ if (group == STR_EMPTY)
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!raw_xml_.get())
+ return XMPP_RETURN_OK;
+
+ XmlChild * child_before;
+ if (FindGroup(group, NULL, &child_before)) {
+ raw_xml_->RemoveChildAfter(child_before);
+ ResetGroupCache();
+ }
+ return XMPP_RETURN_OK;
+}
+
+bool
+XmppRosterContactImpl::FindGroup(const std::string& group,
+ XmlElement** element,
+ XmlChild** child_before) {
+ XmlChild * prev_child = NULL;
+ XmlChild * next_child;
+ XmlChild * child;
+ for (child = raw_xml_->FirstChild(); child; child = next_child) {
+ next_child = child->NextChild();
+ if (!child->IsText() &&
+ child->AsElement()->Name() == QN_ROSTER_GROUP &&
+ child->AsElement()->BodyText() == group) {
+ if (element)
+ *element = child->AsElement();
+ if (child_before)
+ *child_before = prev_child;
+ return true;
+ }
+ prev_child = child;
+ }
+
+ return false;
+}
+
+const XmlElement*
+XmppRosterContactImpl::raw_xml() const {
+ if (!raw_xml_.get())
+ const_cast<XmppRosterContactImpl*>(this)->CreateRawXmlSkeleton();
+ return raw_xml_.get();
+}
+
+XmppReturnStatus
+XmppRosterContactImpl::set_raw_xml(const XmlElement* xml) {
+ if (!xml ||
+ xml->Name() != QN_ROSTER_ITEM ||
+ xml->HasAttr(QN_SUBSCRIPTION) ||
+ xml->HasAttr(QN_ASK))
+ return XMPP_RETURN_BADARGUMENT;
+
+ ResetGroupCache();
+
+ raw_xml_.reset(new XmlElement(*xml));
+
+ return XMPP_RETURN_OK;
+}
+
+void
+XmppRosterContactImpl::CreateRawXmlSkeleton() {
+ raw_xml_.reset(new XmlElement(QN_ROSTER_ITEM));
+}
+
+// XmppRosterModuleImpl --------------------------------------------------------
+XmppRosterModule *
+XmppRosterModule::Create() {
+ return new XmppRosterModuleImpl();
+}
+
+XmppRosterModuleImpl::XmppRosterModuleImpl() :
+ roster_handler_(NULL),
+ incoming_presence_map_(new JidPresenceVectorMap()),
+ incoming_presence_vector_(new PresenceVector()),
+ contacts_(new ContactVector()) {
+
+}
+
+XmppRosterModuleImpl::~XmppRosterModuleImpl() {
+ DeleteIncomingPresence();
+ DeleteContacts();
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::set_roster_handler(XmppRosterHandler * handler) {
+ roster_handler_ = handler;
+ return XMPP_RETURN_OK;
+}
+
+XmppRosterHandler*
+XmppRosterModuleImpl::roster_handler() {
+ return roster_handler_;
+}
+
+XmppPresence*
+XmppRosterModuleImpl::outgoing_presence() {
+ return &outgoing_presence_;
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::BroadcastPresence() {
+ // Scrub the outgoing presence
+ const XmlElement* element = outgoing_presence_.raw_xml();
+
+ ASSERT(!element->HasAttr(QN_TO) &&
+ !element->HasAttr(QN_FROM) &&
+ (element->Attr(QN_TYPE) == STR_EMPTY ||
+ element->Attr(QN_TYPE) == "unavailable"));
+
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ return engine()->SendStanza(element);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::SendDirectedPresence(const XmppPresence* presence,
+ const Jid& to_jid) {
+ if (!presence)
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement element(*(presence->raw_xml()));
+
+ if (element.Name() != QN_PRESENCE ||
+ element.HasAttr(QN_TO) ||
+ element.HasAttr(QN_FROM))
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (element.HasAttr(QN_TYPE)) {
+ if (element.Attr(QN_TYPE) != STR_EMPTY &&
+ element.Attr(QN_TYPE) != "unavailable") {
+ return XMPP_RETURN_BADARGUMENT;
+ }
+ }
+
+ element.SetAttr(QN_TO, to_jid.Str());
+
+ return engine()->SendStanza(&element);
+}
+
+size_t
+XmppRosterModuleImpl::GetIncomingPresenceCount() {
+ return incoming_presence_vector_->size();
+}
+
+const XmppPresence*
+XmppRosterModuleImpl::GetIncomingPresence(size_t index) {
+ if (index >= incoming_presence_vector_->size())
+ return NULL;
+ return (*incoming_presence_vector_)[index];
+}
+
+size_t
+XmppRosterModuleImpl::GetIncomingPresenceForJidCount(const Jid& jid)
+{
+ // find the vector in the map
+ JidPresenceVectorMap::iterator pos;
+ pos = incoming_presence_map_->find(jid);
+ if (pos == incoming_presence_map_->end())
+ return 0;
+
+ ASSERT(pos->second != NULL);
+
+ return pos->second->size();
+}
+
+const XmppPresence*
+XmppRosterModuleImpl::GetIncomingPresenceForJid(const Jid& jid,
+ size_t index) {
+ JidPresenceVectorMap::iterator pos;
+ pos = incoming_presence_map_->find(jid);
+ if (pos == incoming_presence_map_->end())
+ return NULL;
+
+ ASSERT(pos->second != NULL);
+
+ if (index >= pos->second->size())
+ return NULL;
+
+ return (*pos->second)[index];
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterUpdate() {
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement roster_get(QN_IQ);
+ roster_get.AddAttr(QN_TYPE, "get");
+ roster_get.AddAttr(QN_ID, engine()->NextId());
+ roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ return engine()->SendIq(&roster_get, this, NULL);
+}
+
+size_t
+XmppRosterModuleImpl::GetRosterContactCount() {
+ return contacts_->size();
+}
+
+const XmppRosterContact*
+XmppRosterModuleImpl::GetRosterContact(size_t index) {
+ if (index >= contacts_->size())
+ return NULL;
+ return (*contacts_)[index];
+}
+
+class RosterPredicate {
+public:
+ explicit RosterPredicate(const Jid& jid) : jid_(jid) {
+ }
+
+ bool operator() (XmppRosterContactImpl *& contact) {
+ return contact->jid() == jid_;
+ }
+
+private:
+ Jid jid_;
+};
+
+const XmppRosterContact*
+XmppRosterModuleImpl::FindRosterContact(const Jid& jid) {
+ ContactVector::iterator pos;
+
+ pos = std::find_if(contacts_->begin(),
+ contacts_->end(),
+ RosterPredicate(jid));
+ if (pos == contacts_->end())
+ return NULL;
+
+ return *pos;
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterChange(
+ const XmppRosterContact* contact) {
+ if (!contact)
+ return XMPP_RETURN_BADARGUMENT;
+
+ Jid jid = contact->jid();
+
+ if (!jid.IsValid())
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ const XmlElement* contact_xml = contact->raw_xml();
+ if (contact_xml->Name() != QN_ROSTER_ITEM ||
+ contact_xml->HasAttr(QN_SUBSCRIPTION) ||
+ contact_xml->HasAttr(QN_ASK))
+ return XMPP_RETURN_BADARGUMENT;
+
+ XmlElement roster_add(QN_IQ);
+ roster_add.AddAttr(QN_TYPE, "set");
+ roster_add.AddAttr(QN_ID, engine()->NextId());
+ roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ roster_add.AddElement(new XmlElement(*contact_xml), 1);
+
+ return engine()->SendIq(&roster_add, this, NULL);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestRosterRemove(const Jid& jid) {
+ if (!jid.IsValid())
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement roster_add(QN_IQ);
+ roster_add.AddAttr(QN_TYPE, "set");
+ roster_add.AddAttr(QN_ID, engine()->NextId());
+ roster_add.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ roster_add.AddAttr(QN_JID, jid.Str(), 1);
+ roster_add.AddAttr(QN_SUBSCRIPTION, "remove", 1);
+
+ return engine()->SendIq(&roster_add, this, NULL);
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::RequestSubscription(const Jid& jid) {
+ return SendSubscriptionRequest(jid, "subscribe");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::CancelSubscription(const Jid& jid) {
+ return SendSubscriptionRequest(jid, "unsubscribe");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::ApproveSubscriber(const Jid& jid) {
+ return SendSubscriptionRequest(jid, "subscribed");
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::CancelSubscriber(const Jid& jid) {
+ return SendSubscriptionRequest(jid, "unsubscribed");
+}
+
+void
+XmppRosterModuleImpl::IqResponse(XmppIqCookie, const XmlElement * stanza) {
+ // The only real Iq response that we expect to recieve are initial roster
+ // population
+ if (stanza->Attr(QN_TYPE) == "error")
+ {
+ if (roster_handler_)
+ roster_handler_->RosterError(this, stanza);
+
+ return;
+ }
+
+ ASSERT(stanza->Attr(QN_TYPE) == "result");
+
+ InternalRosterItems(stanza);
+}
+
+bool
+XmppRosterModuleImpl::HandleStanza(const XmlElement * stanza)
+{
+ ASSERT(engine() != NULL);
+
+ // There are two types of stanzas that we care about: presence and roster push
+ // Iqs
+ if (stanza->Name() == QN_PRESENCE) {
+ const std::string& jid_string = stanza->Attr(QN_FROM);
+ Jid jid(jid_string);
+
+ if (!jid.IsValid())
+ return false; // if the Jid isn't valid, don't process
+
+ const std::string& type = stanza->Attr(QN_TYPE);
+ XmppSubscriptionRequestType request_type;
+ if (StringToSubscriptionRequestType(type, &request_type))
+ InternalSubscriptionRequest(jid, stanza, request_type);
+ else if (type == "unavailable" || type == STR_EMPTY)
+ InternalIncomingPresence(jid, stanza);
+ else if (type == "error")
+ InternalIncomingPresenceError(jid, stanza);
+ else
+ return false;
+
+ return true;
+ } else if (stanza->Name() == QN_IQ) {
+ const XmlElement * roster_query = stanza->FirstNamed(QN_ROSTER_QUERY);
+ if (!roster_query || stanza->Attr(QN_TYPE) != "set")
+ return false;
+
+ InternalRosterItems(stanza);
+
+ // respond to the IQ
+ XmlElement result(QN_IQ);
+ result.AddAttr(QN_TYPE, "result");
+ result.AddAttr(QN_TO, stanza->Attr(QN_FROM));
+ result.AddAttr(QN_ID, stanza->Attr(QN_ID));
+
+ engine()->SendStanza(&result);
+ return true;
+ }
+
+ return false;
+}
+
+void
+XmppRosterModuleImpl::DeleteIncomingPresence() {
+ // Clear out the vector of all presence notifications
+ {
+ PresenceVector::iterator pos;
+ for (pos = incoming_presence_vector_->begin();
+ pos < incoming_presence_vector_->end();
+ ++pos) {
+ XmppPresenceImpl * presence = *pos;
+ *pos = NULL;
+ delete presence;
+ }
+ incoming_presence_vector_->clear();
+ }
+
+ // Clear out all of the small presence vectors per Jid
+ {
+ JidPresenceVectorMap::iterator pos;
+ for (pos = incoming_presence_map_->begin();
+ pos != incoming_presence_map_->end();
+ ++pos) {
+ PresenceVector* presence_vector = pos->second;
+ pos->second = NULL;
+ delete presence_vector;
+ }
+ incoming_presence_map_->clear();
+ }
+}
+
+void
+XmppRosterModuleImpl::DeleteContacts() {
+ ContactVector::iterator pos;
+ for (pos = contacts_->begin();
+ pos < contacts_->end();
+ ++pos) {
+ XmppRosterContact* contact = *pos;
+ *pos = NULL;
+ delete contact;
+ }
+ contacts_->clear();
+}
+
+XmppReturnStatus
+XmppRosterModuleImpl::SendSubscriptionRequest(const Jid& jid,
+ const std::string& type) {
+ if (!jid.IsValid())
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement presence_request(QN_PRESENCE);
+ presence_request.AddAttr(QN_TO, jid.Str());
+ presence_request.AddAttr(QN_TYPE, type);
+
+ return engine()->SendStanza(&presence_request);
+}
+
+
+void
+XmppRosterModuleImpl::InternalSubscriptionRequest(const Jid& jid,
+ const XmlElement* stanza,
+ XmppSubscriptionRequestType
+ request_type) {
+ if (roster_handler_)
+ roster_handler_->SubscriptionRequest(this, jid, request_type, stanza);
+}
+
+class PresencePredicate {
+public:
+ explicit PresencePredicate(const Jid& jid) : jid_(jid) {
+ }
+
+ bool operator() (XmppPresenceImpl *& contact) {
+ return contact->jid() == jid_;
+ }
+
+private:
+ Jid jid_;
+};
+
+void
+XmppRosterModuleImpl::InternalIncomingPresence(const Jid& jid,
+ const XmlElement* stanza) {
+ bool added = false;
+ Jid bare_jid = jid.BareJid();
+
+ // First add the presence to the map
+ JidPresenceVectorMap::iterator pos;
+ pos = incoming_presence_map_->find(jid.BareJid());
+ if (pos == incoming_presence_map_->end()) {
+ // Insert a new entry into the map. Get the position of this new entry
+ pos = (incoming_presence_map_->insert(
+ std::make_pair(bare_jid, new PresenceVector()))).first;
+ }
+
+ PresenceVector * presence_vector = pos->second;
+ ASSERT(presence_vector != NULL);
+
+ // Try to find this jid in the bare jid bucket
+ PresenceVector::iterator presence_pos;
+ XmppPresenceImpl* presence;
+ presence_pos = std::find_if(presence_vector->begin(),
+ presence_vector->end(),
+ PresencePredicate(jid));
+
+ // Update/add it to the bucket
+ if (presence_pos == presence_vector->end()) {
+ presence = new XmppPresenceImpl();
+ if (XMPP_RETURN_OK == presence->set_raw_xml(stanza)) {
+ added = true;
+ presence_vector->push_back(presence);
+ } else {
+ delete presence;
+ presence = NULL;
+ }
+ } else {
+ presence = *presence_pos;
+ presence->set_raw_xml(stanza);
+ }
+
+ // now add to the comprehensive vector
+ if (added)
+ incoming_presence_vector_->push_back(presence);
+
+ // Call back to the user with the changed presence information
+ if (roster_handler_)
+ roster_handler_->IncomingPresenceChanged(this, presence);
+}
+
+
+void
+XmppRosterModuleImpl::InternalIncomingPresenceError(const Jid& jid,
+ const XmlElement* stanza) {
+ if (roster_handler_)
+ roster_handler_->SubscriptionError(this, jid, stanza);
+}
+
+void
+XmppRosterModuleImpl::InternalRosterItems(const XmlElement* stanza) {
+ const XmlElement* result_data = stanza->FirstNamed(QN_ROSTER_QUERY);
+ if (!result_data)
+ return; // unknown stuff in result!
+
+ bool all_new = contacts_->empty();
+
+ for (const XmlElement* roster_item = result_data->FirstNamed(QN_ROSTER_ITEM);
+ roster_item;
+ roster_item = roster_item->NextNamed(QN_ROSTER_ITEM))
+ {
+ const std::string& jid_string = roster_item->Attr(QN_JID);
+ Jid jid(jid_string);
+ if (!jid.IsValid())
+ continue;
+
+ // This algorithm is N^2 on the number of incoming contacts after the
+ // initial load. There is no way to do this faster without allowing
+ // duplicates, introducing more data structures or write a custom data
+ // structure. We'll see if this becomes a perf problem and fix it if it
+ // does.
+ ContactVector::iterator pos = contacts_->end();
+
+ if (!all_new) {
+ pos = std::find_if(contacts_->begin(),
+ contacts_->end(),
+ RosterPredicate(jid));
+ }
+
+ if (pos != contacts_->end()) { // Update/remove a current contact
+ if (roster_item->Attr(QN_SUBSCRIPTION) == "remove") {
+ XmppRosterContact* contact = *pos;
+ contacts_->erase(pos);
+ if (roster_handler_)
+ roster_handler_->ContactRemoved(this, contact,
+ std::distance(contacts_->begin(), pos));
+ delete contact;
+ } else {
+ XmppRosterContact* old_contact = *pos;
+ *pos = new XmppRosterContactImpl();
+ (*pos)->SetXmlFromWire(roster_item);
+ if (roster_handler_)
+ roster_handler_->ContactChanged(this, old_contact,
+ std::distance(contacts_->begin(), pos));
+ delete old_contact;
+ }
+ } else { // Add a new contact
+ XmppRosterContactImpl* contact = new XmppRosterContactImpl();
+ contact->SetXmlFromWire(roster_item);
+ contacts_->push_back(contact);
+ if (roster_handler_ && !all_new)
+ roster_handler_->ContactsAdded(this, contacts_->size() - 1, 1);
+ }
+ }
+
+ // Send a consolidated update if all contacts are new
+ if (roster_handler_ && all_new)
+ roster_handler_->ContactsAdded(this, 0, contacts_->size());
+}
+
+}
diff --git a/talk/xmpp/rostermoduleimpl.h b/talk/xmpp/rostermoduleimpl.h
new file mode 100644
index 0000000..48ad5e5
--- /dev/null
+++ b/talk/xmpp/rostermoduleimpl.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 _rostermoduleimpl_h_
+#define _rostermoduleimpl_h_
+
+#include "talk/xmpp/moduleimpl.h"
+#include "talk/xmpp/rostermodule.h"
+
+namespace buzz {
+
+//! Presence Information
+//! This class stores both presence information for outgoing presence and is
+//! returned by methods in XmppRosterModule to represent recieved incoming
+//! presence information. When this class is writeable (non-const) then each
+//! update to any property will set the inner xml. Setting the raw_xml will
+//! rederive all of the other properties.
+class XmppPresenceImpl : public XmppPresence {
+public:
+ virtual ~XmppPresenceImpl() {}
+
+ //! The from Jid of for the presence information.
+ //! Typically this will be a full Jid with resource specified. For outgoing
+ //! presence this should remain JID_NULL and will be scrubbed from the
+ //! stanza when being sent.
+ virtual const Jid jid() const;
+
+ //! Is the contact available?
+ virtual XmppPresenceAvailable available() const;
+
+ //! Sets if the user is available or not
+ virtual XmppReturnStatus set_available(XmppPresenceAvailable available);
+
+ //! The show value of the presence info
+ virtual XmppPresenceShow presence_show() const;
+
+ //! Set the presence show value
+ virtual XmppReturnStatus set_presence_show(XmppPresenceShow show);
+
+ //! The Priority of the presence info
+ virtual int priority() const;
+
+ //! Set the priority of the presence
+ virtual XmppReturnStatus set_priority(int priority);
+
+ //! The plain text status of the presence info.
+ //! If there are multiple status because of language, this will either be a
+ //! status that is not tagged for language or the first available
+ virtual const std::string& status() const;
+
+ //! Sets the status for the presence info.
+ //! If there is more than one status present already then this will remove
+ //! them all and replace it with one status element we no specified language
+ virtual XmppReturnStatus set_status(const std::string& status);
+
+ //! The raw xml of the presence update
+ virtual const XmlElement* raw_xml() const;
+
+ //! Sets the raw presence stanza for the presence update
+ //! This will cause all other data items in this structure to be rederived
+ virtual XmppReturnStatus set_raw_xml(const XmlElement * xml);
+
+private:
+ XmppPresenceImpl();
+
+ friend class XmppPresence;
+ friend class XmppRosterModuleImpl;
+
+ void CreateRawXmlSkeleton();
+
+ // Store everything in the XML element. If this becomes a perf issue we can
+ // cache the data.
+ talk_base::scoped_ptr<XmlElement> raw_xml_;
+};
+
+//! A contact as given by the server
+class XmppRosterContactImpl : public XmppRosterContact {
+public:
+ virtual ~XmppRosterContactImpl() {}
+
+ //! The jid for the contact.
+ //! Typically this will be a bare Jid.
+ virtual const Jid jid() const;
+
+ //! Sets the jid for the roster contact update
+ virtual XmppReturnStatus set_jid(const Jid& jid);
+
+ //! The name (nickname) stored for this contact
+ virtual const std::string& name() const;
+
+ //! Sets the name
+ virtual XmppReturnStatus set_name(const std::string& name);
+
+ //! The Presence subscription state stored on the server for this contact
+ //! This is never settable and will be ignored when generating a roster
+ //! add/update request
+ virtual XmppSubscriptionState subscription_state() const;
+
+ //! The number of Groups applied to this contact
+ virtual size_t GetGroupCount() const;
+
+ //! Gets a Group applied to the contact based on index.
+ virtual const std::string& GetGroup(size_t index) const;
+
+ //! Adds a group to this contact.
+ //! This will return a no error if the group is already present.
+ virtual XmppReturnStatus AddGroup(const std::string& group);
+
+ //! Removes a group from the contact.
+ //! This will return no error if the group isn't there
+ virtual XmppReturnStatus RemoveGroup(const std::string& group);
+
+ //! The raw xml for this roster contact
+ virtual const XmlElement* raw_xml() const;
+
+ //! Sets the raw presence stanza for the presence update
+ //! This will cause all other data items in this structure to be rederived
+ virtual XmppReturnStatus set_raw_xml(const XmlElement * xml);
+
+private:
+ XmppRosterContactImpl();
+
+ void CreateRawXmlSkeleton();
+ void SetXmlFromWire(const XmlElement * xml);
+ void ResetGroupCache();
+
+ bool FindGroup(const std::string& group,
+ XmlElement** element,
+ XmlChild** child_before);
+
+
+ friend class XmppRosterContact;
+ friend class XmppRosterModuleImpl;
+
+ int group_count_;
+ int group_index_returned_;
+ XmlElement * group_returned_;
+ talk_base::scoped_ptr<XmlElement> raw_xml_;
+};
+
+//! An XmppModule for handle roster and presence functionality
+class XmppRosterModuleImpl : public XmppModuleImpl,
+ public XmppRosterModule, public XmppIqHandler {
+public:
+ virtual ~XmppRosterModuleImpl();
+
+ IMPLEMENT_XMPPMODULE
+
+ //! Sets the roster handler (callbacks) for the module
+ virtual XmppReturnStatus set_roster_handler(XmppRosterHandler * handler);
+
+ //! Gets the roster handler for the module
+ virtual XmppRosterHandler* roster_handler();
+
+ // USER PRESENCE STATE -------------------------------------------------------
+
+ //! Gets the aggregate outgoing presence
+ //! This object is non-const and be edited directly. No update is sent
+ //! to the server until a Broadcast is sent
+ virtual XmppPresence* outgoing_presence();
+
+ //! Broadcasts that the user is available.
+ //! Nothing with respect to presence is sent until this is called.
+ virtual XmppReturnStatus BroadcastPresence();
+
+ //! Sends a directed presence to a Jid
+ //! Note that the client doesn't store where directed presence notifications
+ //! have been sent. The server can keep the appropriate state
+ virtual XmppReturnStatus SendDirectedPresence(const XmppPresence* presence,
+ const Jid& to_jid);
+
+ // INCOMING PRESENCE STATUS --------------------------------------------------
+
+ //! Returns the number of incoming presence data recorded
+ virtual size_t GetIncomingPresenceCount();
+
+ //! Returns an incoming presence datum based on index
+ virtual const XmppPresence* GetIncomingPresence(size_t index);
+
+ //! Gets the number of presence data for a bare Jid
+ //! There may be a datum per resource
+ virtual size_t GetIncomingPresenceForJidCount(const Jid& jid);
+
+ //! Returns a single presence data for a Jid based on index
+ virtual const XmppPresence* GetIncomingPresenceForJid(const Jid& jid,
+ size_t index);
+
+ // ROSTER MANAGEMENT ---------------------------------------------------------
+
+ //! Requests an update of the roster from the server
+ //! This must be called to initialize the client side cache of the roster
+ //! After this is sent the server should keep this module apprised of any
+ //! changes.
+ virtual XmppReturnStatus RequestRosterUpdate();
+
+ //! Returns the number of contacts in the roster
+ virtual size_t GetRosterContactCount();
+
+ //! Returns a contact by index
+ virtual const XmppRosterContact* GetRosterContact(size_t index);
+
+ //! Finds a contact by Jid
+ virtual const XmppRosterContact* FindRosterContact(const Jid& jid);
+
+ //! Send a request to the server to add a contact
+ //! Note that the contact won't show up in the roster until the server can
+ //! respond. This happens async when the socket is being serviced
+ virtual XmppReturnStatus RequestRosterChange(
+ const XmppRosterContact* contact);
+
+ //! Request that the server remove a contact
+ //! The jabber protocol specifies that the server should also cancel any
+ //! subscriptions when this is done. Like adding, this contact won't be
+ //! removed until the server responds.
+ virtual XmppReturnStatus RequestRosterRemove(const Jid& jid);
+
+ // SUBSCRIPTION MANAGEMENT ---------------------------------------------------
+
+ //! Request a subscription to presence notifications form a Jid
+ virtual XmppReturnStatus RequestSubscription(const Jid& jid);
+
+ //! Cancel a subscription to presence notifications from a Jid
+ virtual XmppReturnStatus CancelSubscription(const Jid& jid);
+
+ //! Approve a request to deliver presence notifications to a jid
+ virtual XmppReturnStatus ApproveSubscriber(const Jid& jid);
+
+ //! Deny or cancel presence notification deliver to a jid
+ virtual XmppReturnStatus CancelSubscriber(const Jid& jid);
+
+ // XmppIqHandler IMPLEMENTATION ----------------------------------------------
+ virtual void IqResponse(XmppIqCookie cookie, const XmlElement * stanza);
+
+protected:
+ // XmppModuleImpl OVERRIDES --------------------------------------------------
+ virtual bool HandleStanza(const XmlElement *);
+
+ // PRIVATE DATA --------------------------------------------------------------
+private:
+ friend class XmppRosterModule;
+ XmppRosterModuleImpl();
+
+ // Helper functions
+ void DeleteIncomingPresence();
+ void DeleteContacts();
+ XmppReturnStatus SendSubscriptionRequest(const Jid& jid,
+ const std::string& type);
+ void InternalSubscriptionRequest(const Jid& jid, const XmlElement* stanza,
+ XmppSubscriptionRequestType request_type);
+ void InternalIncomingPresence(const Jid& jid, const XmlElement* stanza);
+ void InternalIncomingPresenceError(const Jid& jid, const XmlElement* stanza);
+ void InternalRosterItems(const XmlElement* stanza);
+
+ // Member data
+ XmppPresenceImpl outgoing_presence_;
+ XmppRosterHandler* roster_handler_;
+
+ typedef std::vector<XmppPresenceImpl*> PresenceVector;
+ typedef std::map<Jid, PresenceVector*> JidPresenceVectorMap;
+ talk_base::scoped_ptr<JidPresenceVectorMap> incoming_presence_map_;
+ talk_base::scoped_ptr<PresenceVector> incoming_presence_vector_;
+
+ typedef std::vector<XmppRosterContactImpl*> ContactVector;
+ talk_base::scoped_ptr<ContactVector> contacts_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslcookiemechanism.h b/talk/xmpp/saslcookiemechanism.h
index 92cff4d..fd65b94 100644
--- a/talk/xmpp/saslcookiemechanism.h
+++ b/talk/xmpp/saslcookiemechanism.h
@@ -56,15 +56,14 @@
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_);
+ el->AddAttr(QName("http://www.google.com/talk/protocol/auth", "service"),
+ token_service_);
}
std::string credential;
@@ -75,7 +74,7 @@
el->AddText(Base64Encode(credential));
return el;
}
-
+
private:
std::string mechanism_;
std::string username_;
diff --git a/talk/xmpp/util_unittest.cc b/talk/xmpp/util_unittest.cc
new file mode 100644
index 0000000..91ecb69
--- /dev/null
+++ b/talk/xmpp/util_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2004 Google, Inc. All Rights Reserved.
+// Author: Joe Beda
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/util_unittest.h"
+
+namespace buzz {
+
+void XmppTestHandler::WriteOutput(const char * bytes, size_t len) {
+ output_ << std::string(bytes, len);
+}
+
+void XmppTestHandler::StartTls(const std::string & cname) {
+ output_ << "[START-TLS " << cname << "]";
+}
+
+void XmppTestHandler::CloseConnection() {
+ output_ << "[CLOSED]";
+}
+
+void XmppTestHandler::OnStateChange(int state) {
+ switch (static_cast<XmppEngine::State>(state)) {
+ case XmppEngine::STATE_START:
+ session_ << "[START]";
+ break;
+ case XmppEngine::STATE_OPENING:
+ session_ << "[OPENING]";
+ break;
+ case XmppEngine::STATE_OPEN:
+ session_ << "[OPEN]";
+ break;
+ case XmppEngine::STATE_CLOSED:
+ session_ << "[CLOSED]";
+ switch (engine_->GetError(NULL)) {
+ case XmppEngine::ERROR_NONE:
+ // do nothing
+ break;
+ case XmppEngine::ERROR_XML:
+ session_ << "[ERROR-XML]";
+ break;
+ case XmppEngine::ERROR_STREAM:
+ session_ << "[ERROR-STREAM]";
+ break;
+ case XmppEngine::ERROR_VERSION:
+ session_ << "[ERROR-VERSION]";
+ break;
+ case XmppEngine::ERROR_UNAUTHORIZED:
+ session_ << "[ERROR-UNAUTHORIZED]";
+ break;
+ case XmppEngine::ERROR_TLS:
+ session_ << "[ERROR-TLS]";
+ break;
+ case XmppEngine::ERROR_AUTH:
+ session_ << "[ERROR-AUTH]";
+ break;
+ case XmppEngine::ERROR_BIND:
+ session_ << "[ERROR-BIND]";
+ break;
+ case XmppEngine::ERROR_CONNECTION_CLOSED:
+ session_ << "[ERROR-CONNECTION-CLOSED]";
+ break;
+ case XmppEngine::ERROR_DOCUMENT_CLOSED:
+ session_ << "[ERROR-DOCUMENT-CLOSED]";
+ break;
+ }
+ break;
+ }
+}
+
+bool XmppTestHandler::HandleStanza(const XmlElement * stanza) {
+ stanza_ << stanza->Str();
+ return true;
+}
+
+std::string XmppTestHandler::OutputActivity() {
+ std::string result = output_.str();
+ output_.str("");
+ return result;
+}
+
+std::string XmppTestHandler::SessionActivity() {
+ std::string result = session_.str();
+ session_.str("");
+ return result;
+}
+
+std::string XmppTestHandler::StanzaActivity() {
+ std::string result = stanza_.str();
+ stanza_.str("");
+ return result;
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/util_unittest.h b/talk/xmpp/util_unittest.h
new file mode 100644
index 0000000..bb0656c
--- /dev/null
+++ b/talk/xmpp/util_unittest.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 TALK_XMPP_UTIL_UNITTEST_H_
+#define TALK_XMPP_UTIL_UNITTEST_H_
+
+#include <string>
+#include <sstream>
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+// This class captures callbacks from engine.
+class XmppTestHandler : public XmppOutputHandler, public XmppSessionHandler,
+ public XmppStanzaHandler {
+ public:
+ explicit XmppTestHandler(XmppEngine* engine) : engine_(engine) {}
+ virtual ~XmppTestHandler() {}
+
+ void SetEngine(XmppEngine* engine);
+
+ // Output handler
+ virtual void WriteOutput(const char * bytes, size_t len);
+ virtual void StartTls(const std::string & cname);
+ virtual void CloseConnection();
+
+ // Session handler
+ virtual void OnStateChange(int state);
+
+ // Stanza handler
+ virtual bool HandleStanza(const XmlElement* stanza);
+
+ std::string OutputActivity();
+ std::string SessionActivity();
+ std::string StanzaActivity();
+
+ private:
+ XmppEngine* engine_;
+ std::stringstream output_;
+ std::stringstream session_;
+ std::stringstream stanza_;
+};
+
+} // namespace buzz
+
+inline std::ostream& operator<<(std::ostream& os, const buzz::Jid& jid) {
+ os << jid.Str();
+ return os;
+}
+
+#endif // TALK_XMPP_UTIL_UNITTEST_H_
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
index 3207ed5..563dc2a 100644
--- a/talk/xmpp/xmppclient.cc
+++ b/talk/xmpp/xmppclient.cc
@@ -27,11 +27,12 @@
#include "xmppclient.h"
#include "xmpptask.h"
-#include "talk/xmpp/constants.h"
#include "talk/base/sigslot.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/xmpp/constants.h"
#include "talk/xmpp/saslplainmechanism.h"
#include "talk/xmpp/prexmppauth.h"
-#include "talk/base/scoped_ptr.h"
#include "talk/xmpp/plainsaslhandler.h"
namespace buzz {
@@ -82,8 +83,16 @@
void OnSocketClosed();
};
+bool IsTestServer(const std::string& server_name,
+ const std::string& test_server_domain) {
+ return (!test_server_domain.empty() &&
+ talk_base::ends_with(server_name.c_str(),
+ test_server_domain.c_str()));
+}
+
XmppReturnStatus
-XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
+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)
@@ -101,7 +110,7 @@
if (!settings.resource().empty()) {
d_->engine_->SetRequestedResource(settings.resource());
}
- d_->engine_->SetUseTls(settings.use_tls());
+ d_->engine_->SetTls(settings.use_tls());
// The talk.google.com server returns a certificate with common-name:
// CN="gmail.com" for @gmail.com accounts,
@@ -116,7 +125,8 @@
if (server_name == buzz::STR_TALK_GOOGLE_COM ||
server_name == buzz::STR_TALKX_L_GOOGLE_COM ||
server_name == buzz::STR_XMPP_GOOGLE_COM ||
- server_name == buzz::STR_XMPPX_L_GOOGLE_COM) {
+ server_name == buzz::STR_XMPPX_L_GOOGLE_COM ||
+ IsTestServer(server_name, settings.test_server_domain())) {
if (settings.host() != STR_GMAIL_COM &&
settings.host() != STR_GOOGLEMAIL_COM) {
d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM);
diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h
index 4f821ab..29dfeec 100644
--- a/talk/xmpp/xmppclientsettings.h
+++ b/talk/xmpp/xmppclientsettings.h
@@ -30,13 +30,14 @@
#include "talk/p2p/base/port.h"
#include "talk/base/cryptstring.h"
+#include "talk/xmpp/xmppengine.h"
namespace buzz {
class XmppUserSettings {
public:
XmppUserSettings()
- : use_tls_(false),
+ : use_tls_(buzz::TLS_DISABLED),
allow_plain_(false) {
}
@@ -45,8 +46,11 @@
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_use_tls(const TlsOptions use_tls) { use_tls_ = use_tls; }
void set_allow_plain(bool f) { allow_plain_ = f; }
+ void set_test_server_domain(const std::string & test_server_domain) {
+ test_server_domain_ = test_server_domain;
+ }
void set_token_service(const std::string & token_service) {
token_service_ = token_service;
}
@@ -56,8 +60,9 @@
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_; }
+ TlsOptions use_tls() const { return use_tls_; }
bool allow_plain() const { return allow_plain_; }
+ const std::string & test_server_domain() const { return test_server_domain_; }
const std::string & token_service() const { return token_service_; }
private:
@@ -66,8 +71,9 @@
talk_base::CryptString pass_;
std::string auth_cookie_;
std::string resource_;
- bool use_tls_;
+ TlsOptions use_tls_;
bool allow_plain_;
+ std::string test_server_domain_;
std::string token_service_;
};
@@ -80,8 +86,8 @@
use_proxy_auth_(false) {
}
- void set_server(const talk_base::SocketAddress & server) {
- server_ = server;
+ 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; }
diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h
index 44ed83d..e1b35a3 100644
--- a/talk/xmpp/xmppengine.h
+++ b/talk/xmpp/xmppengine.h
@@ -77,6 +77,14 @@
XMPP_RETURN_NOTYETIMPLEMENTED,
};
+// TlsOptions
+// This is used by API to identify TLS setting.
+enum TlsOptions {
+ TLS_DISABLED,
+ TLS_ENABLED,
+ TLS_REQUIRED
+};
+
//! Callback for socket output for an XmppEngine connection.
//! Register via XmppEngine.SetOutputHandler. An XmppEngine
//! can call back to this handler while it is processing
@@ -197,7 +205,7 @@
virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
//! Sets whether TLS will be used within the connection (default true).
- virtual XmppReturnStatus SetUseTls(bool useTls) = 0;
+ virtual XmppReturnStatus SetTls(TlsOptions 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
@@ -207,7 +215,7 @@
const std::string & proxy_domain) = 0;
//! Gets whether TLS will be used within the connection.
- virtual bool GetUseTls() = 0;
+ virtual TlsOptions GetTls() = 0;
//! Sets the request resource name, if any (optional).
//! Note that the resource name may be overridden by the server; after
diff --git a/talk/xmpp/xmppengine_unittest.cc b/talk/xmpp/xmppengine_unittest.cc
new file mode 100644
index 0000000..46b79c6
--- /dev/null
+++ b/talk/xmpp/xmppengine_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+// Author: David Bau
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/util_unittest.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/plainsaslhandler.h"
+#include "talk/xmpp/xmppengine.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppIqCookie;
+using buzz::XmppIqHandler;
+using buzz::XmppTestHandler;
+using buzz::QN_ID;
+using buzz::QN_IQ;
+using buzz::QN_TYPE;
+using buzz::QN_ROSTER_QUERY;
+using buzz::XMPP_RETURN_OK;
+using buzz::XMPP_RETURN_BADARGUMENT;
+
+// XmppEngineTestIqHandler
+// This class grabs the response to an IQ stanza and stores it in a string.
+class XmppEngineTestIqHandler : public XmppIqHandler {
+ public:
+ virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) {
+ ss_ << stanza->Str();
+ }
+
+ std::string IqResponseActivity() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+ private:
+ std::stringstream ss_;
+};
+
+class XmppEngineTest : public testing::Test {
+ public:
+ XmppEngine* engine() { return engine_.get(); }
+ XmppTestHandler* handler() { return handler_.get(); }
+ virtual void SetUp() {
+ engine_.reset(XmppEngine::Create());
+ handler_.reset(new XmppTestHandler(engine_.get()));
+
+ Jid jid("david@my-server");
+ talk_base::InsecureCryptStringImpl pass;
+ pass.password() = "david";
+ engine_->SetSessionHandler(handler_.get());
+ engine_->SetOutputHandler(handler_.get());
+ engine_->AddStanzaHandler(handler_.get());
+ engine_->SetUser(jid);
+ engine_->SetSaslHandler(
+ new buzz::PlainSaslHandler(jid, talk_base::CryptString(pass), true));
+ }
+ virtual void TearDown() {
+ handler_.reset();
+ engine_.reset();
+ }
+ void RunLogin();
+
+ private:
+ talk_base::scoped_ptr<XmppEngine> engine_;
+ talk_base::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppEngineTest::RunLogin() {
+ // Connect
+ EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState());
+ engine()->Connect();
+ EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState());
+
+ EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ std::string input =
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input =
+ "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+ "<required/>"
+ "</starttls>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[START-TLS my-server]"
+ "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input =
+ "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" "
+ "auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+ "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>"
+ "david@my-server/test</jid></bind></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+ "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<iq type='result' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid());
+}
+
+// TestSuccessfulLogin()
+// This function simply tests to see if a login works. This includes
+// encryption and authentication
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) {
+ RunLogin();
+ engine()->Disconnect();
+ EXPECT_EQ("</stream:stream>[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) {
+ RunLogin();
+ engine()->ConnectionClosed(0);
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+
+// TestNotXmpp()
+// This tests the error case when connecting to a non XMPP service
+TEST_F(XmppEngineTest, TestNotXmpp) {
+ // Connect
+ engine()->Connect();
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler()->OutputActivity());
+
+ // Send garbage response (courtesy of apache)
+ std::string input = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestPassthrough()
+// This tests that arbitrary stanzas can be passed to the server through
+// the engine.
+TEST_F(XmppEngineTest, TestPassthrough) {
+ // Queue up an app stanza
+ XmlElement application_stanza(QName("test", "app-stanza"));
+ application_stanza.AddText("this-is-a-test");
+ engine()->SendStanza(&application_stanza);
+
+ // Do the whole login handshake
+ RunLogin();
+
+ EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+ "</test:app-stanza>", handler()->OutputActivity());
+
+ // do another stanza
+ XmlElement roster_get(QN_IQ);
+ roster_get.AddAttr(QN_TYPE, "get");
+ roster_get.AddAttr(QN_ID, engine()->NextId());
+ roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ engine()->SendStanza(&roster_get);
+ EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+
+ // now say the server ends the stream
+ engine()->HandleInput("</stream:stream>", 16);
+ EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestIqCallback()
+// This tests the routing of Iq stanzas and responses.
+TEST_F(XmppEngineTest, TestIqCallback) {
+ XmppEngineTestIqHandler iq_response;
+ XmppIqCookie cookie;
+
+ // Do the whole login handshake
+ RunLogin();
+
+ // Build an iq request
+ XmlElement roster_get(QN_IQ);
+ roster_get.AddAttr(QN_TYPE, "get");
+ roster_get.AddAttr(QN_ID, engine()->NextId());
+ roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ engine()->SendIq(&roster_get, &iq_response, &cookie);
+ EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+
+ // now say the server responds to the iq
+ std::string input = "<iq type='result' id='2'>"
+ "<query xmlns='jabber:iq:roster'><item>foo</item>"
+ "</query></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("<cli:iq type=\"result\" id=\"2\" xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:roster\"><item>foo</item></query>"
+ "</cli:iq>", iq_response.IqResponseActivity());
+
+ EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL));
+
+ // Do it again with another id to test cancel
+ roster_get.SetAttr(QN_ID, engine()->NextId());
+ engine()->SendIq(&roster_get, &iq_response, &cookie);
+ EXPECT_EQ("<iq type=\"get\" id=\"3\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+
+ // cancel the handler this time
+ EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL));
+
+ // now say the server responds to the iq: the iq handler should not get it.
+ input = "<iq type='result' id='3'><query xmlns='jabber:iq:roster'><item>bar"
+ "</item></query></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<cli:iq type=\"result\" id=\"3\" xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:roster\"><item>bar</item></query>"
+ "</cli:iq>", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+}
diff --git a/talk/xmpp/xmppengineimpl.cc b/talk/xmpp/xmppengineimpl.cc
index 262c531..9e21249 100644
--- a/talk/xmpp/xmppengineimpl.cc
+++ b/talk/xmpp/xmppengineimpl.cc
@@ -2,89 +2,84 @@
* libjingle
* Copyright 2004--2005, Google Inc.
*
- * Redistribution and use in source and binary forms, with or without
+ * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
- * 1. Redistributions of source code must retain the above copyright notice,
+ * 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
+ * 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (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 <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/constants.h"
#include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/xmpplogintask.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() {
+XmppEngine* XmppEngine::Create() {
return new XmppEngineImpl();
}
-XmppEngineImpl::XmppEngineImpl() :
- stanzaParseHandler_(this),
- stanzaParser_(&stanzaParseHandler_),
- engine_entered_(0),
- user_jid_(JID_EMPTY),
- password_(),
- requested_resource_(STR_EMPTY),
- tls_needed_(true),
- login_task_(new XmppLoginTask(this)),
- next_id_(0),
- bound_jid_(JID_EMPTY),
- state_(STATE_START),
- encrypted_(false),
- error_code_(ERROR_NONE),
- subcode_(0),
- stream_error_(NULL),
- raised_reset_(false),
- output_handler_(NULL),
- session_handler_(NULL),
- iq_entries_(new IqEntryVector()),
- sasl_handler_(NULL),
- output_(new std::stringstream()) {
+XmppEngineImpl::XmppEngineImpl()
+ : stanza_parse_handler_(this),
+ stanza_parser_(&stanza_parse_handler_),
+ engine_entered_(0),
+ password_(),
+ requested_resource_(STR_EMPTY),
+ tls_option_(buzz::TLS_REQUIRED),
+ login_task_(new XmppLoginTask(this)),
+ next_id_(0),
+ state_(STATE_START),
+ encrypted_(false),
+ error_code_(ERROR_NONE),
+ subcode_(0),
+ stream_error_(NULL),
+ raised_reset_(false),
+ output_handler_(NULL),
+ session_handler_(NULL),
+ iq_entries_(new IqEntryVector()),
+ sasl_handler_(NULL),
+ output_(new std::stringstream()) {
for (int i = 0; i < HL_COUNT; i+= 1) {
stanza_handlers_[i].reset(new StanzaHandlerVector());
}
+
+ // Add XMPP namespaces to XML namespaces stack.
+ xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
+ xmlns_stack_.AddXmlns("", "jabber:client");
}
XmppEngineImpl::~XmppEngineImpl() {
DeleteIqCookies();
}
-XmppReturnStatus
-XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) {
+XmppReturnStatus XmppEngineImpl::SetOutputHandler(
+ XmppOutputHandler* output_handler) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -93,8 +88,8 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) {
+XmppReturnStatus XmppEngineImpl::SetSessionHandler(
+ XmppSessionHandler* session_handler) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -103,21 +98,20 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::HandleInput(const char * bytes, size_t len) {
+XmppReturnStatus XmppEngineImpl::HandleInput(
+ const char* bytes, size_t len) {
if (state_ < STATE_OPENING || state_ > STATE_OPEN)
return XMPP_RETURN_BADSTATE;
EnterExit ee(this);
// TODO: The return value of the xml parser is not checked.
- stanzaParser_.Parse(bytes, len, false);
+ stanza_parser_.Parse(bytes, len, false);
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::ConnectionClosed(int subcode) {
+XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
if (state_ != STATE_CLOSED) {
EnterExit ee(this);
// If told that connection closed and not already closed,
@@ -131,19 +125,16 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SetUseTls(bool useTls) {
+XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
-
- tls_needed_ = useTls;
-
+ tls_option_ = use_tls;
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SetTlsServer(const std::string & tls_server_hostname,
- const std::string & tls_server_domain) {
+XmppReturnStatus XmppEngineImpl::SetTlsServer(
+ const std::string& tls_server_hostname,
+ const std::string& tls_server_domain) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -153,13 +144,11 @@
return XMPP_RETURN_OK;
}
-bool
-XmppEngineImpl::GetUseTls() {
- return tls_needed_;
+TlsOptions XmppEngineImpl::GetTls() {
+ return tls_option_;
}
-XmppReturnStatus
-XmppEngineImpl::SetUser(const Jid & jid) {
+XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -168,13 +157,11 @@
return XMPP_RETURN_OK;
}
-const Jid &
-XmppEngineImpl::GetUser() {
+const Jid& XmppEngineImpl::GetUser() {
return user_jid_;
}
-XmppReturnStatus
-XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) {
+XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -182,8 +169,8 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SetRequestedResource(const std::string & resource) {
+XmppReturnStatus XmppEngineImpl::SetRequestedResource(
+ const std::string& resource) {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -192,14 +179,13 @@
return XMPP_RETURN_OK;
}
-const std::string &
-XmppEngineImpl::GetRequestedResource() {
+const std::string& XmppEngineImpl::GetRequestedResource() {
return requested_resource_;
}
-XmppReturnStatus
-XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler,
- XmppEngine::HandlerLevel level) {
+XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
+ XmppStanzaHandler* stanza_handler,
+ XmppEngine::HandlerLevel level) {
if (state_ == STATE_CLOSED)
return XMPP_RETURN_BADSTATE;
@@ -208,9 +194,8 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) {
-
+XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
+ XmppStanzaHandler* stanza_handler) {
bool found = false;
for (int level = 0; level < HL_COUNT; level += 1) {
@@ -225,15 +210,13 @@
}
}
- if (!found) {
+ if (!found)
return XMPP_RETURN_BADARGUMENT;
- }
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::Connect() {
+XmppReturnStatus XmppEngineImpl::Connect() {
if (state_ != STATE_START)
return XMPP_RETURN_BADSTATE;
@@ -250,8 +233,7 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SendStanza(const XmlElement * element) {
+XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
if (state_ == STATE_CLOSED)
return XMPP_RETURN_BADSTATE;
@@ -268,8 +250,7 @@
return XMPP_RETURN_OK;
}
-XmppReturnStatus
-XmppEngineImpl::SendRaw(const std::string & text) {
+XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
if (state_ == STATE_CLOSED || login_task_.get())
return XMPP_RETURN_BADSTATE;
@@ -280,16 +261,13 @@
return XMPP_RETURN_OK;
}
-std::string
-XmppEngineImpl::NextId() {
+std::string XmppEngineImpl::NextId() {
std::stringstream ss;
ss << next_id_++;
return ss.str();
}
-XmppReturnStatus
-XmppEngineImpl::Disconnect() {
-
+XmppReturnStatus XmppEngineImpl::Disconnect() {
if (state_ != STATE_CLOSED) {
EnterExit ee(this);
if (state_ == STATE_OPEN)
@@ -300,14 +278,13 @@
return XMPP_RETURN_OK;
}
-void
-XmppEngineImpl::IncomingStart(const XmlElement * pelStart) {
+void XmppEngineImpl::IncomingStart(const XmlElement* start) {
if (HasError() || raised_reset_)
return;
if (login_task_.get()) {
// start-stream should go to login task
- login_task_->IncomingStanza(pelStart, true);
+ login_task_->IncomingStanza(start, true);
if (login_task_->IsDone())
login_task_.reset();
}
@@ -317,8 +294,7 @@
}
}
-void
-XmppEngineImpl::IncomingStanza(const XmlElement * stanza) {
+void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
if (HasError() || raised_reset_)
return;
@@ -342,7 +318,7 @@
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;
+ return;
}
}
@@ -350,29 +326,24 @@
// 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 &&
+ 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) {
+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) {
+void XmppEngineImpl::InternalSendStart(const std::string& to) {
std::string hostname = tls_server_hostname_;
- if (hostname.empty()) {
+ if (hostname.empty())
hostname = to;
- }
// If not language is specified, the spec says use *
std::string lang = lang_;
@@ -389,61 +360,56 @@
<< "xmlns=\"jabber:client\">\r\n";
}
-void
-XmppEngineImpl::InternalSendStanza(const XmlElement * element) {
+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);
+ XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
}
-std::string
-XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+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) {
+SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
return sasl_handler_->CreateSaslMechanism(name);
}
-void
-XmppEngineImpl::SignalBound(const Jid & fullJid) {
+void XmppEngineImpl::SignalBound(const Jid& fullJid) {
if (state_ == STATE_OPENING) {
bound_jid_ = fullJid;
state_ = STATE_OPEN;
}
}
-void
-XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) {
+void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
if (state_ != STATE_CLOSED) {
- stream_error_.reset(new XmlElement(*pelStreamError));
+ stream_error_.reset(new XmlElement(*stream_error));
SignalError(ERROR_STREAM, 0);
}
}
-void
-XmppEngineImpl::SignalError(Error errorCode, int subCode) {
+void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
if (state_ != STATE_CLOSED) {
- error_code_ = errorCode;
- subcode_ = subCode;
+ error_code_ = error_code;
+ subcode_ = sub_code;
state_ = STATE_CLOSED;
}
}
-bool
-XmppEngineImpl::HasError() {
+bool XmppEngineImpl::HasError() {
return error_code_ != ERROR_NONE;
}
-void
-XmppEngineImpl::StartTls(const std::string & domain) {
+void XmppEngineImpl::StartTls(const std::string& domain) {
if (output_handler_) {
+ // As substitute for the real (login jid's) domain, we permit
+ // verifying a tls_server_domain_ instead, if one was passed.
+ // This allows us to avoid running a proxy that needs to handle
+ // valuable certificates.
output_handler_->StartTls(
tls_server_domain_.empty() ? domain : tls_server_domain_);
encrypted_ = true;
@@ -451,7 +417,7 @@
}
XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
- : engine_(engine),
+ : engine_(engine),
state_(engine->state_),
error_(engine->error_code_) {
engine->engine_entered_ += 1;
@@ -482,17 +448,17 @@
return;
if (engine->raised_reset_) {
- engine->stanzaParser_.Reset();
+ engine->stanza_parser_.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.
+ // Note: Handling of OnStateChange(CLOSED) should allow for the
+ // deletion of the engine, so no members should be accessed
+ // after this line.
}
}
-}
+} // namespace buzz
diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h
index e5d3dc6..e292e75 100644
--- a/talk/xmpp/xmppengineimpl.h
+++ b/talk/xmpp/xmppengineimpl.h
@@ -25,8 +25,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _xmppengineimpl_h_
-#define _xmppengineimpl_h_
+#ifndef TALK_XMPP_XMPPENGINEIMPL_H_
+#define TALK_XMPP_XMPPENGINEIMPL_H_
#include <sstream>
#include <vector>
@@ -41,7 +41,6 @@
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
@@ -51,7 +50,7 @@
//! An application can listen for events and receive stanzas by
//! registering an XmppStanzaHandler via AddStanzaHandler().
class XmppEngineImpl : public XmppEngine {
-public:
+ public:
XmppEngineImpl();
virtual ~XmppEngineImpl();
@@ -61,7 +60,7 @@
virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
//! Provides socket input to the engine
- virtual XmppReturnStatus HandleInput(const char * bytes, size_t len);
+ virtual XmppReturnStatus HandleInput(const char* bytes, size_t len);
//! Advises the engine that the socket has closed
virtual XmppReturnStatus ConnectionClosed(int subcode);
@@ -69,26 +68,26 @@
// SESSION SETUP ---------------------------------------------------------
//! Indicates the (bare) JID for the user to use.
- virtual XmppReturnStatus SetUser(const Jid & jid);
+ virtual XmppReturnStatus SetUser(const Jid& jid);
//! Get the login (bare) JID.
- virtual const Jid & GetUser();
+ virtual const Jid& GetUser();
//! Indicates the autentication to use. Takes ownership of the object.
- virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler);
+ virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler);
//! Sets whether TLS will be used within the connection (default true).
- virtual XmppReturnStatus SetUseTls(bool useTls);
+ virtual XmppReturnStatus SetTls(TlsOptions use_tls);
//! 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);
+ 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();
+ virtual TlsOptions GetTls();
//! Sets the request resource name, if any (optional).
//! Note that the resource name may be overridden by the server; after
@@ -96,10 +95,10 @@
virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
//! Gets the request resource name.
- virtual const std::string & GetRequestedResource();
+ virtual const std::string& GetRequestedResource();
//! Sets language
- virtual void SetLanguage(const std::string & lang) {
+ virtual void SetLanguage(const std::string& lang) {
lang_ = lang;
}
@@ -131,7 +130,7 @@
//! 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(); }
+ virtual const XmlElement* GetStreamError() { return stream_error_.get(); }
//! Closes down the connection.
//! Sends CloseConnection to output, and disconnects and registered
@@ -151,14 +150,14 @@
virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
//! Sends a stanza to the server.
- virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza);
+ virtual XmppReturnStatus SendStanza(const XmlElement* stanza);
//! Sends raw text to the server
- virtual XmppReturnStatus SendRaw(const std::string & text);
+ 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,
+ virtual XmppReturnStatus SendIq(const XmlElement* stanza,
XmppIqHandler* iq_handler,
XmppIqCookie* cookie);
@@ -170,55 +169,62 @@
//! 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,
+ virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal,
XmppStanzaError code,
- const std::string & text);
+ 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_; }
+ 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:
+ private:
friend class XmppLoginTask;
friend class XmppIqEntry;
- void IncomingStanza(const XmlElement *pelStanza);
- void IncomingStart(const XmlElement *pelStanza);
+ void IncomingStanza(const XmlElement *stanza);
+ void IncomingStart(const XmlElement *stanza);
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 InternalSendStart(const std::string& domainName);
+ void InternalSendStanza(const XmlElement* stanza);
+ 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* streamError);
void SignalError(Error errorCode, int subCode);
bool HasError();
void DeleteIqCookies();
- bool HandleIqResponse(const XmlElement * element);
- void StartTls(const std::string & domain);
+ 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) {}
+ public:
+ StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {}
virtual ~StanzaParseHandler() {}
- virtual void StartStream(const XmlElement * pelStream)
- { outer_->IncomingStart(pelStream); }
- virtual void Stanza(const XmlElement * pelStanza)
- { outer_->IncomingStanza(pelStanza); }
- virtual void EndStream()
- { outer_->IncomingEnd(false); }
- virtual void XmlError()
- { outer_->IncomingEnd(true); }
- private:
- XmppEngineImpl * const outer_;
+
+ virtual void StartStream(const XmlElement* stream) {
+ outer_->IncomingStart(stream);
+ }
+ virtual void Stanza(const XmlElement* stanza) {
+ outer_->IncomingStanza(stanza);
+ }
+ virtual void EndStream() {
+ outer_->IncomingEnd(false);
+ }
+ virtual void XmlError() {
+ outer_->IncomingEnd(true);
+ }
+
+ private:
+ XmppEngineImpl* const outer_;
};
class EnterExit {
@@ -235,16 +241,15 @@
friend class StanzaParseHandler;
friend class EnterExit;
- StanzaParseHandler stanzaParseHandler_;
- XmppStanzaParser stanzaParser_;
-
+ StanzaParseHandler stanza_parse_handler_;
+ XmppStanzaParser stanza_parser_;
// state
int engine_entered_;
Jid user_jid_;
std::string password_;
std::string requested_resource_;
- bool tls_needed_;
+ TlsOptions tls_option_;
std::string tls_server_hostname_;
std::string tls_server_domain_;
talk_base::scoped_ptr<XmppLoginTask> login_task_;
@@ -261,6 +266,8 @@
XmppOutputHandler* output_handler_;
XmppSessionHandler* session_handler_;
+ XmlnsStack xmlns_stack_;
+
typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
@@ -272,7 +279,6 @@
talk_base::scoped_ptr<std::stringstream> output_;
};
-}
+} // namespace buzz
-
-#endif
+#endif // TALK_XMPP_XMPPENGINEIMPL_H_
diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc
index 340d95d..726802d 100644
--- a/talk/xmpp/xmpplogintask.cc
+++ b/talk/xmpp/xmpplogintask.cc
@@ -25,8 +25,11 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/xmpp/xmpplogintask.h"
+
#include <string>
#include <vector>
+
#include "talk/base/base64.h"
#include "talk/base/common.h"
#include "talk/xmllite/xmlelement.h"
@@ -34,7 +37,6 @@
#include "talk/xmpp/jid.h"
#include "talk/xmpp/saslmechanism.h"
#include "talk/xmpp/xmppengineimpl.h"
-#include "talk/xmpp/xmpplogintask.h"
using talk_base::ConstantLabel;
@@ -56,10 +58,10 @@
LASTLABEL
};
#endif // _DEBUG
-
XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
pctx_(pctx),
authNeeded_(true),
+ allowNonGoogleLogin_(true),
state_(LOGINSTATE_INIT),
pelStanza_(NULL),
isStart_(false),
@@ -138,8 +140,14 @@
if (!HandleFeatures(element))
return Failure(XmppEngine::ERROR_VERSION);
- // Use TLS if forced, or if available
- if (pctx_->tls_needed_ || GetFeature(QN_TLS_STARTTLS) != NULL) {
+ bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
+ // Error if TLS required but not present.
+ if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
+ return Failure(XmppEngine::ERROR_TLS);
+ }
+ // Use TLS if required or enabled, and also available
+ if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
+ pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
state_ = LOGINSTATE_TLS_INIT;
continue;
}
@@ -176,7 +184,7 @@
// 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;
+ pctx_->tls_option_ = buzz::TLS_ENABLED;
state_ = LOGINSTATE_INIT;
continue;
}
@@ -214,6 +222,16 @@
if (auth == NULL) {
return Failure(XmppEngine::ERROR_AUTH);
}
+ if (allowNonGoogleLogin_) {
+ // Setting the following two attributes is required to support
+ // non-google ids.
+
+ // Allow login with non-google id accounts.
+ auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
+
+ // Allow login with either the non-google id or the friendly email.
+ auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
+ }
pctx_->InternalSendStanza(auth);
delete auth;
diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h
index a6507b5..9b3f5ae 100644
--- a/talk/xmpp/xmpplogintask.h
+++ b/talk/xmpp/xmpplogintask.h
@@ -25,10 +25,12 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _logintask_h_
-#define _logintask_h_
+#ifndef TALK_XMPP_LOGINTASK_H_
+#define TALK_XMPP_LOGINTASK_H_
#include <string>
+#include <vector>
+
#include "talk/base/logging.h"
#include "talk/base/scoped_ptr.h"
#include "talk/xmpp/jid.h"
@@ -41,6 +43,7 @@
class SaslMechanism;
+// TODO: Rename to LoginTask.
class XmppLoginTask {
public:
@@ -51,6 +54,8 @@
{ return state_ == LOGINSTATE_DONE; }
void IncomingStanza(const XmlElement * element, bool isStart);
void OutgoingStanza(const XmlElement *element);
+ void set_allow_non_google_login(bool b)
+ { allowNonGoogleLogin_ = b; }
private:
enum LoginTaskState {
@@ -77,6 +82,7 @@
XmppEngineImpl * pctx_;
bool authNeeded_;
+ bool allowNonGoogleLogin_;
LoginTaskState state_;
const XmlElement * pelStanza_;
bool isStart_;
@@ -95,4 +101,4 @@
}
-#endif
+#endif // TALK_XMPP_LOGINTASK_H_
diff --git a/talk/xmpp/xmpplogintask_unittest.cc b/talk/xmpp/xmpplogintask_unittest.cc
new file mode 100644
index 0000000..51af81a
--- /dev/null
+++ b/talk/xmpp/xmpplogintask_unittest.cc
@@ -0,0 +1,614 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/util_unittest.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/plainsaslhandler.h"
+#include "talk/xmpp/xmppengine.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppTestHandler;
+
+enum XlttStage {
+ XLTT_STAGE_CONNECT = 0,
+ XLTT_STAGE_STREAMSTART,
+ XLTT_STAGE_TLS_FEATURES,
+ XLTT_STAGE_TLS_PROCEED,
+ XLTT_STAGE_ENCRYPTED_START,
+ XLTT_STAGE_AUTH_FEATURES,
+ XLTT_STAGE_AUTH_SUCCESS,
+ XLTT_STAGE_AUTHENTICATED_START,
+ XLTT_STAGE_BIND_FEATURES,
+ XLTT_STAGE_BIND_SUCCESS,
+ XLTT_STAGE_SESSION_SUCCESS,
+};
+
+class XmppLoginTaskTest : public testing::Test {
+ public:
+ XmppEngine* engine() { return engine_.get(); }
+ XmppTestHandler* handler() { return handler_.get(); }
+ virtual void SetUp() {
+ engine_.reset(XmppEngine::Create());
+ handler_.reset(new XmppTestHandler(engine_.get()));
+
+ Jid jid("david@my-server");
+ talk_base::InsecureCryptStringImpl pass;
+ pass.password() = "david";
+ engine_->SetSessionHandler(handler_.get());
+ engine_->SetOutputHandler(handler_.get());
+ engine_->AddStanzaHandler(handler_.get());
+ engine_->SetUser(jid);
+ engine_->SetSaslHandler(
+ new buzz::PlainSaslHandler(jid, talk_base::CryptString(pass), true));
+ }
+ virtual void TearDown() {
+ handler_.reset();
+ engine_.reset();
+ }
+ void RunPartialLogin(XlttStage startstage, XlttStage endstage);
+ void SetTlsOptions(buzz::TlsOptions option);
+
+ private:
+ talk_base::scoped_ptr<XmppEngine> engine_;
+ talk_base::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppLoginTaskTest::SetTlsOptions(buzz::TlsOptions option) {
+ engine_->SetTls(option);
+}
+void XmppLoginTaskTest::RunPartialLogin(XlttStage startstage,
+ XlttStage endstage) {
+ std::string input;
+
+ switch (startstage) {
+ case XLTT_STAGE_CONNECT: {
+ engine_->Connect();
+ XmlElement appStanza(QName("test", "app-stanza"));
+ appStanza.AddText("this-is-a-test");
+ engine_->SendStanza(&appStanza);
+
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ if (endstage == XLTT_STAGE_CONNECT)
+ return;
+ }
+
+ case XLTT_STAGE_STREAMSTART: {
+ input = "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_STREAMSTART)
+ return;
+ }
+
+ case XLTT_STAGE_TLS_FEATURES: {
+ input = "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_TLS_FEATURES)
+ return;
+ }
+
+ case XLTT_STAGE_TLS_PROCEED: {
+ input = std::string("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[START-TLS my-server]"
+ "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_TLS_PROCEED)
+ return;
+ }
+
+ case XLTT_STAGE_ENCRYPTED_START: {
+ input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_ENCRYPTED_START)
+ return;
+ }
+
+ case XLTT_STAGE_AUTH_FEATURES: {
+ input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" "
+ "auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_AUTH_FEATURES)
+ return;
+ }
+
+ case XLTT_STAGE_AUTH_SUCCESS: {
+ input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_AUTH_SUCCESS)
+ return;
+ }
+
+ case XLTT_STAGE_AUTHENTICATED_START: {
+ input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_AUTHENTICATED_START)
+ return;
+ }
+
+ case XLTT_STAGE_BIND_FEATURES: {
+ input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+ "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_BIND_FEATURES)
+ return;
+ }
+
+ case XLTT_STAGE_BIND_SUCCESS: {
+ input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+ "<jid>david@my-server/test</jid></bind></iq>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+ "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_BIND_SUCCESS)
+ return;
+ }
+
+ case XLTT_STAGE_SESSION_SUCCESS: {
+ input = "<iq type='result' id='1'/>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+ "</test:app-stanza>", handler_->OutputActivity());
+ EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ if (endstage == XLTT_STAGE_SESSION_SUCCESS)
+ return;
+ }
+ }
+}
+
+TEST_F(XmppLoginTaskTest, TestUtf8Good) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+ std::string input = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNonUtf8Bad) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+ std::string input = "<?xml version='1.0' encoding='ISO-8859-1'?>"
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNoFeatures) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<iq type='get' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequiredNotPresent) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequeiredAndPresent) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+ "<required/>"
+ "</starttls>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledNotPresent) {
+ SetTlsOptions(buzz::TLS_ENABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledAndPresent) {
+ SetTlsOptions(buzz::TLS_ENABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledNotPresent) {
+ SetTlsOptions(buzz::TLS_DISABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledAndPresent) {
+ SetTlsOptions(buzz::TLS_DISABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsFailure) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_FEATURES);
+
+ std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsBadStream) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_PROCEED);
+
+ std::string input = "<wrongtag>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSaslPlain) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_ENCRYPTED_START);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-AUTH]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestWrongPassword) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_FEATURES);
+
+ std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-UNAUTHORIZED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestAuthBadStream) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_SUCCESS);
+
+ std::string input = "<wrongtag>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingBindFeature) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+ std::string input = "<stream:features>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSessionFeature) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+ std::string input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+/* TODO: Handle this case properly inside XmppLoginTask.
+TEST_F(XmppLoginTaskTest, TestBindFailure1) {
+ // check wrong JID
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+ "<jid>davey@my-server/test</jid></bind></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+*/
+
+TEST_F(XmppLoginTaskTest, TestBindFailure2) {
+ // check missing JID
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure3) {
+ // check plain failure
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='error' id='0'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure4) {
+ // check wrong id to ignore
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='error' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ // continue after an ignored iq
+ RunPartialLogin(XLTT_STAGE_BIND_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain1) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+ std::string input = "<iq type='error' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain2) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+ // check reverse iq to ignore
+ // TODO: consider queueing or passing through?
+ std::string input = "<iq type='get' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+
+ // continue after an ignored iq
+ RunPartialLogin(XLTT_STAGE_SESSION_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestBadXml) {
+ int errorKind = 0;
+ for (XlttStage stage = XLTT_STAGE_CONNECT;
+ stage <= XLTT_STAGE_SESSION_SUCCESS;
+ stage = static_cast<XlttStage>(stage + 1)) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+ std::string input;
+ switch (errorKind++ % 5) {
+ case 0: input = "&syntax;"; break;
+ case 1: input = "<nons:iq/>"; break;
+ case 2: input = "<iq a='b' a='dupe'/>"; break;
+ case 3: input = "<>"; break;
+ case 4: input = "<iq a='<wrong>'>"; break;
+ }
+
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+
+ TearDown();
+ SetUp();
+ }
+}
+
+TEST_F(XmppLoginTaskTest, TestStreamError) {
+ for (XlttStage stage = XLTT_STAGE_CONNECT;
+ stage <= XLTT_STAGE_SESSION_SUCCESS;
+ stage = static_cast<XlttStage>(stage + 1)) {
+ switch (stage) {
+ case XLTT_STAGE_CONNECT:
+ case XLTT_STAGE_TLS_PROCEED:
+ case XLTT_STAGE_AUTH_SUCCESS:
+ continue;
+ default:
+ break;
+ }
+
+ RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+ std::string input = "<stream:error>"
+ "<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+ "<text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>"
+ "Some special application diagnostic information!"
+ "</text>"
+ "<escape-your-data xmlns='application-ns'/>"
+ "</stream:error>";
+
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-STREAM]", handler()->SessionActivity());
+
+ EXPECT_EQ("<str:error xmlns:str=\"http://etherx.jabber.org/streams\">"
+ "<xml-not-well-formed xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/>"
+ "<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\">"
+ "Some special application diagnostic information!"
+ "</text>"
+ "<escape-your-data xmlns=\"application-ns\"/>"
+ "</str:error>", engine()->GetStreamError()->Str());
+
+ TearDown();
+ SetUp();
+ }
+}
+
diff --git a/talk/xmpp/xmppstanzaparser_unittest.cc b/talk/xmpp/xmppstanzaparser_unittest.cc
new file mode 100644
index 0000000..06faf87
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2004 Google Inc. All Rights Reserved
+
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppStanzaParser;
+using buzz::XmppStanzaParseHandler;
+
+class XmppStanzaParserTestHandler : public XmppStanzaParseHandler {
+ public:
+ virtual void StartStream(const XmlElement * element) {
+ ss_ << "START" << element->Str();
+ }
+ virtual void Stanza(const XmlElement * element) {
+ ss_ << "STANZA" << element->Str();
+ }
+ virtual void EndStream() {
+ ss_ << "END";
+ }
+ virtual void XmlError() {
+ ss_ << "ERROR";
+ }
+
+ std::string Str() {
+ return ss_.str();
+ }
+
+ std::string StrClear() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+ private:
+ std::stringstream ss_;
+};
+
+
+TEST(XmppStanzaParserTest, TestTrivial) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<trivial/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<trivial/>END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestStanzaAtATime) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "<message type='foo'><body>hello</body></message>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>", handler.StrClear());
+
+ fragment = " SOME TEXT TO IGNORE ";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "<iq type='set' id='123'><abc xmlns='def'/></iq>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:iq type=\"set\" id=\"123\" xmlns:c=\"j:c\">"
+ "<abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+ fragment = "</stream:stream>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestFragmentedStanzas) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "lo</body></message> IGNORE ME <iq type='set' id='123'>"
+ "<abc xmlns='def'/></iq></st";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>STANZA<c:iq type=\"set\" id=\"123\" "
+ "xmlns:c=\"j:c\"><abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+ fragment = "ream:stream>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestReset) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ parser.Reset();
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+ parser.Reset();
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "<message type='foo'><body>hello</body></message>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestError) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<-foobar/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+
+ parser.Reset();
+ fragment = "<stream:stream/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+ parser.Reset();
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+ parser.Reset();
+
+ fragment = "<stream:stream xmlns:stream='st' xmlns='jc'>"
+ "<foo/><bar><st:foobar/></bar>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream xmlns:stream=\"st\" xmlns=\"jc\"/>STANZA"
+ "<jc:foo xmlns:jc=\"jc\"/>ERROR", handler.StrClear());
+}
diff --git a/talk/xmpp/xmpptask.cc b/talk/xmpp/xmpptask.cc
index 60841d4..046f7a1 100644
--- a/talk/xmpp/xmpptask.cc
+++ b/talk/xmpp/xmpptask.cc
@@ -29,12 +29,9 @@
#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;
-
XmppClientInterface::XmppClientInterface() {
}
@@ -115,7 +112,7 @@
XmlElement* result = new XmlElement(QN_IQ);
if (!type.empty())
result->AddAttr(QN_TYPE, type);
- if (to != JID_EMPTY)
+ if (!to.IsEmpty())
result->AddAttr(QN_TO, to.Str());
if (!id.empty())
result->AddAttr(QN_ID, id);
@@ -151,7 +148,7 @@
return true;
// We address the server as "", check if we are doing so here.
- if (to != JID_EMPTY)
+ if (!to.IsEmpty())
return false;
// It is legal for the server to identify itself with "domain" or
@@ -175,10 +172,4 @@
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);
-}
-
}