Update to libjingle 0.6.8.
git-svn-id: http://libjingle.googlecode.com/svn/trunk@103 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index 15ac417..fd9bd69 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
Libjingle
+0.6.8 - Dec 22, 2011
+ -
+
0.6.7 - Dec 21, 2011
- Release new PeerConnection implementation to app/webrtc.
- Bug fixes.
diff --git a/talk/app/webrtc/mediastream.h b/talk/app/webrtc/mediastream.h
index 4bb0b6c..5db591a 100644
--- a/talk/app/webrtc/mediastream.h
+++ b/talk/app/webrtc/mediastream.h
@@ -51,7 +51,6 @@
namespace webrtc {
class AudioDeviceModule;
-class VideoCaptureModule;
// Generic observer interface.
class ObserverInterface {
diff --git a/talk/app/webrtc/mediastreamtrackproxy.cc b/talk/app/webrtc/mediastreamtrackproxy.cc
index afb222e..15b1d6d 100644
--- a/talk/app/webrtc/mediastreamtrackproxy.cc
+++ b/talk/app/webrtc/mediastreamtrackproxy.cc
@@ -293,7 +293,7 @@
}
VideoTrackProxy::VideoTrackProxy(const std::string& label,
- VideoCaptureModule* video_device,
+ cricket::VideoCapturer* video_device,
talk_base::Thread* signaling_thread)
: MediaStreamTrackProxy<LocalVideoTrackInterface>(signaling_thread),
video_track_(VideoTrack::CreateLocal(label, video_device)) {
@@ -318,7 +318,7 @@
talk_base::scoped_refptr<LocalVideoTrackInterface> VideoTrackProxy::CreateLocal(
const std::string& label,
- VideoCaptureModule* video_device,
+ cricket::VideoCapturer* video_device,
talk_base::Thread* signaling_thread) {
ASSERT(signaling_thread != NULL);
talk_base::RefCountedObject<VideoTrackProxy>* track =
diff --git a/talk/app/webrtc/mediastreamtrackproxy.h b/talk/app/webrtc/mediastreamtrackproxy.h
index 000f81e..590d6ab 100644
--- a/talk/app/webrtc/mediastreamtrackproxy.h
+++ b/talk/app/webrtc/mediastreamtrackproxy.h
@@ -119,7 +119,7 @@
talk_base::Thread* signaling_thread);
static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
const std::string& label,
- VideoCaptureModule* video_device,
+ cricket::VideoCapturer* video_device,
talk_base::Thread* signaling_thread);
static talk_base::scoped_refptr<LocalVideoTrackInterface> CreateLocal(
LocalVideoTrackInterface* implementation,
@@ -133,7 +133,7 @@
VideoTrackProxy(const std::string& label,
talk_base::Thread* signaling_thread);
VideoTrackProxy(const std::string& label,
- VideoCaptureModule* video_device,
+ cricket::VideoCapturer* video_device,
talk_base::Thread* signaling_thread);
VideoTrackProxy(LocalVideoTrackInterface* implementation,
talk_base::Thread* signaling_thread);
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 4e2ad0a..6a9b18c 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -84,6 +84,8 @@
}
namespace webrtc {
+class VideoCaptureModule;
+
// MediaStream container interface.
class StreamCollectionInterface : public talk_base::RefCountInterface {
public:
@@ -187,6 +189,12 @@
~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 {
@@ -238,7 +246,7 @@
virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
CreateLocalVideoTrack(const std::string& label,
- VideoCaptureModule* video_device) = 0;
+ cricket::VideoCapturer* video_device) = 0;
virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
CreateLocalAudioTrack(const std::string& label,
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
index 6e61dc2..8a264c4 100644
--- a/talk/app/webrtc/peerconnection_unittest.cc
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -224,8 +224,9 @@
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", fake_video_capture_module_);
+ "video_track", CreateVideoCapturer(fake_video_capture_module_));
}
int id_;
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.cc b/talk/app/webrtc/peerconnectionfactoryimpl.cc
index 4275a5f..e74e16e 100644
--- a/talk/app/webrtc/peerconnectionfactoryimpl.cc
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.cc
@@ -220,7 +220,7 @@
scoped_refptr<LocalVideoTrackInterface>
PeerConnectionFactory::CreateLocalVideoTrack(
const std::string& label,
- VideoCaptureModule* video_device) {
+ cricket::VideoCapturer* video_device) {
return VideoTrackProxy::CreateLocal(label, video_device,
signaling_thread_);
}
diff --git a/talk/app/webrtc/peerconnectionfactoryimpl.h b/talk/app/webrtc/peerconnectionfactoryimpl.h
index 9a64f58..ba22d47 100644
--- a/talk/app/webrtc/peerconnectionfactoryimpl.h
+++ b/talk/app/webrtc/peerconnectionfactoryimpl.h
@@ -50,7 +50,7 @@
virtual talk_base::scoped_refptr<LocalVideoTrackInterface>
CreateLocalVideoTrack(const std::string& label,
- VideoCaptureModule* video_device);
+ cricket::VideoCapturer* video_device);
virtual talk_base::scoped_refptr<LocalAudioTrackInterface>
CreateLocalAudioTrack(const std::string& label,
diff --git a/talk/app/webrtc/peerconnectionimpl.cc b/talk/app/webrtc/peerconnectionimpl.cc
index d5607e1..fdc2659 100644
--- a/talk/app/webrtc/peerconnectionimpl.cc
+++ b/talk/app/webrtc/peerconnectionimpl.cc
@@ -34,6 +34,7 @@
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/webrtcvideocapturer.h"
namespace {
@@ -158,6 +159,16 @@
namespace webrtc {
+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;
+}
+
PeerConnection::PeerConnection(PeerConnectionFactory* factory)
: factory_(factory),
observer_(NULL),
diff --git a/talk/app/webrtc/videotrackimpl.cc b/talk/app/webrtc/videotrackimpl.cc
index 87f797b..0fb218a 100644
--- a/talk/app/webrtc/videotrackimpl.cc
+++ b/talk/app/webrtc/videotrackimpl.cc
@@ -40,13 +40,10 @@
}
VideoTrack::VideoTrack(const std::string& label,
- VideoCaptureModule* video_device)
+ cricket::VideoCapturer* video_device)
: MediaStreamTrack<LocalVideoTrackInterface>(label),
video_device_(NULL) {
- cricket::WebRtcVideoCapturer* video_device_impl =
- new cricket::WebRtcVideoCapturer;
- video_device_impl->Init(video_device);
- video_device_.reset(video_device_impl);
+ video_device_.reset(video_device);
}
void VideoTrack::SetRenderer(VideoRendererWrapperInterface* renderer) {
@@ -76,7 +73,7 @@
talk_base::scoped_refptr<VideoTrack> VideoTrack::CreateLocal(
const std::string& label,
- VideoCaptureModule* video_device) {
+ cricket::VideoCapturer* video_device) {
talk_base::RefCountedObject<VideoTrack>* track =
new talk_base::RefCountedObject<VideoTrack>(label, video_device);
return track;
diff --git a/talk/app/webrtc/videotrackimpl.h b/talk/app/webrtc/videotrackimpl.h
index a8de715..de99e6a 100644
--- a/talk/app/webrtc/videotrackimpl.h
+++ b/talk/app/webrtc/videotrackimpl.h
@@ -58,7 +58,7 @@
// Create a video track used for local video tracks.
static talk_base::scoped_refptr<VideoTrack> CreateLocal(
const std::string& label,
- VideoCaptureModule* video_device);
+ cricket::VideoCapturer* video_device);
virtual cricket::VideoCapturer* GetVideoCapture();
virtual void SetRenderer(VideoRendererWrapperInterface* renderer);
@@ -68,7 +68,7 @@
protected:
explicit VideoTrack(const std::string& label);
- VideoTrack(const std::string& label, VideoCaptureModule* video_device);
+ VideoTrack(const std::string& label, cricket::VideoCapturer* video_device);
private:
talk_base::scoped_ptr<cricket::VideoCapturer> video_device_;
diff --git a/talk/app/webrtcv1/webrtcsession_unittest.cc b/talk/app/webrtcv1/webrtcsession_unittest.cc
index 2c23d6a..c37b0d4 100644
--- a/talk/app/webrtcv1/webrtcsession_unittest.cc
+++ b/talk/app/webrtcv1/webrtcsession_unittest.cc
@@ -29,11 +29,11 @@
#include <list>
-#include "base/gunit.h"
-#include "base/helpers.h"
#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"
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/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/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/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/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_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_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.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/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.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_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..147e8e9
--- /dev/null
+++ b/talk/base/macwindowpicker.cc
@@ -0,0 +1,220 @@
+// 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(WindowId id) {
+ // Init if we're not already inited.
+ if (get_window_list_desc_ == NULL && !Init()) {
+ return false;
+ }
+ CGWindowID ids[1];
+ ids[0] = 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(WindowId id) {
+ // TODO: Implement this method.
+ // Init if we're not already inited.
+ if (get_window_list_desc_ == NULL && !Init()) {
+ return false;
+ }
+ CGWindowID ids[1];
+ ids[0] = 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::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) {
+ WindowDescription desc(id_val, 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..8fff8a0
--- /dev/null
+++ b/talk/base/macwindowpicker.h
@@ -0,0 +1,28 @@
+// 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(WindowId id);
+ virtual bool MoveToFront(WindowId id);
+ virtual bool GetWindowList(WindowDescriptionList* 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/natserver.cc b/talk/base/natserver.cc
index 06493ad..7107f77 100644
--- a/talk/base/natserver.cc
+++ b/talk/base/natserver.cc
@@ -25,6 +25,7 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "talk/base/natsocketfactory.h"
#include "talk/base/natserver.h"
#include "talk/base/logging.h"
@@ -60,7 +61,7 @@
size_t AddrCmp::operator()(const SocketAddress& a) const {
size_t h = 0;
if (use_ip)
- h ^= a.ip();
+ h ^= HashIP(a.ipaddr());
if (use_port)
h ^= a.port() | (a.port() << 16);
return h;
@@ -68,9 +69,9 @@
bool AddrCmp::operator()(
const SocketAddress& a1, const SocketAddress& a2) const {
- if (use_ip && (a1.ip() < a2.ip()))
+ if (use_ip && (a1.ipaddr() < a2.ipaddr()))
return true;
- if (use_ip && (a2.ip() < a1.ip()))
+ if (use_ip && (a2.ipaddr() < a1.ipaddr()))
return false;
if (use_port && (a1.port() < a2.port()))
return true;
@@ -82,7 +83,7 @@
NATServer::NATServer(
NATType type, SocketFactory* internal, const SocketAddress& internal_addr,
SocketFactory* external, const SocketAddress& external_ip)
- : external_(external), external_ip_(external_ip.ip(), 0) {
+ : external_(external), external_ip_(external_ip.ipaddr(), 0) {
nat_ = NAT::Create(type);
server_socket_ = AsyncUDPSocket::Create(internal, internal_addr);
@@ -110,7 +111,7 @@
// Read the intended destination from the wire.
SocketAddress dest_addr;
- dest_addr.Read_(buf, size);
+ int length = UnpackAddressFromNAT(buf, size, &dest_addr);
// Find the translation for these addresses (allocating one if necessary).
SocketAddressPair route(addr, dest_addr);
@@ -125,8 +126,7 @@
iter->second->whitelist->insert(dest_addr);
// Send the packet to its intended destination.
- iter->second->socket->SendTo(
- buf + dest_addr.Size_(), size - dest_addr.Size_(), dest_addr);
+ iter->second->socket->SendTo(buf + length, size - length, dest_addr);
}
void NATServer::OnExternalPacket(
@@ -147,16 +147,15 @@
}
// Forward this packet to the internal address.
-
- size_t real_size = size + remote_addr.Size_();
- char* real_buf = new char[real_size];
-
- remote_addr.Write_(real_buf, real_size);
- std::memcpy(real_buf + remote_addr.Size_(), buf, size);
-
- server_socket_->SendTo(real_buf, real_size, iter->second->route.source());
-
- delete[] real_buf;
+ // 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) {
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
index 846d0e6..de2cf2b 100644
--- a/talk/base/natsocketfactory.cc
+++ b/talk/base/natsocketfactory.cc
@@ -33,6 +33,54 @@
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:
@@ -95,43 +143,44 @@
return result;
}
- virtual int Send(const void *pv, size_t cb) {
+ virtual int Send(const void* data, size_t size) {
ASSERT(connected_);
- return SendTo(pv, cb, remote_addr_);
+ return SendTo(data, size, remote_addr_);
}
- virtual int SendTo(const void *pv, size_t cb, const SocketAddress& 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(pv, cb, addr);
+ return socket_->SendTo(data, size, addr);
}
-
- size_t size = cb + addr.Size_();
- scoped_array<char> buf(new char[size]);
- Encode(static_cast<const char*>(pv), cb, buf.get(), size, addr);
-
- int result = socket_->SendTo(buf.get(), size, server_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>(size));
- result = result - static_cast<int>(addr.Size_());
+ ASSERT(result == static_cast<int>(encoded_size));
+ result = result - static_cast<int>(addrlength);
}
return result;
}
- virtual int Recv(void *pv, size_t cb) {
+ virtual int Recv(void* data, size_t size) {
SocketAddress addr;
- return RecvFrom(pv, cb, &addr);
+ return RecvFrom(data, size, &addr);
}
- virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+ virtual int RecvFrom(void* data, size_t size, SocketAddress *out_addr) {
if (server_addr_.IsAny() || type_ == SOCK_STREAM) {
- return socket_->RecvFrom(pv, cb, paddr);
+ return socket_->RecvFrom(data, size, out_addr);
}
-
// Make sure we have enough room to read the requested amount plus the
- // header address.
+ // largest possible header address.
SocketAddress remote_addr;
- Grow(cb + remote_addr.Size_());
+ Grow(size + kNATEncodedIPv6AddressSize);
// Read the packet from the socket.
int result = socket_->RecvFrom(buf_, size_, &remote_addr);
@@ -145,14 +194,15 @@
// Decode the wire packet into the actual results.
SocketAddress real_remote_addr;
- size_t real_size = cb;
- Decode(buf_, result, pv, &real_size, &real_remote_addr);
+ 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 (paddr)
- *paddr = real_remote_addr;
- result = real_size;
+ 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();
@@ -243,8 +293,8 @@
// Sends the destination address to the server to tell it to connect.
void SendConnectRequest() {
char buf[256];
- remote_addr_.Write_(buf, ARRAY_SIZE(buf));
- socket_->Send(buf, remote_addr_.Size_());
+ 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.
@@ -259,26 +309,6 @@
}
}
- // Encodes the given data and intended remote address into a packet to send
- // to the NAT server.
- static void Encode(const char* data, size_t data_size, char* buf,
- size_t buf_size, const SocketAddress& remote_addr) {
- ASSERT(buf_size == data_size + remote_addr.Size_());
- remote_addr.Write_(buf, static_cast<int>(buf_size));
- std::memcpy(buf + remote_addr.Size_(), data, data_size);
- }
-
- // Decodes the given packet from the NAT server into the actual remote
- // address and data.
- static void Decode(const char* data, size_t data_size, void* buf,
- size_t* buf_size, SocketAddress* remote_addr) {
- ASSERT(data_size >= remote_addr->Size_());
- ASSERT(data_size <= *buf_size + remote_addr->Size_());
- remote_addr->Read_(data, static_cast<int>(data_size));
- *buf_size = data_size - remote_addr->Size_();
- std::memcpy(buf, data + remote_addr->Size_(), *buf_size);
- }
-
NATInternalSocketFactory* sf_;
int type_;
bool async_;
diff --git a/talk/base/natsocketfactory.h b/talk/base/natsocketfactory.h
index bbaaf3e..d0566d3 100644
--- a/talk/base/natsocketfactory.h
+++ b/talk/base/natsocketfactory.h
@@ -38,6 +38,9 @@
namespace talk_base {
+const size_t kNATEncodedIPv4AddressSize = 8U;
+const size_t kNATEncodedIPv6AddressSize = 20U;
+
// Used by the NAT socket implementation.
class NATInternalSocketFactory {
public:
@@ -112,7 +115,7 @@
Translator* GetTranslator(const SocketAddress& ext_ip);
Translator* AddTranslator(const SocketAddress& ext_ip,
- const SocketAddress& int_ip, NATType type);
+ const SocketAddress& int_ip, NATType type);
void RemoveTranslator(const SocketAddress& ext_ip);
bool AddClient(const SocketAddress& int_ip);
@@ -164,6 +167,11 @@
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/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/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/socketaddress.cc b/talk/base/socketaddress.cc
index 42143f5..c338944 100644
--- a/talk/base/socketaddress.cc
+++ b/talk/base/socketaddress.cc
@@ -299,45 +299,6 @@
return h;
}
-size_t SocketAddress::Size_() const {
- return ip_.Size() + sizeof(port_) + 2;
-}
-
-bool SocketAddress::Write_(char* buf, int len) const {
- if (len < static_cast<int>(Size_()))
- return false;
- int family = ip_.family();
- if (family != AF_INET && family != AF_INET6) {
- return false;
- }
- buf[0] = 0;
- SetBE16(buf + 2, port_);
- if (family == AF_INET) {
- buf[1] = kStunFamilyIPv4;
- SetBE32(buf + 4, ip_.v4AddressAsHostOrderInteger());
- } else if (family == AF_INET6) {
- buf[1] = kStunFamilyIPv6;
- in6_addr addr = ip_.ipv6_address();
- memcpy((buf + 4), &addr.s6_addr, sizeof(addr.s6_addr));
- }
- return true;
-}
-
-bool SocketAddress::Read_(const char* buf, int len) {
- if (len < static_cast<int>(Size_()) ||
- (buf[1] != kStunFamilyIPv4 && buf[1] != kStunFamilyIPv6))
- return false;
- port_ = GetBE16(buf + 2);
- if (buf[1] == kStunFamilyIPv4) {
- ip_ = IPAddress(GetBE32(buf + 4));
- } else if (buf[1] == kStunFamilyIPv6) {
- in6_addr addr;
- memcpy(&addr.s6_addr, (buf + 4), sizeof(addr.s6_addr));
- ip_ = IPAddress(addr);
- }
- return true;
-}
-
void SocketAddress::ToSockAddr(sockaddr_in* saddr) const {
memset(saddr, 0, sizeof(*saddr));
if (ip_.family() != AF_INET) {
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
index 3ebf556..7ddd21f 100644
--- a/talk/base/socketaddress.h
+++ b/talk/base/socketaddress.h
@@ -170,16 +170,6 @@
// Hashes this address into a small number.
size_t Hash() const;
- // Returns the size of this address when written (for STUN).
- // TODO: Move STUN functions( Size_/Write_/Read_) out of this class.
- size_t Size_() const;
-
- // Writes this address into the given buffer, according to RFC 5389.
- bool Write_(char* buf, int len) const;
-
- // Reads this address from the given buffer, according to RFC 5389.
- 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;
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
index fcfd89c..6e2ef7f 100644
--- a/talk/base/socketaddress_unittest.cc
+++ b/talk/base/socketaddress_unittest.cc
@@ -232,32 +232,6 @@
EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, NULL));
}
-TEST(SocketAddressTest, TestIPv4ToFromBuffer) {
- SocketAddress from("1.2.3.4", 5678), addr;
- char buf[20];
- EXPECT_TRUE(from.Write_(buf, sizeof(buf)));
- EXPECT_TRUE(addr.Read_(buf, sizeof(buf)));
- EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(AF_INET, addr.ipaddr().family());
- 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, TestIPv6ToFromBuffer) {
- SocketAddress from6(kTestV6AddrString, 5678), addr;
- char buf[20];
- EXPECT_TRUE(from6.Write_(buf, sizeof(buf)));
- EXPECT_TRUE(addr.Read_(buf, sizeof(buf)));
- EXPECT_FALSE(addr.IsUnresolvedIP());
- EXPECT_EQ(AF_INET6, addr.ipaddr().family());
- EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
- EXPECT_EQ(5678, addr.port());
- EXPECT_EQ("", addr.hostname());
- EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
-}
-
TEST(SocketAddressTest, TestGoodResolve) {
SocketAddress addr("localhost", 5678);
int error;
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/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/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_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_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_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..308fff5
--- /dev/null
+++ b/talk/base/win32windowpicker.cc
@@ -0,0 +1,78 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+
+#include "talk/base/win32windowpicker.h"
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+// Window class names that we want to filter out.
+static const std::string kProgramManagerClass = "Progman";
+static const std::string kButtonClass = "Button";
+
+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));
+ WindowDescription desc(reinterpret_cast<int>(hwnd), title);
+ descriptions->push_back(desc);
+ return TRUE;
+}
+
+Win32WindowPicker::Win32WindowPicker() {
+}
+
+bool Win32WindowPicker::Init() {
+ return true;
+}
+
+bool Win32WindowPicker::GetWindowList(WindowDescriptionList* descriptions) {
+ return EnumWindows(Win32WindowPicker::EnumProc,
+ (LPARAM)descriptions) == TRUE ? true : false;
+}
+
+bool Win32WindowPicker::IsVisible(WindowId id) {
+ HWND hwnd = reinterpret_cast<HWND>(id);
+ return (::IsWindow(hwnd) != FALSE && ::IsWindowVisible(hwnd) != FALSE);
+}
+
+bool Win32WindowPicker::MoveToFront(WindowId id) {
+ return SetForegroundWindow(reinterpret_cast<HWND>(id)) != FALSE;
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32windowpicker.h b/talk/base/win32windowpicker.h
new file mode 100644
index 0000000..d799037
--- /dev/null
+++ b/talk/base/win32windowpicker.h
@@ -0,0 +1,26 @@
+// 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(WindowId id);
+ virtual bool MoveToFront(WindowId id);
+ virtual bool GetWindowList(WindowDescriptionList* descriptions);
+
+ protected:
+ static BOOL CALLBACK EnumProc(HWND hwnd, 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..d6ee553
--- /dev/null
+++ b/talk/base/win32windowpicker_unittest.cc
@@ -0,0 +1,99 @@
+// 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(reinterpret_cast<WindowId>(
+ window_picker.visible_window()->handle()), desc.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;
+ WindowId visible_id =
+ reinterpret_cast<WindowId>(window_picker.visible_window()->handle());
+ WindowId invisible_id =
+ reinterpret_cast<WindowId>(window_picker.invisible_window()->handle());
+ EXPECT_TRUE(window_picker.IsVisible(visible_id));
+ EXPECT_FALSE(window_picker.IsVisible(invisible_id));
+}
+
+TEST(Win32WindowPickerTest, TestMoveToFront) {
+ Win32WindowPickerForTest window_picker;
+ WindowId visible_id =
+ reinterpret_cast<WindowId>(window_picker.visible_window()->handle());
+ WindowId invisible_id =
+ reinterpret_cast<WindowId>(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(visible_id);
+ window_picker.MoveToFront(invisible_id);
+}
+
+} // namespace talk_base
diff --git a/talk/base/windowpicker.h b/talk/base/windowpicker.h
new file mode 100644
index 0000000..2de7041
--- /dev/null
+++ b/talk/base/windowpicker.h
@@ -0,0 +1,52 @@
+// Copyright 2010 Google Inc. All Rights Reserved
+
+// thorcarpenter@google.com (Thor Carpenter)
+
+#ifndef TALK_BASE_WINDOWPICKER_H_
+#define TALK_BASE_WINDOWPICKER_H_
+
+#include <list>
+#include <string>
+
+#include "talk/base/window.h"
+
+namespace talk_base {
+
+class WindowDescription {
+ public:
+ WindowDescription() : id_(kInvalidWindowId) {}
+ WindowDescription(WindowId id, const std::string& title)
+ : id_(id), title_(title) {
+ }
+ WindowId id() const {
+ return id_;
+ }
+ const std::string& title() const {
+ return title_;
+ }
+
+ private:
+ WindowId id_;
+ std::string title_;
+};
+
+typedef std::list<WindowDescription> WindowDescriptionList;
+
+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(WindowId id) = 0;
+ virtual bool MoveToFront(WindowId id) = 0;
+
+ // Gets a list of window description.
+ // Returns true if successful.
+ virtual bool GetWindowList(WindowDescriptionList* descriptions) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WINDOWPICKER_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/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 b427783..ad18fa1 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"
@@ -132,7 +131,6 @@
" 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"
@@ -267,8 +265,6 @@
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);
}
@@ -621,10 +617,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();
@@ -769,31 +761,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);
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 3ba9994..825fd7d 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;
@@ -194,8 +193,6 @@
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,
cricket::Session* session,
const cricket::MediaSources& sources);
@@ -225,7 +222,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..e16ad95
--- /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());
+ delete client;
+}
+
+TEST(CallClientTest, CreateCallClientWithFileMediaEngine) {
+ XmppPump pump;
+ CallClient *client = new CallClient(pump.client());
+ client->SetMediaEngine(new cricket::FileMediaEngine);
+ delete client;
+}
diff --git a/talk/examples/login/autoportallocator.h b/talk/examples/login/autoportallocator.h
new file mode 100644
index 0000000..f1993ca
--- /dev/null
+++ b/talk/examples/login/autoportallocator.h
@@ -0,0 +1,70 @@
+/*
+ * 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 sigslot::has_slots<> {
+ 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/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..b417544
--- /dev/null
+++ b/talk/examples/peerconnection/client/linux/main_wnd.cc
@@ -0,0 +1,481 @@
+/*
+ * 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;
+ 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..4ae0da0
--- /dev/null
+++ b/talk/examples/peerconnection/client/main.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 <windows.h>
+
+#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 "system_wrappers/source/trace_impl.h"
+#include "talk/base/win32socketinit.h"
+
+
+int PASCAL wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
+ wchar_t* cmd_line, int cmd_show) {
+ talk_base::EnsureWinsockInit();
+
+ webrtc::Trace::CreateTrace();
+ webrtc::Trace::SetTraceFile("peerconnection_client.log");
+ webrtc::Trace::SetLevelFilter(webrtc::kTraceWarning);
+
+ 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)) && 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)) && 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..e9f79e4
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -0,0 +1,599 @@
+/*
+ * 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"
+
+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) {
+ remote_video_.reset();
+ local_video_.reset();
+
+ 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);
+}
+
+cricket::VideoRenderer* MainWnd::local_renderer() {
+ if (!local_video_.get())
+ local_video_.reset(new VideoRenderer(handle(), 1, 1));
+ return local_video_.get();
+}
+
+cricket::VideoRenderer* MainWnd::remote_renderer() {
+ if (!remote_video_.get())
+ remote_video_.reset(new VideoRenderer(handle(), 1, 1));
+ return remote_video_.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);
+
+ if (ui_ == STREAMING && remote_video_.get() && local_video_.get()) {
+ AutoLock<VideoRenderer> local_lock(local_video_.get());
+ AutoLock<VideoRenderer> remote_lock(remote_video_.get());
+
+ const BITMAPINFO& bmi = remote_video_->bmi();
+ int height = abs(bmi.bmiHeader.biHeight);
+ int width = bmi.bmiHeader.biWidth;
+
+ const uint8* image = remote_video_->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_video_->bmi();
+ image = local_video_->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_video_->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 >> 3));
+ }
+
+ 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..1807f4e
--- /dev/null
+++ b/talk/examples/peerconnection/client/main_wnd.h
@@ -0,0 +1,208 @@
+/*
+ * 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 cricket::VideoRenderer* local_renderer();
+ virtual cricket::VideoRenderer* 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_ptr<VideoRenderer> remote_video_;
+ talk_base::scoped_ptr<VideoRenderer> local_video_;
+ 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..b156dba
--- /dev/null
+++ b/talk/examples/peerconnection/server/server_test.html
@@ -0,0 +1,228 @@
+<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)
+ 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..034a23c
--- /dev/null
+++ b/talk/examples/plus/presencepushtask.cc
@@ -0,0 +1,203 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 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);
+ }
+
+ if (node == GOOGLE_CLIENT_NODE) {
+ s.set_is_google_client(true);
+ 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 8e40bbd..1904550 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -74,6 +74,7 @@
lin_srcs = [
"base/latebindingsymboltable.cc",
"base/linux.cc",
+ "base/libdbusglibsymboltable.cc",
"session/phone/libudevsymboltable.cc",
"session/phone/linuxdevicemanager.cc",
"session/phone/v4llookup.cc",
@@ -96,9 +97,13 @@
},
mac_srcs = [
"base/macasyncsocket.cc",
+ "base/maccocoasocketserver.mm",
+ "base/maccocoathreadhelper.mm",
"base/macconversion.cc",
"base/macsocketserver.cc",
"base/macutils.cc",
+ "base/macwindowpicker.cc",
+ "base/scoped_autorelease_pool.mm",
"session/phone/carbonvideorenderer.cc",
"session/phone/macdevicemanager.cc",
"session/phone/macdevicemanagermm.mm",
@@ -162,6 +167,7 @@
"base/optionsfile.cc",
"base/pathutils.cc",
"base/physicalsocketserver.cc",
+ "base/posix.cc",
"base/proxydetect.cc",
"base/proxyinfo.cc",
"base/proxyserver.cc",
@@ -187,6 +193,7 @@
"base/thread.cc",
"base/timeutils.cc",
"base/timing.cc",
+ "base/transformadapter.cc",
"base/urlencode.cc",
"base/versionparsing.cc",
"base/virtualsocketserver.cc",
@@ -215,6 +222,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",
@@ -242,6 +250,7 @@
"session/phone/soundclip.cc",
"session/phone/srtpfilter.cc",
"session/phone/ssrcmuxfilter.cc",
+ "session/phone/videoadapter.cc",
"session/phone/videocapturer.cc",
"session/phone/videocommon.cc",
"session/phone/videoframe.cc",
@@ -262,9 +271,12 @@
"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/receivetask.cc",
"xmpp/saslmechanism.cc",
@@ -283,13 +295,16 @@
"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",
@@ -299,7 +314,11 @@
"-Wno-deprecated-declarations",
],
extra_srcs = [
+ "base/dbus.cc",
"base/json.cc",
+ "base/natserver_main.cc",
+ "xmpp/chatroommoduleimpl.cc",
+ "xmpp/rostermoduleimpl.cc",
],
)
talk.Library(env, name = "xmpphelp",
@@ -307,6 +326,7 @@
"jingle",
],
srcs = [
+ "examples/login/jingleinfotask.cc",
"examples/login/xmppauth.cc",
"examples/login/xmpppump.cc",
"examples/login/xmppsocket.cc",
@@ -379,9 +399,6 @@
"crypto",
"ssl",
],
- cppdefines = [
- "FEATURE_ENABLE_VOICEMAIL",
- ],
lin_libs = [
"videorenderer",
],
@@ -389,14 +406,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",
@@ -422,14 +437,30 @@
],
)
talk.Unittest(env, name = "base",
- libs = [
- "jingle",
+ lin_srcs = [
+ "base/latebindingsymboltable_unittest.cc",
+ "base/linux_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",
@@ -450,6 +481,7 @@
"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",
@@ -477,14 +509,20 @@
"third_party/srtp",
"third_party/gtest",
],
- posix_srcs = [
- "base/sslidentity_unittest.cc",
- "base/sslstreamadapter_unittest.cc",
+ 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",
],
- cppdefines = [
- "LIBJINGLE_UNITTEST",
- "EXPAT_RELATIVE_PATH",
- "SRTP_RELATIVE_PATH",
+ libs = [
+ "jingle",
+ ],
+ extra_srcs = [
+ "base/json_unittest.cc",
],
)
talk.Unittest(env, name = "p2p",
@@ -514,6 +552,7 @@
"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 = [
@@ -624,9 +663,14 @@
],
)
talk.Unittest(env, name = "xmpp",
- libs = [
- "jingle",
- "expat",
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
],
srcs = [
"xmpp/hangoutpubsubclient_unittest.cc",
@@ -640,19 +684,17 @@
"xmpp/xmpplogintask_unittest.cc",
"xmpp/xmppstanzaparser_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",
+ libs = [
+ "jingle",
+ "expat",
+ ],
+ extra_srcs = [
+ "xmpp/chatroommodule_unittest.cc",
],
)
diff --git a/talk/p2p/client/connectivitychecker.cc b/talk/p2p/client/connectivitychecker.cc
new file mode 100644
index 0000000..a5a36cc
--- /dev/null
+++ b/talk/p2p/client/connectivitychecker.cc
@@ -0,0 +1,511 @@
+// 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);
+ SignalRequestDone(request);
+
+ // For the https response, pass the result back to the port
+ // allocator to generate a config with relay addresses we can
+ // ping. For http, we are only interested in that we got a
+ // response.
+ if (request->port() == talk_base::HTTP_SECURE_PORT) {
+ 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());
+ 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 allocateRelayPorts = 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);
+ allocateRelayPorts = true;
+ }
+ }
+
+ // If any new ip/proxy combinations were added, send a relay allocate.
+ if (allocateRelayPorts) {
+ 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/session/phone/fakenetworkinterface.h b/talk/session/phone/fakenetworkinterface.h
new file mode 100644
index 0000000..09c3bf7
--- /dev/null
+++ b/talk/session/phone/fakenetworkinterface.h
@@ -0,0 +1,165 @@
+/*
+ * 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();
+ }
+
+ const talk_base::Buffer* GetRtpPacket(int index) {
+ talk_base::CritScope cs(&crit_);
+ return (index < NumRtpPackets()) ? &rtp_packets_[index] : NULL;
+ }
+
+ int NumRtcpPackets() {
+ talk_base::CritScope cs(&crit_);
+ return rtcp_packets_.size();
+ }
+
+ const talk_base::Buffer* GetRtcpPacket(int index) {
+ talk_base::CritScope cs(&crit_);
+ return (index < NumRtcpPackets()) ? &rtcp_packets_[index] : NULL;
+ }
+
+ 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/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..1ade630
--- /dev/null
+++ b/talk/session/phone/fakewebrtccommon.h
@@ -0,0 +1,52 @@
+/*
+ * 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_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/fakewebrtcvideoengine.h b/talk/session/phone/fakewebrtcvideoengine.h
new file mode 100644
index 0000000..6269b71
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvideoengine.h
@@ -0,0 +1,588 @@
+/*
+ * 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),
+ nack_(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 nack_;
+ 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 GetNackStatus(int channel) const {
+ WEBRTC_ASSERT_CHANNEL(channel);
+ return channels_.find(channel)->second->nack_;
+ }
+
+ 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 {
+ 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 = static_cast<webrtc::ViEExternalCapture *>(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(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(GetRTCPCName, (const int channel,
+ char rtcp_cname[KMaxRTCPCNameLength])) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ talk_base::strcpyn(rtcp_cname, KMaxRTCPCNameLength,
+ channels_[channel]->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;
+ return 0;
+ }
+ WEBRTC_STUB(SetFECStatus, (const int, const bool, const unsigned char,
+ const unsigned char));
+ WEBRTC_STUB(SetHybridNACKFECStatus, (const int, const bool,
+ const unsigned char, const unsigned char));
+ WEBRTC_FUNC(SetKeyFrameRequestMethod,
+ (const int channel,
+ const webrtc::ViEKeyFrameRequestMethod method)) {
+ WEBRTC_CHECK_CHANNEL(channel);
+ channels_[channel]->key_frame_request_method_ = method;
+ return 0;
+ }
+ 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(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..5b3ff0c
--- /dev/null
+++ b/talk/session/phone/fakewebrtcvoiceengine.h
@@ -0,0 +1,852 @@
+/*
+ * 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::kPlaybackAllChannelsMixed;
+ 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(GetJitterStatistics, (int, webrtc::JitterStatistics&));
+ 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/videoadapter.cc b/talk/session/phone/videoadapter.cc
new file mode 100644
index 0000000..1cb1cc4
--- /dev/null
+++ b/talk/session/phone/videoadapter.cc
@@ -0,0 +1,496 @@
+// 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()
+ : 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/videocommon.h b/talk/session/phone/videocommon.h
index fb1f393..6662bfc 100644
--- a/talk/session/phone/videocommon.h
+++ b/talk/session/phone/videocommon.h
@@ -117,35 +117,31 @@
struct VideoFormatPod {
int width; // in number of pixels
int height; // in number of pixels
- int framerate;
+ int64 interval; // in nanoseconds
uint32 fourcc; // color space. FOURCC_ANY means that any color space is OK.
};
-struct VideoFormat {
+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)
- : width(format.width),
- height(format.height),
- interval(FpsToInterval(format.framerate)),
- fourcc(format.fourcc) {
+ 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) {
@@ -190,11 +186,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/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index 046ae5f..5b110d5 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -232,32 +232,34 @@
#endif
};
+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 VideoFormatPod WebRtcVideoEngine::kVideoFormats[] = {
- {1280, 800, 30, FOURCC_ANY},
- {1280, 720, 30, FOURCC_ANY},
- {960, 600, 30, FOURCC_ANY},
- {960, 540, 30, FOURCC_ANY},
- {640, 400, 30, FOURCC_ANY},
- {640, 360, 30, FOURCC_ANY},
- {640, 480, 30, FOURCC_ANY},
- {480, 300, 30, FOURCC_ANY},
- {480, 270, 30, FOURCC_ANY},
- {480, 360, 30, FOURCC_ANY},
- {320, 200, 30, FOURCC_ANY},
- {320, 180, 30, FOURCC_ANY},
- {320, 240, 30, FOURCC_ANY},
- {240, 150, 30, FOURCC_ANY},
- {240, 135, 30, FOURCC_ANY},
- {240, 180, 30, FOURCC_ANY},
- {160, 100, 30, FOURCC_ANY},
- {160, 90, 30, FOURCC_ANY},
- {160, 120, 30, FOURCC_ANY},
+ {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},
};
const VideoFormatPod WebRtcVideoEngine::kDefaultVideoFormat =
- {640, 400, 30, FOURCC_ANY};
+ {640, 400, kNsPerFrame, FOURCC_ANY};
WebRtcVideoEngine::WebRtcVideoEngine() {
Construct(new ViEWrapper(), new ViETraceWrapper(), NULL);
@@ -304,7 +306,7 @@
kVideoCodecPrefs[0].name,
kDefaultVideoFormat.width,
kDefaultVideoFormat.height,
- kDefaultVideoFormat.framerate,
+ VideoFormat::IntervalToFps(kDefaultVideoFormat.interval),
0);
if (!SetDefaultCodec(max_codec)) {
LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
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/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/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/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/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/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