aboutsummaryrefslogtreecommitdiff
path: root/webrtc/libjingle/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/libjingle/xmpp')
-rw-r--r--webrtc/libjingle/xmpp/asyncsocket.h72
-rw-r--r--webrtc/libjingle/xmpp/chatroommodule.h253
-rw-r--r--webrtc/libjingle/xmpp/chatroommodule_unittest.cc280
-rw-r--r--webrtc/libjingle/xmpp/chatroommoduleimpl.cc735
-rw-r--r--webrtc/libjingle/xmpp/constants.cc614
-rw-r--r--webrtc/libjingle/xmpp/constants.h551
-rw-r--r--webrtc/libjingle/xmpp/discoitemsquerytask.cc62
-rw-r--r--webrtc/libjingle/xmpp/discoitemsquerytask.h65
-rw-r--r--webrtc/libjingle/xmpp/fakexmppclient.h106
-rw-r--r--webrtc/libjingle/xmpp/hangoutpubsubclient.cc400
-rw-r--r--webrtc/libjingle/xmpp/hangoutpubsubclient.h178
-rw-r--r--webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc753
-rw-r--r--webrtc/libjingle/xmpp/iqtask.cc69
-rw-r--r--webrtc/libjingle/xmpp/iqtask.h48
-rw-r--r--webrtc/libjingle/xmpp/jid.cc379
-rw-r--r--webrtc/libjingle/xmpp/jid.h81
-rw-r--r--webrtc/libjingle/xmpp/jid_unittest.cc122
-rw-r--r--webrtc/libjingle/xmpp/jingleinfotask.cc121
-rw-r--r--webrtc/libjingle/xmpp/jingleinfotask.h44
-rw-r--r--webrtc/libjingle/xmpp/module.h35
-rw-r--r--webrtc/libjingle/xmpp/moduleimpl.cc48
-rw-r--r--webrtc/libjingle/xmpp/moduleimpl.h76
-rw-r--r--webrtc/libjingle/xmpp/mucroomconfigtask.cc74
-rw-r--r--webrtc/libjingle/xmpp/mucroomconfigtask.h47
-rw-r--r--webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc127
-rw-r--r--webrtc/libjingle/xmpp/mucroomdiscoverytask.cc66
-rw-r--r--webrtc/libjingle/xmpp/mucroomdiscoverytask.h41
-rw-r--r--webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc145
-rw-r--r--webrtc/libjingle/xmpp/mucroomlookuptask.cc159
-rw-r--r--webrtc/libjingle/xmpp/mucroomlookuptask.h76
-rw-r--r--webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc187
-rw-r--r--webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc51
-rw-r--r--webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h38
-rw-r--r--webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc99
-rw-r--r--webrtc/libjingle/xmpp/pingtask.cc92
-rw-r--r--webrtc/libjingle/xmpp/pingtask.h55
-rw-r--r--webrtc/libjingle/xmpp/pingtask_unittest.cc101
-rw-r--r--webrtc/libjingle/xmpp/plainsaslhandler.h64
-rw-r--r--webrtc/libjingle/xmpp/presenceouttask.cc140
-rw-r--r--webrtc/libjingle/xmpp/presenceouttask.h37
-rw-r--r--webrtc/libjingle/xmpp/presencereceivetask.cc141
-rw-r--r--webrtc/libjingle/xmpp/presencereceivetask.h56
-rw-r--r--webrtc/libjingle/xmpp/presencestatus.cc45
-rw-r--r--webrtc/libjingle/xmpp/presencestatus.h188
-rw-r--r--webrtc/libjingle/xmpp/prexmppauth.h71
-rw-r--r--webrtc/libjingle/xmpp/pubsub_task.cc200
-rw-r--r--webrtc/libjingle/xmpp/pubsub_task.h58
-rw-r--r--webrtc/libjingle/xmpp/pubsubclient.cc129
-rw-r--r--webrtc/libjingle/xmpp/pubsubclient.h111
-rw-r--r--webrtc/libjingle/xmpp/pubsubclient_unittest.cc278
-rw-r--r--webrtc/libjingle/xmpp/pubsubstateclient.cc25
-rw-r--r--webrtc/libjingle/xmpp/pubsubstateclient.h270
-rw-r--r--webrtc/libjingle/xmpp/pubsubtasks.cc204
-rw-r--r--webrtc/libjingle/xmpp/pubsubtasks.h114
-rw-r--r--webrtc/libjingle/xmpp/pubsubtasks_unittest.cc280
-rw-r--r--webrtc/libjingle/xmpp/receivetask.cc34
-rw-r--r--webrtc/libjingle/xmpp/receivetask.h41
-rw-r--r--webrtc/libjingle/xmpp/rostermodule.h331
-rw-r--r--webrtc/libjingle/xmpp/rostermodule_unittest.cc832
-rw-r--r--webrtc/libjingle/xmpp/rostermoduleimpl.cc1064
-rw-r--r--webrtc/libjingle/xmpp/rostermoduleimpl.h285
-rw-r--r--webrtc/libjingle/xmpp/saslcookiemechanism.h69
-rw-r--r--webrtc/libjingle/xmpp/saslhandler.h42
-rw-r--r--webrtc/libjingle/xmpp/saslmechanism.cc55
-rw-r--r--webrtc/libjingle/xmpp/saslmechanism.h57
-rw-r--r--webrtc/libjingle/xmpp/saslplainmechanism.h48
-rw-r--r--webrtc/libjingle/xmpp/util_unittest.cc109
-rw-r--r--webrtc/libjingle/xmpp/util_unittest.h58
-rw-r--r--webrtc/libjingle/xmpp/xmpp.gyp145
-rw-r--r--webrtc/libjingle/xmpp/xmpp_tests.gypi37
-rw-r--r--webrtc/libjingle/xmpp/xmppauth.cc88
-rw-r--r--webrtc/libjingle/xmpp/xmppauth.h61
-rw-r--r--webrtc/libjingle/xmpp/xmppclient.cc424
-rw-r--r--webrtc/libjingle/xmpp/xmppclient.h148
-rw-r--r--webrtc/libjingle/xmpp/xmppclientsettings.h111
-rw-r--r--webrtc/libjingle/xmpp/xmppengine.h332
-rw-r--r--webrtc/libjingle/xmpp/xmppengine_unittest.cc325
-rw-r--r--webrtc/libjingle/xmpp/xmppengineimpl.cc446
-rw-r--r--webrtc/libjingle/xmpp/xmppengineimpl.h265
-rw-r--r--webrtc/libjingle/xmpp/xmppengineimpl_iq.cc260
-rw-r--r--webrtc/libjingle/xmpp/xmpplogintask.cc380
-rw-r--r--webrtc/libjingle/xmpp/xmpplogintask.h87
-rw-r--r--webrtc/libjingle/xmpp/xmpplogintask_unittest.cc634
-rw-r--r--webrtc/libjingle/xmpp/xmpppump.cc67
-rw-r--r--webrtc/libjingle/xmpp/xmpppump.h62
-rw-r--r--webrtc/libjingle/xmpp/xmppsocket.cc245
-rw-r--r--webrtc/libjingle/xmpp/xmppsocket.h72
-rw-r--r--webrtc/libjingle/xmpp/xmppstanzaparser.cc89
-rw-r--r--webrtc/libjingle/xmpp/xmppstanzaparser.h80
-rw-r--r--webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc175
-rw-r--r--webrtc/libjingle/xmpp/xmpptask.cc158
-rw-r--r--webrtc/libjingle/xmpp/xmpptask.h172
-rw-r--r--webrtc/libjingle/xmpp/xmppthread.cc69
-rw-r--r--webrtc/libjingle/xmpp/xmppthread.h45
94 files changed, 16863 insertions, 0 deletions
diff --git a/webrtc/libjingle/xmpp/asyncsocket.h b/webrtc/libjingle/xmpp/asyncsocket.h
new file mode 100644
index 0000000000..6d77ce0842
--- /dev/null
+++ b/webrtc/libjingle/xmpp/asyncsocket.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_
+#define WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_
+
+#include <string>
+
+#include "webrtc/base/sigslot.h"
+
+namespace rtc {
+ class SocketAddress;
+}
+
+namespace buzz {
+
+class AsyncSocket {
+public:
+ enum State {
+ STATE_CLOSED = 0, //!< Socket is not open.
+ STATE_CLOSING, //!< Socket is closing but can have buffered data
+ STATE_CONNECTING, //!< In the process of
+ STATE_OPEN, //!< Socket is connected
+#if defined(FEATURE_ENABLE_SSL)
+ STATE_TLS_CONNECTING, //!< Establishing TLS connection
+ STATE_TLS_OPEN, //!< TLS connected
+#endif
+ };
+
+ enum Error {
+ ERROR_NONE = 0, //!< No error
+ ERROR_WINSOCK, //!< Winsock error
+ ERROR_DNS, //!< Couldn't resolve host name
+ ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state
+#if defined(FEATURE_ENABLE_SSL)
+ ERROR_SSL, //!< Something went wrong with OpenSSL
+#endif
+ };
+
+ virtual ~AsyncSocket() {}
+ virtual State state() = 0;
+ virtual Error error() = 0;
+ virtual int GetError() = 0; // winsock error code
+
+ virtual bool Connect(const rtc::SocketAddress& addr) = 0;
+ virtual bool Read(char * data, size_t len, size_t* len_read) = 0;
+ virtual bool Write(const char * data, size_t len) = 0;
+ virtual bool Close() = 0;
+#if defined(FEATURE_ENABLE_SSL)
+ // We allow matching any passed domain. This allows us to avoid
+ // handling the valuable certificates for logins into proxies. If
+ // both names are passed as empty, we do not require a match.
+ virtual bool StartTls(const std::string & domainname) = 0;
+#endif
+
+ sigslot::signal0<> SignalConnected;
+ sigslot::signal0<> SignalSSLConnected;
+ sigslot::signal0<> SignalClosed;
+ sigslot::signal0<> SignalRead;
+ sigslot::signal0<> SignalError;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_ASYNCSOCKET_H_
diff --git a/webrtc/libjingle/xmpp/chatroommodule.h b/webrtc/libjingle/xmpp/chatroommodule.h
new file mode 100644
index 0000000000..a68f82b34a
--- /dev/null
+++ b/webrtc/libjingle/xmpp/chatroommodule.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_
+#define WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_
+
+#include "webrtc/libjingle/xmpp/module.h"
+#include "webrtc/libjingle/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,
+ const std::string& client_version,
+ const std::string& locale) = 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 RequestConnectionStatusChange(
+ XmppPresenceConnectionStatus connection_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,
+ //! Someone in the room has blocked you
+ XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED = 8,
+ //! You have blocked someone in the room
+ XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING = 9,
+ //! Client is old. User must upgrade to a more recent version for
+ // hangouts to work.
+ XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT = 10,
+ //! 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:
+ virtual ~XmppChatroomHandler() {}
+
+ //! Indicates the response to RequestEnterChatroom method
+ //! XMPP_CHATROOM_SUCCESS represents success.
+ //! Other status codes are for errors
+ virtual void ChatroomEnteredStatus(XmppChatroomModule* room,
+ const XmppPresence* presence,
+ 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.
+ //! It can be called before ChatroomEnteredStatus.
+ 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,
+ const XmppChatroomMember* changed_member) = 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 // WEBRTC_LIBJINGLE_XMPP_CHATROOMMODULE_H_
diff --git a/webrtc/libjingle/xmpp/chatroommodule_unittest.cc b/webrtc/libjingle/xmpp/chatroommodule_unittest.cc
new file mode 100644
index 0000000000..65d28273cf
--- /dev/null
+++ b/webrtc/libjingle/xmpp/chatroommodule_unittest.cc
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "buzz/chatroommodule.h"
+#include "buzz/constants.h"
+#include "buzz/xmlelement.h"
+#include "buzz/xmppengine.h"
+#include "common/common.h"
+#include "engine/util_unittest.h"
+#include "test/unittest-inl.h"
+#include "test/unittest.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) {
+ RTC_UNUSED(room);
+ ss_ <<"[ChatroomEnteredStatus status: ";
+ WriteEnteredStatus(ss_, status);
+ ss_ <<"]";
+ }
+
+
+ void ChatroomExitedStatus(XmppChatroomModule* room,
+ XmppChatroomExitedStatus status) {
+ RTC_UNUSED(room);
+ ss_ <<"[ChatroomExitedStatus status: ";
+ WriteExitedStatus(ss_, status);
+ ss_ <<"]";
+ }
+
+ void MemberEntered(XmppChatroomModule* room,
+ const XmppChatroomMember* entered_member) {
+ RTC_UNUSED(room);
+ ss_ << "[MemberEntered " << entered_member->member_jid().Str() << "]";
+ }
+
+ void MemberExited(XmppChatroomModule* room,
+ const XmppChatroomMember* exited_member) {
+ RTC_UNUSED(room);
+ ss_ << "[MemberExited " << exited_member->member_jid().Str() << "]";
+ }
+
+ void MemberChanged(XmppChatroomModule* room,
+ const XmppChatroomMember* changed_member) {
+ RTC_UNUSED(room);
+ ss_ << "[MemberChanged " << changed_member->member_jid().Str() << "]";
+ }
+
+ virtual void MessageReceived(XmppChatroomModule* room, const XmlElement& message) {
+ RTC_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
+ rtc::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+
+ // Configure the module and handler
+ rtc::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("", XMPP_CONNECTION_STATUS_UNKNOWN, "en");
+ 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/webrtc/libjingle/xmpp/chatroommoduleimpl.cc b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc
new file mode 100644
index 0000000000..546aa75f92
--- /dev/null
+++ b/webrtc/libjingle/xmpp/chatroommoduleimpl.cc
@@ -0,0 +1,735 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+#include "webrtc/libjingle/xmpp/chatroommodule.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/moduleimpl.h"
+#include "webrtc/base/common.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,
+ const std::string& client_version,
+ const std::string& locale);
+ virtual XmppReturnStatus RequestExitChatroom();
+ virtual XmppReturnStatus RequestConnectionStatusChange(
+ XmppPresenceConnectionStatus connection_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) {RTC_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(const XmlElement* presence,
+ XmppChatroomEnteredStatus status);
+ void FireExitStatus(XmppChatroomExitedStatus status);
+ void FireMessageReceived(const XmlElement& message);
+ void FireMemberEntered(const XmppChatroomMember* entered_member);
+ void FireMemberChanged(const XmppChatroomMember* changed_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:
+ rtc::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;
+}
+
+std::string GetAttrValueFor(XmppPresenceConnectionStatus connection_status) {
+ switch (connection_status) {
+ default:
+ case XMPP_CONNECTION_STATUS_UNKNOWN:
+ return "";
+ case XMPP_CONNECTION_STATUS_CONNECTING:
+ return STR_PSTN_CONFERENCE_STATUS_CONNECTING;
+ case XMPP_CONNECTION_STATUS_CONNECTED:
+ return STR_PSTN_CONFERENCE_STATUS_CONNECTED;
+ }
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestEnterChatroom(
+ const std::string& password,
+ const std::string& client_version,
+ const std::string& locale) {
+ RTC_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());
+
+ XmlElement* muc_x = new XmlElement(QN_MUC_X);
+ element.AddElement(muc_x);
+
+ if (!client_version.empty()) {
+ XmlElement* client_version_element = new XmlElement(QN_CLIENT_VERSION,
+ false);
+ client_version_element->SetBodyText(client_version);
+ muc_x->AddElement(client_version_element);
+ }
+
+ if (!locale.empty()) {
+ XmlElement* locale_element = new XmlElement(QN_LOCALE, false);
+
+ locale_element->SetBodyText(locale);
+ muc_x->AddElement(locale_element);
+ }
+
+ 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;
+
+ // 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 &&
+ chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
+ return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT);
+ }
+ return status;
+}
+
+XmppReturnStatus
+XmppChatroomModuleImpl::RequestConnectionStatusChange(
+ XmppPresenceConnectionStatus connection_status) {
+ if (!engine())
+ return XMPP_RETURN_BADSTATE;
+
+ if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
+ // $TODO - this isn't a bad state, it's a bad call, diff error code?
+ return XMPP_RETURN_BADSTATE;
+ }
+
+ 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));
+ if (connection_status != XMPP_CONNECTION_STATUS_UNKNOWN) {
+ XmlElement* con_status_element =
+ new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+ con_status_element->AddAttr(QN_STATUS, GetAttrValueFor(connection_status));
+ element.AddElement(con_status_element);
+ }
+ XmppReturnStatus status = engine()->SendStanza(&element);
+
+ return status;
+}
+
+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 ""; //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(const XmlElement* presence,
+ XmppChatroomEnteredStatus status) {
+ if (chatroom_handler_) {
+ rtc::scoped_ptr<XmppPresence> xmpp_presence(XmppPresence::Create());
+ xmpp_presence->set_raw_xml(presence);
+ chatroom_handler_->ChatroomEnteredStatus(this, xmpp_presence.get(), 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) {
+ if (chatroom_handler_)
+ chatroom_handler_->MemberEntered(this, entered_member);
+}
+
+void
+XmppChatroomModuleImpl::FireMemberChanged(
+ const XmppChatroomMember* changed_member) {
+ if (chatroom_handler_)
+ chatroom_handler_->MemberChanged(this, changed_member);
+}
+
+void
+XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
+ if (chatroom_handler_)
+ chatroom_handler_->MemberExited(this, exited_member);
+}
+
+
+XmppReturnStatus
+XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement&
+ presence_element) {
+ XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
+ rtc::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_++;
+ FireMemberChanged(member);
+ }
+ else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) {
+ member->SetPresence(presence.get());
+ 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) {
+ RTC_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?
+ // Disable the assert because of b/6133072
+ // ASSERT(transition_desc->is_valid_server_transition);
+ if (!transition_desc->is_valid_server_transition) {
+ return XMPP_RETURN_BADSTATE;
+ }
+ } 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(presence, XMPP_CHATROOM_ENTERED_SUCCESS);
+ break;
+ case TRANSITION_TYPE_ENTER_FAILURE:
+ FireEnteredStatus(presence, 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;
+ if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKED)) {
+ status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED;
+ } else if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKING)) {
+ status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING;
+ }
+ break;
+ }
+ case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED; break;
+ case 406: status = XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT; break;
+ case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
+ case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
+ // http://xmpp.org/extensions/xep-0045.html#enter-maxusers
+ case 503: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
+ }
+ }
+ return status;
+}
+
+XmppChatroomExitedStatus
+XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
+ XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
+ const XmlElement* muc_user = presence->FirstNamed(QN_MUC_USER_X);
+ if (muc_user != NULL) {
+ const XmlElement* user_status = muc_user->FirstNamed(QN_MUC_USER_STATUS);
+ if (user_status != NULL && user_status->HasAttr(QN_CODE)) {
+ int code = atoi(user_status->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/webrtc/libjingle/xmpp/constants.cc b/webrtc/libjingle/xmpp/constants.cc
new file mode 100644
index 0000000000..38e0cec48d
--- /dev/null
+++ b/webrtc/libjingle/xmpp/constants.cc
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/constants.h"
+
+#include <string>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlconstants.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/basicdefs.h"
+
+namespace buzz {
+
+// TODO: Remove static objects of complex types, particularly
+// Jid and QName.
+
+const char NS_CLIENT[] = "jabber:client";
+const char NS_SERVER[] = "jabber:server";
+const char NS_STREAM[] = "http://etherx.jabber.org/streams";
+const char NS_XSTREAM[] = "urn:ietf:params:xml:ns:xmpp-streams";
+const char NS_TLS[] = "urn:ietf:params:xml:ns:xmpp-tls";
+const char NS_SASL[] = "urn:ietf:params:xml:ns:xmpp-sasl";
+const char NS_BIND[] = "urn:ietf:params:xml:ns:xmpp-bind";
+const char NS_DIALBACK[] = "jabber:server:dialback";
+const char NS_SESSION[] = "urn:ietf:params:xml:ns:xmpp-session";
+const char NS_STANZA[] = "urn:ietf:params:xml:ns:xmpp-stanzas";
+const char NS_PRIVACY[] = "jabber:iq:privacy";
+const char NS_ROSTER[] = "jabber:iq:roster";
+const char NS_VCARD[] = "vcard-temp";
+const char NS_AVATAR_HASH[] = "google:avatar";
+const char NS_VCARD_UPDATE[] = "vcard-temp:x:update";
+const char STR_CLIENT[] = "client";
+const char STR_SERVER[] = "server";
+const char STR_STREAM[] = "stream";
+
+const char STR_GET[] = "get";
+const char STR_SET[] = "set";
+const char STR_RESULT[] = "result";
+const char STR_ERROR[] = "error";
+
+const char STR_FORM[] = "form";
+const char STR_SUBMIT[] = "submit";
+const char STR_TEXT_SINGLE[] = "text-single";
+const char STR_LIST_SINGLE[] = "list-single";
+const char STR_LIST_MULTI[] = "list-multi";
+const char STR_HIDDEN[] = "hidden";
+const char STR_FORM_TYPE[] = "FORM_TYPE";
+
+const char STR_FROM[] = "from";
+const char STR_TO[] = "to";
+const char STR_BOTH[] = "both";
+const char STR_REMOVE[] = "remove";
+const char STR_TRUE[] = "true";
+
+const char STR_TYPE[] = "type";
+const char STR_NAME[] = "name";
+const char STR_ID[] = "id";
+const char STR_JID[] = "jid";
+const char STR_SUBSCRIPTION[] = "subscription";
+const char STR_ASK[] = "ask";
+const char STR_X[] = "x";
+const char STR_GOOGLE_COM[] = "google.com";
+const char STR_GMAIL_COM[] = "gmail.com";
+const char STR_GOOGLEMAIL_COM[] = "googlemail.com";
+const char STR_DEFAULT_DOMAIN[] = "default.talk.google.com";
+const char STR_TALK_GOOGLE_COM[] = "talk.google.com";
+const char STR_TALKX_L_GOOGLE_COM[] = "talkx.l.google.com";
+const char STR_XMPP_GOOGLE_COM[] = "xmpp.google.com";
+const char STR_XMPPX_L_GOOGLE_COM[] = "xmppx.l.google.com";
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const char STR_VOICEMAIL[] = "voicemail";
+const char STR_OUTGOINGVOICEMAIL[] = "outgoingvoicemail";
+#endif
+
+const char STR_UNAVAILABLE[] = "unavailable";
+
+const char NS_PING[] = "urn:xmpp:ping";
+const StaticQName QN_PING = { NS_PING, "ping" };
+
+const char NS_MUC_UNIQUE[] = "http://jabber.org/protocol/muc#unique";
+const StaticQName QN_MUC_UNIQUE_QUERY = { NS_MUC_UNIQUE, "unique" };
+const StaticQName QN_HANGOUT_ID = { STR_EMPTY, "hangout-id" };
+
+const char STR_GOOGLE_MUC_LOOKUP_JID[] = "lookup.groupchat.google.com";
+
+const char STR_MUC_ROOMCONFIG_ROOMNAME[] = "muc#roomconfig_roomname";
+const char STR_MUC_ROOMCONFIG_FEATURES[] = "muc#roomconfig_features";
+const char STR_MUC_ROOM_FEATURE_ENTERPRISE[] = "muc_enterprise";
+const char STR_MUC_ROOMCONFIG[] = "http://jabber.org/protocol/muc#roomconfig";
+const char STR_MUC_ROOM_FEATURE_HANGOUT[] = "muc_es";
+const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[] = "muc_lite";
+const char STR_MUC_ROOM_FEATURE_BROADCAST[] = "broadcast";
+const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[] = "muc_muvc";
+const char STR_MUC_ROOM_FEATURE_RECORDABLE[] = "recordable";
+const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[] = "custom_recording";
+const char STR_MUC_ROOM_OWNER_PROFILE_ID[] = "muc#roominfo_owner_profile_id";
+const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[] = "abuse_recordable";
+
+const char STR_ID_TYPE_CONVERSATION[] = "conversation";
+const char NS_GOOGLE_MUC_HANGOUT[] = "google:muc#hangout";
+const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE =
+ { NS_GOOGLE_MUC_HANGOUT, "invite" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE =
+ { NS_GOOGLE_MUC_HANGOUT, "invite-type" };
+const StaticQName QN_ATTR_CREATE_ACTIVITY =
+ { STR_EMPTY, "create-activity" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC =
+ { NS_GOOGLE_MUC_HANGOUT, "public" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE =
+ { NS_GOOGLE_MUC_HANGOUT, "invitee" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS =
+ { NS_GOOGLE_MUC_HANGOUT, "notification-status" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE = {
+ NS_GOOGLE_MUC_HANGOUT, "notification-type" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT = {
+ NS_GOOGLE_MUC_HANGOUT, "hangout-start-context" };
+const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID = {
+ NS_GOOGLE_MUC_HANGOUT, "conversation-id" };
+
+const StaticQName QN_STREAM_STREAM = { NS_STREAM, STR_STREAM };
+const StaticQName QN_STREAM_FEATURES = { NS_STREAM, "features" };
+const StaticQName QN_STREAM_ERROR = { NS_STREAM, "error" };
+
+const StaticQName QN_XSTREAM_BAD_FORMAT = { NS_XSTREAM, "bad-format" };
+const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX =
+ { NS_XSTREAM, "bad-namespace-prefix" };
+const StaticQName QN_XSTREAM_CONFLICT = { NS_XSTREAM, "conflict" };
+const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT =
+ { NS_XSTREAM, "connection-timeout" };
+const StaticQName QN_XSTREAM_HOST_GONE = { NS_XSTREAM, "host-gone" };
+const StaticQName QN_XSTREAM_HOST_UNKNOWN = { NS_XSTREAM, "host-unknown" };
+const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING =
+ { NS_XSTREAM, "improper-addressing" };
+const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR =
+ { NS_XSTREAM, "internal-server-error" };
+const StaticQName QN_XSTREAM_INVALID_FROM = { NS_XSTREAM, "invalid-from" };
+const StaticQName QN_XSTREAM_INVALID_ID = { NS_XSTREAM, "invalid-id" };
+const StaticQName QN_XSTREAM_INVALID_NAMESPACE =
+ { NS_XSTREAM, "invalid-namespace" };
+const StaticQName QN_XSTREAM_INVALID_XML = { NS_XSTREAM, "invalid-xml" };
+const StaticQName QN_XSTREAM_NOT_AUTHORIZED = { NS_XSTREAM, "not-authorized" };
+const StaticQName QN_XSTREAM_POLICY_VIOLATION =
+ { NS_XSTREAM, "policy-violation" };
+const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED =
+ { NS_XSTREAM, "remote-connection-failed" };
+const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT =
+ { NS_XSTREAM, "resource-constraint" };
+const StaticQName QN_XSTREAM_RESTRICTED_XML = { NS_XSTREAM, "restricted-xml" };
+const StaticQName QN_XSTREAM_SEE_OTHER_HOST = { NS_XSTREAM, "see-other-host" };
+const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN =
+ { NS_XSTREAM, "system-shutdown" };
+const StaticQName QN_XSTREAM_UNDEFINED_CONDITION =
+ { NS_XSTREAM, "undefined-condition" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING =
+ { NS_XSTREAM, "unsupported-encoding" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE =
+ { NS_XSTREAM, "unsupported-stanza-type" };
+const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION =
+ { NS_XSTREAM, "unsupported-version" };
+const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED =
+ { NS_XSTREAM, "xml-not-well-formed" };
+const StaticQName QN_XSTREAM_TEXT = { NS_XSTREAM, "text" };
+
+const StaticQName QN_TLS_STARTTLS = { NS_TLS, "starttls" };
+const StaticQName QN_TLS_REQUIRED = { NS_TLS, "required" };
+const StaticQName QN_TLS_PROCEED = { NS_TLS, "proceed" };
+const StaticQName QN_TLS_FAILURE = { NS_TLS, "failure" };
+
+const StaticQName QN_SASL_MECHANISMS = { NS_SASL, "mechanisms" };
+const StaticQName QN_SASL_MECHANISM = { NS_SASL, "mechanism" };
+const StaticQName QN_SASL_AUTH = { NS_SASL, "auth" };
+const StaticQName QN_SASL_CHALLENGE = { NS_SASL, "challenge" };
+const StaticQName QN_SASL_RESPONSE = { NS_SASL, "response" };
+const StaticQName QN_SASL_ABORT = { NS_SASL, "abort" };
+const StaticQName QN_SASL_SUCCESS = { NS_SASL, "success" };
+const StaticQName QN_SASL_FAILURE = { NS_SASL, "failure" };
+const StaticQName QN_SASL_ABORTED = { NS_SASL, "aborted" };
+const StaticQName QN_SASL_INCORRECT_ENCODING =
+ { NS_SASL, "incorrect-encoding" };
+const StaticQName QN_SASL_INVALID_AUTHZID = { NS_SASL, "invalid-authzid" };
+const StaticQName QN_SASL_INVALID_MECHANISM = { NS_SASL, "invalid-mechanism" };
+const StaticQName QN_SASL_MECHANISM_TOO_WEAK =
+ { NS_SASL, "mechanism-too-weak" };
+const StaticQName QN_SASL_NOT_AUTHORIZED = { NS_SASL, "not-authorized" };
+const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE =
+ { NS_SASL, "temporary-auth-failure" };
+
+// These are non-standard.
+const char NS_GOOGLE_AUTH_PROTOCOL[] =
+ "http://www.google.com/talk/protocol/auth";
+const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT =
+ { NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result" };
+const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN =
+ { NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login" };
+const StaticQName QN_GOOGLE_AUTH_SERVICE =
+ { NS_GOOGLE_AUTH_PROTOCOL, "service" };
+
+const StaticQName QN_DIALBACK_RESULT = { NS_DIALBACK, "result" };
+const StaticQName QN_DIALBACK_VERIFY = { NS_DIALBACK, "verify" };
+
+const StaticQName QN_STANZA_BAD_REQUEST = { NS_STANZA, "bad-request" };
+const StaticQName QN_STANZA_CONFLICT = { NS_STANZA, "conflict" };
+const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED =
+ { NS_STANZA, "feature-not-implemented" };
+const StaticQName QN_STANZA_FORBIDDEN = { NS_STANZA, "forbidden" };
+const StaticQName QN_STANZA_GONE = { NS_STANZA, "gone" };
+const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR =
+ { NS_STANZA, "internal-server-error" };
+const StaticQName QN_STANZA_ITEM_NOT_FOUND = { NS_STANZA, "item-not-found" };
+const StaticQName QN_STANZA_JID_MALFORMED = { NS_STANZA, "jid-malformed" };
+const StaticQName QN_STANZA_NOT_ACCEPTABLE = { NS_STANZA, "not-acceptable" };
+const StaticQName QN_STANZA_NOT_ALLOWED = { NS_STANZA, "not-allowed" };
+const StaticQName QN_STANZA_PAYMENT_REQUIRED =
+ { NS_STANZA, "payment-required" };
+const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE =
+ { NS_STANZA, "recipient-unavailable" };
+const StaticQName QN_STANZA_REDIRECT = { NS_STANZA, "redirect" };
+const StaticQName QN_STANZA_REGISTRATION_REQUIRED =
+ { NS_STANZA, "registration-required" };
+const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND =
+ { NS_STANZA, "remote-server-not-found" };
+const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT =
+ { NS_STANZA, "remote-server-timeout" };
+const StaticQName QN_STANZA_RESOURCE_CONSTRAINT =
+ { NS_STANZA, "resource-constraint" };
+const StaticQName QN_STANZA_SERVICE_UNAVAILABLE =
+ { NS_STANZA, "service-unavailable" };
+const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED =
+ { NS_STANZA, "subscription-required" };
+const StaticQName QN_STANZA_UNDEFINED_CONDITION =
+ { NS_STANZA, "undefined-condition" };
+const StaticQName QN_STANZA_UNEXPECTED_REQUEST =
+ { NS_STANZA, "unexpected-request" };
+const StaticQName QN_STANZA_TEXT = { NS_STANZA, "text" };
+
+const StaticQName QN_BIND_BIND = { NS_BIND, "bind" };
+const StaticQName QN_BIND_RESOURCE = { NS_BIND, "resource" };
+const StaticQName QN_BIND_JID = { NS_BIND, "jid" };
+
+const StaticQName QN_MESSAGE = { NS_CLIENT, "message" };
+const StaticQName QN_BODY = { NS_CLIENT, "body" };
+const StaticQName QN_SUBJECT = { NS_CLIENT, "subject" };
+const StaticQName QN_THREAD = { NS_CLIENT, "thread" };
+const StaticQName QN_PRESENCE = { NS_CLIENT, "presence" };
+const StaticQName QN_SHOW = { NS_CLIENT, "show" };
+const StaticQName QN_STATUS = { NS_CLIENT, "status" };
+const StaticQName QN_LANG = { NS_CLIENT, "lang" };
+const StaticQName QN_PRIORITY = { NS_CLIENT, "priority" };
+const StaticQName QN_IQ = { NS_CLIENT, "iq" };
+const StaticQName QN_ERROR = { NS_CLIENT, "error" };
+
+const StaticQName QN_SERVER_MESSAGE = { NS_SERVER, "message" };
+const StaticQName QN_SERVER_BODY = { NS_SERVER, "body" };
+const StaticQName QN_SERVER_SUBJECT = { NS_SERVER, "subject" };
+const StaticQName QN_SERVER_THREAD = { NS_SERVER, "thread" };
+const StaticQName QN_SERVER_PRESENCE = { NS_SERVER, "presence" };
+const StaticQName QN_SERVER_SHOW = { NS_SERVER, "show" };
+const StaticQName QN_SERVER_STATUS = { NS_SERVER, "status" };
+const StaticQName QN_SERVER_LANG = { NS_SERVER, "lang" };
+const StaticQName QN_SERVER_PRIORITY = { NS_SERVER, "priority" };
+const StaticQName QN_SERVER_IQ = { NS_SERVER, "iq" };
+const StaticQName QN_SERVER_ERROR = { NS_SERVER, "error" };
+
+const StaticQName QN_SESSION_SESSION = { NS_SESSION, "session" };
+
+const StaticQName QN_PRIVACY_QUERY = { NS_PRIVACY, "query" };
+const StaticQName QN_PRIVACY_ACTIVE = { NS_PRIVACY, "active" };
+const StaticQName QN_PRIVACY_DEFAULT = { NS_PRIVACY, "default" };
+const StaticQName QN_PRIVACY_LIST = { NS_PRIVACY, "list" };
+const StaticQName QN_PRIVACY_ITEM = { NS_PRIVACY, "item" };
+const StaticQName QN_PRIVACY_IQ = { NS_PRIVACY, "iq" };
+const StaticQName QN_PRIVACY_MESSAGE = { NS_PRIVACY, "message" };
+const StaticQName QN_PRIVACY_PRESENCE_IN = { NS_PRIVACY, "presence-in" };
+const StaticQName QN_PRIVACY_PRESENCE_OUT = { NS_PRIVACY, "presence-out" };
+
+const StaticQName QN_ROSTER_QUERY = { NS_ROSTER, "query" };
+const StaticQName QN_ROSTER_ITEM = { NS_ROSTER, "item" };
+const StaticQName QN_ROSTER_GROUP = { NS_ROSTER, "group" };
+
+const StaticQName QN_VCARD = { NS_VCARD, "vCard" };
+const StaticQName QN_VCARD_FN = { NS_VCARD, "FN" };
+const StaticQName QN_VCARD_PHOTO = { NS_VCARD, "PHOTO" };
+const StaticQName QN_VCARD_PHOTO_BINVAL = { NS_VCARD, "BINVAL" };
+const StaticQName QN_VCARD_AVATAR_HASH = { NS_AVATAR_HASH, "hash" };
+const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED =
+ { NS_AVATAR_HASH, "modified" };
+
+const StaticQName QN_NAME = { STR_EMPTY, "name" };
+const StaticQName QN_AFFILIATION = { STR_EMPTY, "affiliation" };
+const StaticQName QN_ROLE = { STR_EMPTY, "role" };
+
+#if defined(FEATURE_ENABLE_PSTN)
+const StaticQName QN_VCARD_TEL = { NS_VCARD, "TEL" };
+const StaticQName QN_VCARD_VOICE = { NS_VCARD, "VOICE" };
+const StaticQName QN_VCARD_HOME = { NS_VCARD, "HOME" };
+const StaticQName QN_VCARD_WORK = { NS_VCARD, "WORK" };
+const StaticQName QN_VCARD_CELL = { NS_VCARD, "CELL" };
+const StaticQName QN_VCARD_NUMBER = { NS_VCARD, "NUMBER" };
+#endif
+
+const StaticQName QN_XML_LANG = { NS_XML, "lang" };
+
+const StaticQName QN_ENCODING = { STR_EMPTY, STR_ENCODING };
+const StaticQName QN_VERSION = { STR_EMPTY, STR_VERSION };
+const StaticQName QN_TO = { STR_EMPTY, "to" };
+const StaticQName QN_FROM = { STR_EMPTY, "from" };
+const StaticQName QN_TYPE = { STR_EMPTY, "type" };
+const StaticQName QN_ID = { STR_EMPTY, "id" };
+const StaticQName QN_CODE = { STR_EMPTY, "code" };
+
+const StaticQName QN_VALUE = { STR_EMPTY, "value" };
+const StaticQName QN_ACTION = { STR_EMPTY, "action" };
+const StaticQName QN_ORDER = { STR_EMPTY, "order" };
+const StaticQName QN_MECHANISM = { STR_EMPTY, "mechanism" };
+const StaticQName QN_ASK = { STR_EMPTY, "ask" };
+const StaticQName QN_JID = { STR_EMPTY, "jid" };
+const StaticQName QN_NICK = { STR_EMPTY, "nick" };
+const StaticQName QN_SUBSCRIPTION = { STR_EMPTY, "subscription" };
+const StaticQName QN_TITLE1 = { STR_EMPTY, "title1" };
+const StaticQName QN_TITLE2 = { STR_EMPTY, "title2" };
+
+const StaticQName QN_XMLNS_CLIENT = { NS_XMLNS, STR_CLIENT };
+const StaticQName QN_XMLNS_SERVER = { NS_XMLNS, STR_SERVER };
+const StaticQName QN_XMLNS_STREAM = { NS_XMLNS, STR_STREAM };
+
+
+// Presence
+const char STR_SHOW_AWAY[] = "away";
+const char STR_SHOW_CHAT[] = "chat";
+const char STR_SHOW_DND[] = "dnd";
+const char STR_SHOW_XA[] = "xa";
+const char STR_SHOW_OFFLINE[] = "offline";
+
+const char NS_GOOGLE_PSTN_CONFERENCE[] = "http://www.google.com/pstn-conference";
+const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS = { NS_GOOGLE_PSTN_CONFERENCE, "status" };
+const StaticQName QN_ATTR_STATUS = { STR_EMPTY, "status" };
+
+// Presence connection status
+const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[] = "connecting";
+const char STR_PSTN_CONFERENCE_STATUS_JOINING[] = "joining";
+const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[] = "connected";
+const char STR_PSTN_CONFERENCE_STATUS_HANGUP[] = "hangup";
+
+// Subscription
+const char STR_SUBSCRIBE[] = "subscribe";
+const char STR_SUBSCRIBED[] = "subscribed";
+const char STR_UNSUBSCRIBE[] = "unsubscribe";
+const char STR_UNSUBSCRIBED[] = "unsubscribed";
+
+// Google Invite
+const char NS_GOOGLE_SUBSCRIBE[] = "google:subscribe";
+const StaticQName QN_INVITATION = { NS_GOOGLE_SUBSCRIBE, "invitation" };
+const StaticQName QN_INVITE_NAME = { NS_GOOGLE_SUBSCRIBE, "name" };
+const StaticQName QN_INVITE_SUBJECT = { NS_GOOGLE_SUBSCRIBE, "subject" };
+const StaticQName QN_INVITE_MESSAGE = { NS_GOOGLE_SUBSCRIBE, "body" };
+
+// Kick
+const char NS_GOOGLE_MUC_ADMIN[] = "google:muc#admin";
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY = { NS_GOOGLE_MUC_ADMIN, "query" };
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM =
+ { NS_GOOGLE_MUC_ADMIN, "item" };
+const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON =
+ { NS_GOOGLE_MUC_ADMIN, "reason" };
+
+// PubSub: http://xmpp.org/extensions/xep-0060.html
+const char NS_PUBSUB[] = "http://jabber.org/protocol/pubsub";
+const StaticQName QN_PUBSUB = { NS_PUBSUB, "pubsub" };
+const StaticQName QN_PUBSUB_ITEMS = { NS_PUBSUB, "items" };
+const StaticQName QN_PUBSUB_ITEM = { NS_PUBSUB, "item" };
+const StaticQName QN_PUBSUB_PUBLISH = { NS_PUBSUB, "publish" };
+const StaticQName QN_PUBSUB_RETRACT = { NS_PUBSUB, "retract" };
+const StaticQName QN_ATTR_PUBLISHER = { STR_EMPTY, "publisher" };
+
+const char NS_PUBSUB_EVENT[] = "http://jabber.org/protocol/pubsub#event";
+const StaticQName QN_NODE = { STR_EMPTY, "node" };
+const StaticQName QN_PUBSUB_EVENT = { NS_PUBSUB_EVENT, "event" };
+const StaticQName QN_PUBSUB_EVENT_ITEMS = { NS_PUBSUB_EVENT, "items" };
+const StaticQName QN_PUBSUB_EVENT_ITEM = { NS_PUBSUB_EVENT, "item" };
+const StaticQName QN_PUBSUB_EVENT_RETRACT = { NS_PUBSUB_EVENT, "retract" };
+const StaticQName QN_NOTIFY = { STR_EMPTY, "notify" };
+
+const char NS_PRESENTER[] = "google:presenter";
+const StaticQName QN_PRESENTER_PRESENTER = { NS_PRESENTER, "presenter" };
+const StaticQName QN_PRESENTER_PRESENTATION_ITEM =
+ { NS_PRESENTER, "presentation-item" };
+const StaticQName QN_PRESENTER_PRESENTATION_TYPE =
+ { NS_PRESENTER, "presentation-type" };
+const StaticQName QN_PRESENTER_PRESENTATION_ID =
+ { NS_PRESENTER, "presentation-id" };
+
+// JEP 0030
+const StaticQName QN_CATEGORY = { STR_EMPTY, "category" };
+const StaticQName QN_VAR = { STR_EMPTY, "var" };
+const char NS_DISCO_INFO[] = "http://jabber.org/protocol/disco#info";
+const char NS_DISCO_ITEMS[] = "http://jabber.org/protocol/disco#items";
+const StaticQName QN_DISCO_INFO_QUERY = { NS_DISCO_INFO, "query" };
+const StaticQName QN_DISCO_IDENTITY = { NS_DISCO_INFO, "identity" };
+const StaticQName QN_DISCO_FEATURE = { NS_DISCO_INFO, "feature" };
+
+const StaticQName QN_DISCO_ITEMS_QUERY = { NS_DISCO_ITEMS, "query" };
+const StaticQName QN_DISCO_ITEM = { NS_DISCO_ITEMS, "item" };
+
+// JEP 0020
+const char NS_FEATURE[] = "http://jabber.org/protocol/feature-neg";
+const StaticQName QN_FEATURE_FEATURE = { NS_FEATURE, "feature" };
+
+// JEP 0004
+const char NS_XDATA[] = "jabber:x:data";
+const StaticQName QN_XDATA_X = { NS_XDATA, "x" };
+const StaticQName QN_XDATA_INSTRUCTIONS = { NS_XDATA, "instructions" };
+const StaticQName QN_XDATA_TITLE = { NS_XDATA, "title" };
+const StaticQName QN_XDATA_FIELD = { NS_XDATA, "field" };
+const StaticQName QN_XDATA_REPORTED = { NS_XDATA, "reported" };
+const StaticQName QN_XDATA_ITEM = { NS_XDATA, "item" };
+const StaticQName QN_XDATA_DESC = { NS_XDATA, "desc" };
+const StaticQName QN_XDATA_REQUIRED = { NS_XDATA, "required" };
+const StaticQName QN_XDATA_VALUE = { NS_XDATA, "value" };
+const StaticQName QN_XDATA_OPTION = { NS_XDATA, "option" };
+
+// JEP 0045
+const char NS_MUC[] = "http://jabber.org/protocol/muc";
+const StaticQName QN_MUC_X = { NS_MUC, "x" };
+const StaticQName QN_MUC_ITEM = { NS_MUC, "item" };
+const StaticQName QN_MUC_AFFILIATION = { NS_MUC, "affiliation" };
+const StaticQName QN_MUC_ROLE = { NS_MUC, "role" };
+const char STR_AFFILIATION_NONE[] = "none";
+const char STR_ROLE_PARTICIPANT[] = "participant";
+
+const char NS_GOOGLE_SESSION[] = "http://www.google.com/session";
+const StaticQName QN_GOOGLE_CIRCLE_ID = { STR_EMPTY, "google-circle-id" };
+const StaticQName QN_GOOGLE_USER_ID = { STR_EMPTY, "google-user-id" };
+const StaticQName QN_GOOGLE_SESSION_BLOCKED = { NS_GOOGLE_SESSION, "blocked" };
+const StaticQName QN_GOOGLE_SESSION_BLOCKING =
+ { NS_GOOGLE_SESSION, "blocking" };
+
+const char NS_MUC_OWNER[] = "http://jabber.org/protocol/muc#owner";
+const StaticQName QN_MUC_OWNER_QUERY = { NS_MUC_OWNER, "query" };
+
+const char NS_MUC_USER[] = "http://jabber.org/protocol/muc#user";
+const StaticQName QN_MUC_USER_CONTINUE = { NS_MUC_USER, "continue" };
+const StaticQName QN_MUC_USER_X = { NS_MUC_USER, "x" };
+const StaticQName QN_MUC_USER_ITEM = { NS_MUC_USER, "item" };
+const StaticQName QN_MUC_USER_STATUS = { NS_MUC_USER, "status" };
+const StaticQName QN_MUC_USER_REASON = { NS_MUC_USER, "reason" };
+const StaticQName QN_MUC_USER_ABUSE_VIOLATION = { NS_MUC_USER, "abuse-violation" };
+
+// JEP 0055 - Jabber Search
+const char NS_SEARCH[] = "jabber:iq:search";
+const StaticQName QN_SEARCH_QUERY = { NS_SEARCH, "query" };
+const StaticQName QN_SEARCH_ITEM = { NS_SEARCH, "item" };
+const StaticQName QN_SEARCH_ROOM_NAME = { NS_SEARCH, "room-name" };
+const StaticQName QN_SEARCH_ROOM_DOMAIN = { NS_SEARCH, "room-domain" };
+const StaticQName QN_SEARCH_ROOM_JID = { NS_SEARCH, "room-jid" };
+const StaticQName QN_SEARCH_HANGOUT_ID = { NS_SEARCH, "hangout-id" };
+const StaticQName QN_SEARCH_EXTERNAL_ID = { NS_SEARCH, "external-id" };
+
+// JEP 0115
+const char NS_CAPS[] = "http://jabber.org/protocol/caps";
+const StaticQName QN_CAPS_C = { NS_CAPS, "c" };
+const StaticQName QN_VER = { STR_EMPTY, "ver" };
+const StaticQName QN_EXT = { STR_EMPTY, "ext" };
+
+// JEP 0153
+const char kNSVCard[] = "vcard-temp:x:update";
+const StaticQName kQnVCardX = { kNSVCard, "x" };
+const StaticQName kQnVCardPhoto = { kNSVCard, "photo" };
+
+// JEP 0172 User Nickname
+const char NS_NICKNAME[] = "http://jabber.org/protocol/nick";
+const StaticQName QN_NICKNAME = { NS_NICKNAME, "nick" };
+
+// JEP 0085 chat state
+const char NS_CHATSTATE[] = "http://jabber.org/protocol/chatstates";
+const StaticQName QN_CS_ACTIVE = { NS_CHATSTATE, "active" };
+const StaticQName QN_CS_COMPOSING = { NS_CHATSTATE, "composing" };
+const StaticQName QN_CS_PAUSED = { NS_CHATSTATE, "paused" };
+const StaticQName QN_CS_INACTIVE = { NS_CHATSTATE, "inactive" };
+const StaticQName QN_CS_GONE = { NS_CHATSTATE, "gone" };
+
+// JEP 0091 Delayed Delivery
+const char kNSDelay[] = "jabber:x:delay";
+const StaticQName kQnDelayX = { kNSDelay, "x" };
+const StaticQName kQnStamp = { STR_EMPTY, "stamp" };
+
+// Google time stamping (higher resolution)
+const char kNSTimestamp[] = "google:timestamp";
+const StaticQName kQnTime = { kNSTimestamp, "time" };
+const StaticQName kQnMilliseconds = { STR_EMPTY, "ms" };
+
+// Jingle Info
+const char NS_JINGLE_INFO[] = "google:jingleinfo";
+const StaticQName QN_JINGLE_INFO_QUERY = { NS_JINGLE_INFO, "query" };
+const StaticQName QN_JINGLE_INFO_STUN = { NS_JINGLE_INFO, "stun" };
+const StaticQName QN_JINGLE_INFO_RELAY = { NS_JINGLE_INFO, "relay" };
+const StaticQName QN_JINGLE_INFO_SERVER = { NS_JINGLE_INFO, "server" };
+const StaticQName QN_JINGLE_INFO_TOKEN = { NS_JINGLE_INFO, "token" };
+const StaticQName QN_JINGLE_INFO_HOST = { STR_EMPTY, "host" };
+const StaticQName QN_JINGLE_INFO_TCP = { STR_EMPTY, "tcp" };
+const StaticQName QN_JINGLE_INFO_UDP = { STR_EMPTY, "udp" };
+const StaticQName QN_JINGLE_INFO_TCPSSL = { STR_EMPTY, "tcpssl" };
+
+// Call Performance Logging
+const char NS_GOOGLE_CALLPERF_STATS[] = "google:call-perf-stats";
+const StaticQName QN_CALLPERF_STATS =
+ { NS_GOOGLE_CALLPERF_STATS, "callPerfStats" };
+const StaticQName QN_CALLPERF_SESSIONID = { STR_EMPTY, "sessionId" };
+const StaticQName QN_CALLPERF_LOCALUSER = { STR_EMPTY, "localUser" };
+const StaticQName QN_CALLPERF_REMOTEUSER = { STR_EMPTY, "remoteUser" };
+const StaticQName QN_CALLPERF_STARTTIME = { STR_EMPTY, "startTime" };
+const StaticQName QN_CALLPERF_CALL_LENGTH = { STR_EMPTY, "callLength" };
+const StaticQName QN_CALLPERF_CALL_ACCEPTED = { STR_EMPTY, "callAccepted" };
+const StaticQName QN_CALLPERF_CALL_ERROR_CODE = { STR_EMPTY, "callErrorCode" };
+const StaticQName QN_CALLPERF_TERMINATE_CODE = { STR_EMPTY, "terminateCode" };
+const StaticQName QN_CALLPERF_DATAPOINT =
+ { NS_GOOGLE_CALLPERF_STATS, "dataPoint" };
+const StaticQName QN_CALLPERF_DATAPOINT_TIME = { STR_EMPTY, "timeStamp" };
+const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST =
+ { STR_EMPTY, "fraction_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST = { STR_EMPTY, "cum_lost" };
+const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX = { STR_EMPTY, "ext_max" };
+const StaticQName QN_CALLPERF_DATAPOINT_JITTER = { STR_EMPTY, "jitter" };
+const StaticQName QN_CALLPERF_DATAPOINT_RTT = { STR_EMPTY, "RTT" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R =
+ { STR_EMPTY, "bytesReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R =
+ { STR_EMPTY, "packetsReceived" };
+const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S = { STR_EMPTY, "bytesSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S =
+ { STR_EMPTY, "packetsSent" };
+const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU =
+ { STR_EMPTY, "processCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU = { STR_EMPTY, "systemCpu" };
+const StaticQName QN_CALLPERF_DATAPOINT_CPUS = { STR_EMPTY, "cpus" };
+const StaticQName QN_CALLPERF_CONNECTION =
+ { NS_GOOGLE_CALLPERF_STATS, "connection" };
+const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS =
+ { STR_EMPTY, "localAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS =
+ { STR_EMPTY, "remoteAddress" };
+const StaticQName QN_CALLPERF_CONNECTION_FLAGS = { STR_EMPTY, "flags" };
+const StaticQName QN_CALLPERF_CONNECTION_RTT = { STR_EMPTY, "rtt" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S =
+ { STR_EMPTY, "totalBytesSent" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S =
+ { STR_EMPTY, "bytesSecondSent" };
+const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R =
+ { STR_EMPTY, "totalBytesRecv" };
+const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R =
+ { STR_EMPTY, "bytesSecondRecv" };
+const StaticQName QN_CALLPERF_CANDIDATE =
+ { NS_GOOGLE_CALLPERF_STATS, "candidate" };
+const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT = { STR_EMPTY, "endpoint" };
+const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL = { STR_EMPTY, "protocol" };
+const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS = { STR_EMPTY, "address" };
+const StaticQName QN_CALLPERF_MEDIA = { NS_GOOGLE_CALLPERF_STATS, "media" };
+const StaticQName QN_CALLPERF_MEDIA_DIRECTION = { STR_EMPTY, "direction" };
+const StaticQName QN_CALLPERF_MEDIA_SSRC = { STR_EMPTY, "SSRC" };
+const StaticQName QN_CALLPERF_MEDIA_ENERGY = { STR_EMPTY, "energy" };
+const StaticQName QN_CALLPERF_MEDIA_FIR = { STR_EMPTY, "fir" };
+const StaticQName QN_CALLPERF_MEDIA_NACK = { STR_EMPTY, "nack" };
+const StaticQName QN_CALLPERF_MEDIA_FPS = { STR_EMPTY, "fps" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK = { STR_EMPTY, "fpsNetwork" };
+const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED = { STR_EMPTY, "fpsDecoded" };
+const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE =
+ { STR_EMPTY, "jitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE =
+ { STR_EMPTY, "preferredJitterBufferSize" };
+const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY =
+ { STR_EMPTY, "totalPlayoutDelay" };
+
+// Muc invites.
+const StaticQName QN_MUC_USER_INVITE = { NS_MUC_USER, "invite" };
+
+// Multiway audio/video.
+const char NS_GOOGLE_MUC_USER[] = "google:muc#user";
+const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA =
+ { NS_GOOGLE_MUC_USER, "available-media" };
+const StaticQName QN_GOOGLE_MUC_USER_ENTRY = { NS_GOOGLE_MUC_USER, "entry" };
+const StaticQName QN_GOOGLE_MUC_USER_MEDIA = { NS_GOOGLE_MUC_USER, "media" };
+const StaticQName QN_GOOGLE_MUC_USER_TYPE = { NS_GOOGLE_MUC_USER, "type" };
+const StaticQName QN_GOOGLE_MUC_USER_SRC_ID = { NS_GOOGLE_MUC_USER, "src-id" };
+const StaticQName QN_GOOGLE_MUC_USER_STATUS = { NS_GOOGLE_MUC_USER, "status" };
+const StaticQName QN_CLIENT_VERSION = { NS_GOOGLE_MUC_USER, "client-version" };
+const StaticQName QN_LOCALE = { NS_GOOGLE_MUC_USER, "locale" };
+const StaticQName QN_LABEL = { STR_EMPTY, "label" };
+
+const char NS_GOOGLE_MUC_MEDIA[] = "google:muc#media";
+const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE =
+ { NS_GOOGLE_MUC_MEDIA, "audio-mute" };
+const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE =
+ { NS_GOOGLE_MUC_MEDIA, "video-mute" };
+const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE =
+ { NS_GOOGLE_MUC_MEDIA, "video-pause" };
+const StaticQName QN_GOOGLE_MUC_RECORDING =
+ { NS_GOOGLE_MUC_MEDIA, "recording" };
+const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK = { NS_GOOGLE_MUC_MEDIA, "block" };
+const StaticQName QN_STATE_ATTR = { STR_EMPTY, "state" };
+
+const char AUTH_MECHANISM_GOOGLE_COOKIE[] = "X-GOOGLE-COOKIE";
+const char AUTH_MECHANISM_GOOGLE_TOKEN[] = "X-GOOGLE-TOKEN";
+const char AUTH_MECHANISM_OAUTH2[] = "X-OAUTH2";
+const char AUTH_MECHANISM_PLAIN[] = "PLAIN";
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/constants.h b/webrtc/libjingle/xmpp/constants.h
new file mode 100644
index 0000000000..5c1967e5f7
--- /dev/null
+++ b/webrtc/libjingle/xmpp/constants.h
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_
+#define WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_
+
+#include <string>
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+
+namespace buzz {
+
+extern const char NS_CLIENT[];
+extern const char NS_SERVER[];
+extern const char NS_STREAM[];
+extern const char NS_XSTREAM[];
+extern const char NS_TLS[];
+extern const char NS_SASL[];
+extern const char NS_BIND[];
+extern const char NS_DIALBACK[];
+extern const char NS_SESSION[];
+extern const char NS_STANZA[];
+extern const char NS_PRIVACY[];
+extern const char NS_ROSTER[];
+extern const char NS_VCARD[];
+extern const char NS_AVATAR_HASH[];
+extern const char NS_VCARD_UPDATE[];
+extern const char STR_CLIENT[];
+extern const char STR_SERVER[];
+extern const char STR_STREAM[];
+
+extern const char STR_GET[];
+extern const char STR_SET[];
+extern const char STR_RESULT[];
+extern const char STR_ERROR[];
+
+extern const char STR_FORM[];
+extern const char STR_SUBMIT[];
+extern const char STR_TEXT_SINGLE[];
+extern const char STR_LIST_SINGLE[];
+extern const char STR_LIST_MULTI[];
+extern const char STR_HIDDEN[];
+extern const char STR_FORM_TYPE[];
+
+extern const char STR_FROM[];
+extern const char STR_TO[];
+extern const char STR_BOTH[];
+extern const char STR_REMOVE[];
+extern const char STR_TRUE[];
+
+extern const char STR_TYPE[];
+extern const char STR_NAME[];
+extern const char STR_ID[];
+extern const char STR_JID[];
+extern const char STR_SUBSCRIPTION[];
+extern const char STR_ASK[];
+extern const char STR_X[];
+extern const char STR_GOOGLE_COM[];
+extern const char STR_GMAIL_COM[];
+extern const char STR_GOOGLEMAIL_COM[];
+extern const char STR_DEFAULT_DOMAIN[];
+extern const char STR_TALK_GOOGLE_COM[];
+extern const char STR_TALKX_L_GOOGLE_COM[];
+extern const char STR_XMPP_GOOGLE_COM[];
+extern const char STR_XMPPX_L_GOOGLE_COM[];
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const char STR_VOICEMAIL[];
+extern const char STR_OUTGOINGVOICEMAIL[];
+#endif
+
+extern const char STR_UNAVAILABLE[];
+
+extern const char NS_PING[];
+extern const StaticQName QN_PING;
+
+extern const char NS_MUC_UNIQUE[];
+extern const StaticQName QN_MUC_UNIQUE_QUERY;
+extern const StaticQName QN_HANGOUT_ID;
+
+extern const char STR_GOOGLE_MUC_LOOKUP_JID[];
+extern const char STR_MUC_ROOMCONFIG_ROOMNAME[];
+extern const char STR_MUC_ROOMCONFIG_FEATURES[];
+extern const char STR_MUC_ROOM_FEATURE_ENTERPRISE[];
+extern const char STR_MUC_ROOMCONFIG[];
+extern const char STR_MUC_ROOM_FEATURE_HANGOUT[];
+extern const char STR_MUC_ROOM_FEATURE_HANGOUT_LITE[];
+extern const char STR_MUC_ROOM_FEATURE_BROADCAST[];
+extern const char STR_MUC_ROOM_FEATURE_MULTI_USER_VC[];
+extern const char STR_MUC_ROOM_FEATURE_RECORDABLE[];
+extern const char STR_MUC_ROOM_FEATURE_CUSTOM_RECORDING[];
+extern const char STR_MUC_ROOM_OWNER_PROFILE_ID[];
+extern const char STR_MUC_ROOM_FEATURE_ABUSE_RECORDABLE[];
+
+extern const char STR_ID_TYPE_CONVERSATION[];
+extern const char NS_GOOGLE_MUC_HANGOUT[];
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITE_TYPE;
+extern const StaticQName QN_ATTR_CREATE_ACTIVITY;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_PUBLIC;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_INVITEE;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_STATUS;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_NOTIFICATION_TYPE;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_HANGOUT_START_CONTEXT;
+extern const StaticQName QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID;
+
+extern const StaticQName QN_STREAM_STREAM;
+extern const StaticQName QN_STREAM_FEATURES;
+extern const StaticQName QN_STREAM_ERROR;
+
+extern const StaticQName QN_XSTREAM_BAD_FORMAT;
+extern const StaticQName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
+extern const StaticQName QN_XSTREAM_CONFLICT;
+extern const StaticQName QN_XSTREAM_CONNECTION_TIMEOUT;
+extern const StaticQName QN_XSTREAM_HOST_GONE;
+extern const StaticQName QN_XSTREAM_HOST_UNKNOWN;
+extern const StaticQName QN_XSTREAM_IMPROPER_ADDRESSIING;
+extern const StaticQName QN_XSTREAM_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_XSTREAM_INVALID_FROM;
+extern const StaticQName QN_XSTREAM_INVALID_ID;
+extern const StaticQName QN_XSTREAM_INVALID_NAMESPACE;
+extern const StaticQName QN_XSTREAM_INVALID_XML;
+extern const StaticQName QN_XSTREAM_NOT_AUTHORIZED;
+extern const StaticQName QN_XSTREAM_POLICY_VIOLATION;
+extern const StaticQName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
+extern const StaticQName QN_XSTREAM_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_XSTREAM_RESTRICTED_XML;
+extern const StaticQName QN_XSTREAM_SEE_OTHER_HOST;
+extern const StaticQName QN_XSTREAM_SYSTEM_SHUTDOWN;
+extern const StaticQName QN_XSTREAM_UNDEFINED_CONDITION;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_ENCODING;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
+extern const StaticQName QN_XSTREAM_UNSUPPORTED_VERSION;
+extern const StaticQName QN_XSTREAM_XML_NOT_WELL_FORMED;
+extern const StaticQName QN_XSTREAM_TEXT;
+
+extern const StaticQName QN_TLS_STARTTLS;
+extern const StaticQName QN_TLS_REQUIRED;
+extern const StaticQName QN_TLS_PROCEED;
+extern const StaticQName QN_TLS_FAILURE;
+
+extern const StaticQName QN_SASL_MECHANISMS;
+extern const StaticQName QN_SASL_MECHANISM;
+extern const StaticQName QN_SASL_AUTH;
+extern const StaticQName QN_SASL_CHALLENGE;
+extern const StaticQName QN_SASL_RESPONSE;
+extern const StaticQName QN_SASL_ABORT;
+extern const StaticQName QN_SASL_SUCCESS;
+extern const StaticQName QN_SASL_FAILURE;
+extern const StaticQName QN_SASL_ABORTED;
+extern const StaticQName QN_SASL_INCORRECT_ENCODING;
+extern const StaticQName QN_SASL_INVALID_AUTHZID;
+extern const StaticQName QN_SASL_INVALID_MECHANISM;
+extern const StaticQName QN_SASL_MECHANISM_TOO_WEAK;
+extern const StaticQName QN_SASL_NOT_AUTHORIZED;
+extern const StaticQName QN_SASL_TEMPORARY_AUTH_FAILURE;
+
+// These are non-standard.
+extern const char NS_GOOGLE_AUTH[];
+extern const char NS_GOOGLE_AUTH_PROTOCOL[];
+extern const StaticQName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT;
+extern const StaticQName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN;
+extern const StaticQName QN_GOOGLE_AUTH_SERVICE;
+
+extern const StaticQName QN_DIALBACK_RESULT;
+extern const StaticQName QN_DIALBACK_VERIFY;
+
+extern const StaticQName QN_STANZA_BAD_REQUEST;
+extern const StaticQName QN_STANZA_CONFLICT;
+extern const StaticQName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
+extern const StaticQName QN_STANZA_FORBIDDEN;
+extern const StaticQName QN_STANZA_GONE;
+extern const StaticQName QN_STANZA_INTERNAL_SERVER_ERROR;
+extern const StaticQName QN_STANZA_ITEM_NOT_FOUND;
+extern const StaticQName QN_STANZA_JID_MALFORMED;
+extern const StaticQName QN_STANZA_NOT_ACCEPTABLE;
+extern const StaticQName QN_STANZA_NOT_ALLOWED;
+extern const StaticQName QN_STANZA_PAYMENT_REQUIRED;
+extern const StaticQName QN_STANZA_RECIPIENT_UNAVAILABLE;
+extern const StaticQName QN_STANZA_REDIRECT;
+extern const StaticQName QN_STANZA_REGISTRATION_REQUIRED;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
+extern const StaticQName QN_STANZA_REMOTE_SERVER_TIMEOUT;
+extern const StaticQName QN_STANZA_RESOURCE_CONSTRAINT;
+extern const StaticQName QN_STANZA_SERVICE_UNAVAILABLE;
+extern const StaticQName QN_STANZA_SUBSCRIPTION_REQUIRED;
+extern const StaticQName QN_STANZA_UNDEFINED_CONDITION;
+extern const StaticQName QN_STANZA_UNEXPECTED_REQUEST;
+extern const StaticQName QN_STANZA_TEXT;
+
+extern const StaticQName QN_BIND_BIND;
+extern const StaticQName QN_BIND_RESOURCE;
+extern const StaticQName QN_BIND_JID;
+
+extern const StaticQName QN_MESSAGE;
+extern const StaticQName QN_BODY;
+extern const StaticQName QN_SUBJECT;
+extern const StaticQName QN_THREAD;
+extern const StaticQName QN_PRESENCE;
+extern const StaticQName QN_SHOW;
+extern const StaticQName QN_STATUS;
+extern const StaticQName QN_LANG;
+extern const StaticQName QN_PRIORITY;
+extern const StaticQName QN_IQ;
+extern const StaticQName QN_ERROR;
+
+extern const StaticQName QN_SERVER_MESSAGE;
+extern const StaticQName QN_SERVER_BODY;
+extern const StaticQName QN_SERVER_SUBJECT;
+extern const StaticQName QN_SERVER_THREAD;
+extern const StaticQName QN_SERVER_PRESENCE;
+extern const StaticQName QN_SERVER_SHOW;
+extern const StaticQName QN_SERVER_STATUS;
+extern const StaticQName QN_SERVER_LANG;
+extern const StaticQName QN_SERVER_PRIORITY;
+extern const StaticQName QN_SERVER_IQ;
+extern const StaticQName QN_SERVER_ERROR;
+
+extern const StaticQName QN_SESSION_SESSION;
+
+extern const StaticQName QN_PRIVACY_QUERY;
+extern const StaticQName QN_PRIVACY_ACTIVE;
+extern const StaticQName QN_PRIVACY_DEFAULT;
+extern const StaticQName QN_PRIVACY_LIST;
+extern const StaticQName QN_PRIVACY_ITEM;
+extern const StaticQName QN_PRIVACY_IQ;
+extern const StaticQName QN_PRIVACY_MESSAGE;
+extern const StaticQName QN_PRIVACY_PRESENCE_IN;
+extern const StaticQName QN_PRIVACY_PRESENCE_OUT;
+
+extern const StaticQName QN_ROSTER_QUERY;
+extern const StaticQName QN_ROSTER_ITEM;
+extern const StaticQName QN_ROSTER_GROUP;
+
+extern const StaticQName QN_VCARD;
+extern const StaticQName QN_VCARD_FN;
+extern const StaticQName QN_VCARD_PHOTO;
+extern const StaticQName QN_VCARD_PHOTO_BINVAL;
+extern const StaticQName QN_VCARD_AVATAR_HASH;
+extern const StaticQName QN_VCARD_AVATAR_HASH_MODIFIED;
+
+#if defined(FEATURE_ENABLE_PSTN)
+extern const StaticQName QN_VCARD_TEL;
+extern const StaticQName QN_VCARD_VOICE;
+extern const StaticQName QN_VCARD_HOME;
+extern const StaticQName QN_VCARD_WORK;
+extern const StaticQName QN_VCARD_CELL;
+extern const StaticQName QN_VCARD_NUMBER;
+#endif
+
+#if defined(FEATURE_ENABLE_RICHPROFILES)
+extern const StaticQName QN_USER_PROFILE_QUERY;
+extern const StaticQName QN_USER_PROFILE_URL;
+
+extern const StaticQName QN_ATOM_FEED;
+extern const StaticQName QN_ATOM_ENTRY;
+extern const StaticQName QN_ATOM_TITLE;
+extern const StaticQName QN_ATOM_ID;
+extern const StaticQName QN_ATOM_MODIFIED;
+extern const StaticQName QN_ATOM_IMAGE;
+extern const StaticQName QN_ATOM_LINK;
+extern const StaticQName QN_ATOM_HREF;
+#endif
+
+extern const StaticQName QN_XML_LANG;
+
+extern const StaticQName QN_ENCODING;
+extern const StaticQName QN_VERSION;
+extern const StaticQName QN_TO;
+extern const StaticQName QN_FROM;
+extern const StaticQName QN_TYPE;
+extern const StaticQName QN_ID;
+extern const StaticQName QN_CODE;
+extern const StaticQName QN_NAME;
+extern const StaticQName QN_VALUE;
+extern const StaticQName QN_ACTION;
+extern const StaticQName QN_ORDER;
+extern const StaticQName QN_MECHANISM;
+extern const StaticQName QN_ASK;
+extern const StaticQName QN_JID;
+extern const StaticQName QN_NICK;
+extern const StaticQName QN_SUBSCRIPTION;
+extern const StaticQName QN_TITLE1;
+extern const StaticQName QN_TITLE2;
+extern const StaticQName QN_AFFILIATION;
+extern const StaticQName QN_ROLE;
+extern const StaticQName QN_TIME;
+
+extern const StaticQName QN_XMLNS_CLIENT;
+extern const StaticQName QN_XMLNS_SERVER;
+extern const StaticQName QN_XMLNS_STREAM;
+
+// Presence
+extern const char STR_SHOW_AWAY[];
+extern const char STR_SHOW_CHAT[];
+extern const char STR_SHOW_DND[];
+extern const char STR_SHOW_XA[];
+extern const char STR_SHOW_OFFLINE[];
+
+extern const char NS_GOOGLE_PSTN_CONFERENCE[];
+extern const StaticQName QN_GOOGLE_PSTN_CONFERENCE_STATUS;
+extern const StaticQName QN_ATTR_STATUS;
+
+// Presence connection status
+extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTING[];
+extern const char STR_PSTN_CONFERENCE_STATUS_JOINING[];
+extern const char STR_PSTN_CONFERENCE_STATUS_CONNECTED[];
+extern const char STR_PSTN_CONFERENCE_STATUS_HANGUP[];
+
+// Subscription
+extern const char STR_SUBSCRIBE[];
+extern const char STR_SUBSCRIBED[];
+extern const char STR_UNSUBSCRIBE[];
+extern const char STR_UNSUBSCRIBED[];
+
+// Google Invite
+extern const char NS_GOOGLE_SUBSCRIBE[];
+extern const StaticQName QN_INVITATION;
+extern const StaticQName QN_INVITE_NAME;
+extern const StaticQName QN_INVITE_SUBJECT;
+extern const StaticQName QN_INVITE_MESSAGE;
+
+// Kick
+extern const char NS_GOOGLE_MUC_ADMIN[];
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY;
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM;
+extern const StaticQName QN_GOOGLE_MUC_ADMIN_QUERY_ITEM_REASON;
+
+// PubSub: http://xmpp.org/extensions/xep-0060.html
+extern const char NS_PUBSUB[];
+extern const StaticQName QN_PUBSUB;
+extern const StaticQName QN_PUBSUB_ITEMS;
+extern const StaticQName QN_PUBSUB_ITEM;
+extern const StaticQName QN_PUBSUB_PUBLISH;
+extern const StaticQName QN_PUBSUB_RETRACT;
+extern const StaticQName QN_ATTR_PUBLISHER;
+
+extern const char NS_PUBSUB_EVENT[];
+extern const StaticQName QN_NODE;
+extern const StaticQName QN_PUBSUB_EVENT;
+extern const StaticQName QN_PUBSUB_EVENT_ITEMS;
+extern const StaticQName QN_PUBSUB_EVENT_ITEM;
+extern const StaticQName QN_PUBSUB_EVENT_RETRACT;
+extern const StaticQName QN_NOTIFY;
+
+extern const char NS_PRESENTER[];
+extern const StaticQName QN_PRESENTER_PRESENTER;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ITEM;
+extern const StaticQName QN_PRESENTER_PRESENTATION_TYPE;
+extern const StaticQName QN_PRESENTER_PRESENTATION_ID;
+
+// JEP 0030
+extern const StaticQName QN_CATEGORY;
+extern const StaticQName QN_VAR;
+extern const char NS_DISCO_INFO[];
+extern const char NS_DISCO_ITEMS[];
+
+extern const StaticQName QN_DISCO_INFO_QUERY;
+extern const StaticQName QN_DISCO_IDENTITY;
+extern const StaticQName QN_DISCO_FEATURE;
+
+extern const StaticQName QN_DISCO_ITEMS_QUERY;
+extern const StaticQName QN_DISCO_ITEM;
+
+// JEP 0020
+extern const char NS_FEATURE[];
+extern const StaticQName QN_FEATURE_FEATURE;
+
+// JEP 0004
+extern const char NS_XDATA[];
+extern const StaticQName QN_XDATA_X;
+extern const StaticQName QN_XDATA_INSTRUCTIONS;
+extern const StaticQName QN_XDATA_TITLE;
+extern const StaticQName QN_XDATA_FIELD;
+extern const StaticQName QN_XDATA_REPORTED;
+extern const StaticQName QN_XDATA_ITEM;
+extern const StaticQName QN_XDATA_DESC;
+extern const StaticQName QN_XDATA_REQUIRED;
+extern const StaticQName QN_XDATA_VALUE;
+extern const StaticQName QN_XDATA_OPTION;
+
+// JEP 0045
+extern const char NS_MUC[];
+extern const StaticQName QN_MUC_X;
+extern const StaticQName QN_MUC_ITEM;
+extern const StaticQName QN_MUC_AFFILIATION;
+extern const StaticQName QN_MUC_ROLE;
+extern const StaticQName QN_CLIENT_VERSION;
+extern const StaticQName QN_LOCALE;
+extern const char STR_AFFILIATION_NONE[];
+extern const char STR_ROLE_PARTICIPANT[];
+
+extern const char NS_GOOGLE_SESSION[];
+extern const StaticQName QN_GOOGLE_USER_ID;
+extern const StaticQName QN_GOOGLE_CIRCLE_ID;
+extern const StaticQName QN_GOOGLE_SESSION_BLOCKED;
+extern const StaticQName QN_GOOGLE_SESSION_BLOCKING;
+
+extern const char NS_MUC_OWNER[];
+extern const StaticQName QN_MUC_OWNER_QUERY;
+
+extern const char NS_MUC_USER[];
+extern const StaticQName QN_MUC_USER_CONTINUE;
+extern const StaticQName QN_MUC_USER_X;
+extern const StaticQName QN_MUC_USER_ITEM;
+extern const StaticQName QN_MUC_USER_STATUS;
+extern const StaticQName QN_MUC_USER_REASON;
+extern const StaticQName QN_MUC_USER_ABUSE_VIOLATION;
+
+// JEP 0055 - Jabber Search
+extern const char NS_SEARCH[];
+extern const StaticQName QN_SEARCH_QUERY;
+extern const StaticQName QN_SEARCH_ITEM;
+extern const StaticQName QN_SEARCH_ROOM_NAME;
+extern const StaticQName QN_SEARCH_ROOM_JID;
+extern const StaticQName QN_SEARCH_ROOM_DOMAIN;
+extern const StaticQName QN_SEARCH_HANGOUT_ID;
+extern const StaticQName QN_SEARCH_EXTERNAL_ID;
+
+// JEP 0115
+extern const char NS_CAPS[];
+extern const StaticQName QN_CAPS_C;
+extern const StaticQName QN_VER;
+extern const StaticQName QN_EXT;
+
+
+// Avatar - JEP 0153
+extern const char kNSVCard[];
+extern const StaticQName kQnVCardX;
+extern const StaticQName kQnVCardPhoto;
+
+// JEP 0172 User Nickname
+extern const char NS_NICKNAME[];
+extern const StaticQName QN_NICKNAME;
+
+// JEP 0085 chat state
+extern const char NS_CHATSTATE[];
+extern const StaticQName QN_CS_ACTIVE;
+extern const StaticQName QN_CS_COMPOSING;
+extern const StaticQName QN_CS_PAUSED;
+extern const StaticQName QN_CS_INACTIVE;
+extern const StaticQName QN_CS_GONE;
+
+// JEP 0091 Delayed Delivery
+extern const char kNSDelay[];
+extern const StaticQName kQnDelayX;
+extern const StaticQName kQnStamp;
+
+// Google time stamping (higher resolution)
+extern const char kNSTimestamp[];
+extern const StaticQName kQnTime;
+extern const StaticQName kQnMilliseconds;
+
+extern const char NS_JINGLE_INFO[];
+extern const StaticQName QN_JINGLE_INFO_QUERY;
+extern const StaticQName QN_JINGLE_INFO_STUN;
+extern const StaticQName QN_JINGLE_INFO_RELAY;
+extern const StaticQName QN_JINGLE_INFO_SERVER;
+extern const StaticQName QN_JINGLE_INFO_TOKEN;
+extern const StaticQName QN_JINGLE_INFO_HOST;
+extern const StaticQName QN_JINGLE_INFO_TCP;
+extern const StaticQName QN_JINGLE_INFO_UDP;
+extern const StaticQName QN_JINGLE_INFO_TCPSSL;
+
+extern const char NS_GOOGLE_CALLPERF_STATS[];
+extern const StaticQName QN_CALLPERF_STATS;
+extern const StaticQName QN_CALLPERF_SESSIONID;
+extern const StaticQName QN_CALLPERF_LOCALUSER;
+extern const StaticQName QN_CALLPERF_REMOTEUSER;
+extern const StaticQName QN_CALLPERF_STARTTIME;
+extern const StaticQName QN_CALLPERF_CALL_LENGTH;
+extern const StaticQName QN_CALLPERF_CALL_ACCEPTED;
+extern const StaticQName QN_CALLPERF_CALL_ERROR_CODE;
+extern const StaticQName QN_CALLPERF_TERMINATE_CODE;
+extern const StaticQName QN_CALLPERF_DATAPOINT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_TIME;
+extern const StaticQName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CUM_LOST;
+extern const StaticQName QN_CALLPERF_DATAPOINT_EXT_MAX;
+extern const StaticQName QN_CALLPERF_DATAPOINT_JITTER;
+extern const StaticQName QN_CALLPERF_DATAPOINT_RTT;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_R;
+extern const StaticQName QN_CALLPERF_DATAPOINT_BYTES_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PACKETS_S;
+extern const StaticQName QN_CALLPERF_DATAPOINT_PROCESS_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_SYSTEM_CPU;
+extern const StaticQName QN_CALLPERF_DATAPOINT_CPUS;
+extern const StaticQName QN_CALLPERF_CONNECTION;
+extern const StaticQName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
+extern const StaticQName QN_CALLPERF_CONNECTION_FLAGS;
+extern const StaticQName QN_CALLPERF_CONNECTION_RTT;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_S;
+extern const StaticQName QN_CALLPERF_CONNECTION_TOTAL_BYTES_R;
+extern const StaticQName QN_CALLPERF_CONNECTION_BYTES_SECOND_R;
+extern const StaticQName QN_CALLPERF_CANDIDATE;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ENDPOINT;
+extern const StaticQName QN_CALLPERF_CANDIDATE_PROTOCOL;
+extern const StaticQName QN_CALLPERF_CANDIDATE_ADDRESS;
+extern const StaticQName QN_CALLPERF_MEDIA;
+extern const StaticQName QN_CALLPERF_MEDIA_DIRECTION;
+extern const StaticQName QN_CALLPERF_MEDIA_SSRC;
+extern const StaticQName QN_CALLPERF_MEDIA_ENERGY;
+extern const StaticQName QN_CALLPERF_MEDIA_FIR;
+extern const StaticQName QN_CALLPERF_MEDIA_NACK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_NETWORK;
+extern const StaticQName QN_CALLPERF_MEDIA_FPS_DECODED;
+extern const StaticQName QN_CALLPERF_MEDIA_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_PREFERRED_JITTER_BUFFER_SIZE;
+extern const StaticQName QN_CALLPERF_MEDIA_TOTAL_PLAYOUT_DELAY;
+
+// Muc invites.
+extern const StaticQName QN_MUC_USER_INVITE;
+
+// Multiway audio/video.
+extern const char NS_GOOGLE_MUC_USER[];
+extern const StaticQName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_ENTRY;
+extern const StaticQName QN_GOOGLE_MUC_USER_MEDIA;
+extern const StaticQName QN_GOOGLE_MUC_USER_TYPE;
+extern const StaticQName QN_GOOGLE_MUC_USER_SRC_ID;
+extern const StaticQName QN_GOOGLE_MUC_USER_STATUS;
+extern const StaticQName QN_LABEL;
+
+extern const char NS_GOOGLE_MUC_MEDIA[];
+extern const StaticQName QN_GOOGLE_MUC_AUDIO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_VIDEO_MUTE;
+extern const StaticQName QN_GOOGLE_MUC_VIDEO_PAUSE;
+extern const StaticQName QN_GOOGLE_MUC_RECORDING;
+extern const StaticQName QN_GOOGLE_MUC_MEDIA_BLOCK;
+extern const StaticQName QN_STATE_ATTR;
+
+
+extern const char AUTH_MECHANISM_GOOGLE_COOKIE[];
+extern const char AUTH_MECHANISM_GOOGLE_TOKEN[];
+extern const char AUTH_MECHANISM_OAUTH2[];
+extern const char AUTH_MECHANISM_PLAIN[];
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_CONSTANTS_H_
diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.cc b/webrtc/libjingle/xmpp/discoitemsquerytask.cc
new file mode 100644
index 0000000000..765ee14397
--- /dev/null
+++ b/webrtc/libjingle/xmpp/discoitemsquerytask.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/discoitemsquerytask.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace buzz {
+
+DiscoItemsQueryTask::DiscoItemsQueryTask(XmppTaskParentInterface* parent,
+ const Jid& to,
+ const std::string& node)
+ : IqTask(parent, STR_GET, to, MakeRequest(node)) {
+}
+
+XmlElement* DiscoItemsQueryTask::MakeRequest(const std::string& node) {
+ XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true);
+ if (!node.empty()) {
+ element->AddAttr(QN_NODE, node);
+ }
+ return element;
+}
+
+void DiscoItemsQueryTask::HandleResult(const XmlElement* stanza) {
+ const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY);
+ if (query) {
+ std::vector<DiscoItem> items;
+ for (const buzz::XmlChild* child = query->FirstChild(); child;
+ child = child->NextChild()) {
+ DiscoItem item;
+ const buzz::XmlElement* child_element = child->AsElement();
+ if (ParseItem(child_element, &item)) {
+ items.push_back(item);
+ }
+ }
+ SignalResult(items);
+ } else {
+ SignalError(this, NULL);
+ }
+}
+
+bool DiscoItemsQueryTask::ParseItem(const XmlElement* element,
+ DiscoItem* item) {
+ if (element->HasAttr(QN_JID)) {
+ return false;
+ }
+
+ item->jid = element->Attr(QN_JID);
+ item->name = element->Attr(QN_NAME);
+ item->node = element->Attr(QN_NODE);
+ return true;
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/discoitemsquerytask.h b/webrtc/libjingle/xmpp/discoitemsquerytask.h
new file mode 100644
index 0000000000..62e862e193
--- /dev/null
+++ b/webrtc/libjingle/xmpp/discoitemsquerytask.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// Fires a disco items query, such as the following example:
+//
+// <iq type='get'
+// from='foo@gmail.com/asdf'
+// to='bar@google.com'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items'
+// node='blah '/>
+// </iq>
+//
+// Sample response:
+//
+// <iq type='result'
+// from=' hendriks@google.com'
+// to='rsturgell@google.com/asdf'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items '
+// node='blah'>
+// <item something='somethingelse'/>
+// </query>
+// </iq>
+
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct DiscoItem {
+ std::string jid;
+ std::string node;
+ std::string name;
+};
+
+class DiscoItemsQueryTask : public IqTask {
+ public:
+ DiscoItemsQueryTask(XmppTaskParentInterface* parent,
+ const Jid& to, const std::string& node);
+
+ sigslot::signal1<std::vector<DiscoItem> > SignalResult;
+
+ private:
+ static XmlElement* MakeRequest(const std::string& node);
+ virtual void HandleResult(const XmlElement* result);
+ static bool ParseItem(const XmlElement* element, DiscoItem* item);
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_DISCOITEMSQUERYTASK_H_
diff --git a/webrtc/libjingle/xmpp/fakexmppclient.h b/webrtc/libjingle/xmpp/fakexmppclient.h
new file mode 100644
index 0000000000..453a7c86f1
--- /dev/null
+++ b/webrtc/libjingle/xmpp/fakexmppclient.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+// A fake XmppClient for use in unit tests.
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_
+#define WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class XmlElement;
+
+class FakeXmppClient : public XmppTaskParentInterface,
+ public XmppClientInterface {
+ public:
+ explicit FakeXmppClient(rtc::TaskParent* parent)
+ : XmppTaskParentInterface(parent) {
+ }
+
+ // As XmppTaskParentInterface
+ virtual XmppClientInterface* GetClient() {
+ return this;
+ }
+
+ virtual int ProcessStart() {
+ return STATE_RESPONSE;
+ }
+
+ // As XmppClientInterface
+ virtual XmppEngine::State GetState() const {
+ return XmppEngine::STATE_OPEN;
+ }
+
+ virtual const Jid& jid() const {
+ return jid_;
+ }
+
+ virtual std::string NextId() {
+ // Implement if needed for tests.
+ return "0";
+ }
+
+ virtual XmppReturnStatus SendStanza(const XmlElement* stanza) {
+ sent_stanzas_.push_back(stanza);
+ return XMPP_RETURN_OK;
+ }
+
+ const std::vector<const XmlElement*>& sent_stanzas() {
+ return sent_stanzas_;
+ }
+
+ virtual XmppReturnStatus SendStanzaError(
+ const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text) {
+ // Implement if needed for tests.
+ return XMPP_RETURN_OK;
+ }
+
+ virtual void AddXmppTask(XmppTask* task,
+ XmppEngine::HandlerLevel level) {
+ tasks_.push_back(task);
+ }
+
+ virtual void RemoveXmppTask(XmppTask* task) {
+ std::remove(tasks_.begin(), tasks_.end(), task);
+ }
+
+ // As FakeXmppClient
+ void set_jid(const Jid& jid) {
+ jid_ = jid;
+ }
+
+ // Takes ownership of stanza.
+ void HandleStanza(XmlElement* stanza) {
+ for (std::vector<XmppTask*>::iterator task = tasks_.begin();
+ task != tasks_.end(); ++task) {
+ if ((*task)->HandleStanza(stanza)) {
+ delete stanza;
+ return;
+ }
+ }
+ delete stanza;
+ }
+
+ private:
+ Jid jid_;
+ std::vector<XmppTask*> tasks_;
+ std::vector<const XmlElement*> sent_stanzas_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_FAKEXMPPCLIENT_H_
diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc
new file mode 100644
index 0000000000..db1ac31491
--- /dev/null
+++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.cc
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/logging.h"
+
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+namespace {
+const char kPresenting[] = "s";
+const char kNotPresenting[] = "o";
+
+} // namespace
+
+// A simple serialiazer where presence of item => true, lack of item
+// => false.
+class BoolStateSerializer : public PubSubStateSerializer<bool> {
+ virtual XmlElement* Write(const QName& state_name, const bool& state) {
+ if (!state) {
+ return NULL;
+ }
+
+ return new XmlElement(state_name, true);
+ }
+
+ virtual void Parse(const XmlElement* state_elem, bool *state_out) {
+ *state_out = state_elem != NULL;
+ }
+};
+
+class PresenterStateClient : public PubSubStateClient<bool> {
+ public:
+ PresenterStateClient(const std::string& publisher_nick,
+ PubSubClient* client,
+ const QName& state_name,
+ bool default_state)
+ : PubSubStateClient<bool>(
+ publisher_nick, client, state_name, default_state,
+ new PublishedNickKeySerializer(), NULL) {
+ }
+
+ virtual void Publish(const std::string& published_nick,
+ const bool& state,
+ std::string* task_id_out) {
+ XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
+ presenter_elem->AddAttr(QN_NICK, published_nick);
+
+ XmlElement* presentation_item_elem =
+ new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
+ const std::string& presentation_type = state ? kPresenting : kNotPresenting;
+ presentation_item_elem->AddAttr(
+ QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
+
+ // The Presenter state is kind of dumb in that it doesn't always use
+ // retracts. It relies on setting the "type" to a special value.
+ std::string itemid = published_nick;
+ std::vector<XmlElement*> children;
+ children.push_back(presenter_elem);
+ children.push_back(presentation_item_elem);
+ client()->PublishItem(itemid, children, task_id_out);
+ }
+
+ protected:
+ virtual bool ParseStateItem(const PubSubItem& item,
+ StateItemInfo* info_out,
+ bool* state_out) {
+ const XmlElement* presenter_elem =
+ item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
+ const XmlElement* presentation_item_elem =
+ item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
+ if (presentation_item_elem == NULL || presenter_elem == NULL) {
+ return false;
+ }
+
+ info_out->publisher_nick =
+ client()->GetPublisherNickFromPubSubItem(item.elem);
+ info_out->published_nick = presenter_elem->Attr(QN_NICK);
+ *state_out = (presentation_item_elem->Attr(
+ QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
+ return true;
+ }
+
+ virtual bool StatesEqual(const bool& state1, const bool& state2) {
+ // Make every item trigger an event, even if state doesn't change.
+ return false;
+ }
+};
+
+HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
+ const Jid& mucjid,
+ const std::string& nick)
+ : mucjid_(mucjid),
+ nick_(nick) {
+ presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
+ presenter_client_->SignalRequestError.connect(
+ this, &HangoutPubSubClient::OnPresenterRequestError);
+
+ media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
+ media_client_->SignalRequestError.connect(
+ this, &HangoutPubSubClient::OnMediaRequestError);
+
+ presenter_state_client_.reset(new PresenterStateClient(
+ nick_, presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+ presenter_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnPresenterStateChange);
+ presenter_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnPresenterPublishResult);
+ presenter_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnPresenterPublishError);
+
+ audio_mute_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
+ // Can't just repeat because we need to watch for remote mutes.
+ audio_mute_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnAudioMuteStateChange);
+ audio_mute_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnAudioMutePublishResult);
+ audio_mute_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnAudioMutePublishError);
+
+ video_mute_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_MUTE, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
+ // Can't just repeat because we need to watch for remote mutes.
+ video_mute_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnVideoMuteStateChange);
+ video_mute_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnVideoMutePublishResult);
+ video_mute_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnVideoMutePublishError);
+
+ video_pause_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_VIDEO_PAUSE, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
+ video_pause_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnVideoPauseStateChange);
+ video_pause_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnVideoPausePublishResult);
+ video_pause_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnVideoPausePublishError);
+
+ recording_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
+ new PublishedNickKeySerializer(), new BoolStateSerializer()));
+ recording_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnRecordingStateChange);
+ recording_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnRecordingPublishResult);
+ recording_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnRecordingPublishError);
+
+ media_block_state_client_.reset(new PubSubStateClient<bool>(
+ nick_, media_client_.get(), QN_GOOGLE_MUC_MEDIA_BLOCK, false,
+ new PublisherAndPublishedNicksKeySerializer(),
+ new BoolStateSerializer()));
+ media_block_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnMediaBlockStateChange);
+ media_block_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnMediaBlockPublishResult);
+ media_block_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnMediaBlockPublishError);
+}
+
+HangoutPubSubClient::~HangoutPubSubClient() {
+}
+
+void HangoutPubSubClient::RequestAll() {
+ presenter_client_->RequestItems();
+ media_client_->RequestItems();
+}
+
+void HangoutPubSubClient::OnPresenterRequestError(
+ PubSubClient* client, const XmlElement* stanza) {
+ SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::OnMediaRequestError(
+ PubSubClient* client, const XmlElement* stanza) {
+ SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::PublishPresenterState(
+ bool presenting, std::string* task_id_out) {
+ presenter_state_client_->Publish(nick_, presenting, task_id_out);
+}
+
+void HangoutPubSubClient::PublishAudioMuteState(
+ bool muted, std::string* task_id_out) {
+ audio_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishVideoMuteState(
+ bool muted, std::string* task_id_out) {
+ video_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishVideoPauseState(
+ bool paused, std::string* task_id_out) {
+ video_pause_state_client_->Publish(nick_, paused, task_id_out);
+}
+
+void HangoutPubSubClient::PublishRecordingState(
+ bool recording, std::string* task_id_out) {
+ recording_state_client_->Publish(nick_, recording, task_id_out);
+}
+
+// Remote mute is accomplished by setting another client's mute state.
+void HangoutPubSubClient::RemoteMute(
+ const std::string& mutee_nick, std::string* task_id_out) {
+ audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
+}
+
+// Block media is accomplished by setting another client's block
+// state, kind of like remote mute.
+void HangoutPubSubClient::BlockMedia(
+ const std::string& blockee_nick, std::string* task_id_out) {
+ media_block_state_client_->Publish(blockee_nick, true, task_id_out);
+}
+
+void HangoutPubSubClient::OnPresenterStateChange(
+ const PubSubStateChange<bool>& change) {
+ SignalPresenterStateChange(
+ change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnPresenterPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishPresenterResult(task_id);
+}
+
+void HangoutPubSubClient::OnPresenterPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishPresenterError(task_id, stanza);
+}
+
+// Since a remote mute is accomplished by another client setting our
+// mute state, if our state changes to muted, we should mute ourselves.
+// Note that remote un-muting is disallowed by the RoomServer.
+void HangoutPubSubClient::OnAudioMuteStateChange(
+ const PubSubStateChange<bool>& change) {
+ bool was_muted = change.old_state;
+ bool is_muted = change.new_state;
+ bool remote_action = (!change.publisher_nick.empty() &&
+ (change.publisher_nick != change.published_nick));
+
+ if (remote_action) {
+ const std::string& mutee_nick = change.published_nick;
+ const std::string& muter_nick = change.publisher_nick;
+ if (!is_muted) {
+ // The server should prevent remote un-mute.
+ LOG(LS_WARNING) << muter_nick << " remote unmuted " << mutee_nick;
+ return;
+ }
+ bool should_mute_locally = (mutee_nick == nick_);
+ SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
+ }
+ SignalAudioMuteStateChange(change.published_nick, was_muted, is_muted);
+}
+
+const std::string GetAudioMuteNickFromItem(const XmlElement* item) {
+ if (item != NULL) {
+ const XmlElement* audio_mute_state =
+ item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
+ if (audio_mute_state != NULL) {
+ return audio_mute_state->Attr(QN_NICK);
+ }
+ }
+ return std::string();
+}
+
+const std::string GetBlockeeNickFromItem(const XmlElement* item) {
+ if (item != NULL) {
+ const XmlElement* media_block_state =
+ item->FirstNamed(QN_GOOGLE_MUC_MEDIA_BLOCK);
+ if (media_block_state != NULL) {
+ return media_block_state->Attr(QN_NICK);
+ }
+ }
+ return std::string();
+}
+
+void HangoutPubSubClient::OnAudioMutePublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+ if (mutee_nick != nick_) {
+ SignalRemoteMuteResult(task_id, mutee_nick);
+ } else {
+ SignalPublishAudioMuteResult(task_id);
+ }
+}
+
+void HangoutPubSubClient::OnAudioMutePublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+ if (mutee_nick != nick_) {
+ SignalRemoteMuteError(task_id, mutee_nick, stanza);
+ } else {
+ SignalPublishAudioMuteError(task_id, stanza);
+ }
+}
+
+void HangoutPubSubClient::OnVideoMuteStateChange(
+ const PubSubStateChange<bool>& change) {
+ SignalVideoMuteStateChange(
+ change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnVideoMutePublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishVideoMuteResult(task_id);
+}
+
+void HangoutPubSubClient::OnVideoMutePublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishVideoMuteError(task_id, stanza);
+}
+
+void HangoutPubSubClient::OnVideoPauseStateChange(
+ const PubSubStateChange<bool>& change) {
+ SignalVideoPauseStateChange(
+ change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnVideoPausePublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishVideoPauseResult(task_id);
+}
+
+void HangoutPubSubClient::OnVideoPausePublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishVideoPauseError(task_id, stanza);
+}
+
+void HangoutPubSubClient::OnRecordingStateChange(
+ const PubSubStateChange<bool>& change) {
+ SignalRecordingStateChange(
+ change.published_nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnRecordingPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishRecordingResult(task_id);
+}
+
+void HangoutPubSubClient::OnRecordingPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishRecordingError(task_id, stanza);
+}
+
+void HangoutPubSubClient::OnMediaBlockStateChange(
+ const PubSubStateChange<bool>& change) {
+ const std::string& blockee_nick = change.published_nick;
+ const std::string& blocker_nick = change.publisher_nick;
+
+ bool was_blockee = change.old_state;
+ bool is_blockee = change.new_state;
+ if (!was_blockee && is_blockee) {
+ SignalMediaBlock(blockee_nick, blocker_nick);
+ }
+ // TODO: Should we bother signaling unblock? Currently
+ // it isn't allowed, but it might happen when a participant leaves
+ // the room and the item is retracted.
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+ SignalMediaBlockResult(task_id, blockee_nick);
+}
+
+void HangoutPubSubClient::OnMediaBlockPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ const std::string& blockee_nick = GetBlockeeNickFromItem(item);
+ SignalMediaBlockError(task_id, blockee_nick, stanza);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient.h b/webrtc/libjingle/xmpp/hangoutpubsubclient.h
new file mode 100644
index 0000000000..2586768e2b
--- /dev/null
+++ b/webrtc/libjingle/xmpp/hangoutpubsubclient.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_
+#define WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubclient.h"
+#include "webrtc/libjingle/xmpp/pubsubstateclient.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sigslotrepeater.h"
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// A client tied to a specific MUC jid and local nick. Provides ways
+// to get updates and publish state and events. Must call
+// RequestAll() to start getting updates.
+class HangoutPubSubClient : public sigslot::has_slots<> {
+ public:
+ HangoutPubSubClient(XmppTaskParentInterface* parent,
+ const Jid& mucjid,
+ const std::string& nick);
+ ~HangoutPubSubClient();
+ const Jid& mucjid() const { return mucjid_; }
+ const std::string& nick() const { return nick_; }
+
+ // Requests all of the different states and subscribes for updates.
+ // Responses and updates will be signalled via the various signals.
+ void RequestAll();
+ // Signal (nick, was_presenting, is_presenting)
+ sigslot::signal3<const std::string&, bool, bool> SignalPresenterStateChange;
+ // Signal (nick, was_muted, is_muted)
+ sigslot::signal3<const std::string&, bool, bool> SignalAudioMuteStateChange;
+ // Signal (nick, was_muted, is_muted)
+ sigslot::signal3<const std::string&, bool, bool> SignalVideoMuteStateChange;
+ // Signal (nick, was_paused, is_paused)
+ sigslot::signal3<const std::string&, bool, bool> SignalVideoPauseStateChange;
+ // Signal (nick, was_recording, is_recording)
+ sigslot::signal3<const std::string&, bool, bool> SignalRecordingStateChange;
+ // Signal (mutee_nick, muter_nick, should_mute_locally)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ bool> SignalRemoteMute;
+ // Signal (blockee_nick, blocker_nick)
+ sigslot::signal2<const std::string&, const std::string&> SignalMediaBlock;
+
+ // Signal (node, error stanza)
+ sigslot::signal2<const std::string&, const XmlElement*> SignalRequestError;
+
+ // On each of these, provide a task_id_out to get the task_id, which
+ // can be correlated to the error and result signals.
+ void PublishPresenterState(
+ bool presenting, std::string* task_id_out = NULL);
+ void PublishAudioMuteState(
+ bool muted, std::string* task_id_out = NULL);
+ void PublishVideoMuteState(
+ bool muted, std::string* task_id_out = NULL);
+ void PublishVideoPauseState(
+ bool paused, std::string* task_id_out = NULL);
+ void PublishRecordingState(
+ bool recording, std::string* task_id_out = NULL);
+ void RemoteMute(
+ const std::string& mutee_nick, std::string* task_id_out = NULL);
+ void BlockMedia(
+ const std::string& blockee_nick, std::string* task_id_out = NULL);
+
+ // Signal task_id
+ sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
+ sigslot::signal1<const std::string&> SignalPublishVideoMuteResult;
+ sigslot::signal1<const std::string&> SignalPublishVideoPauseResult;
+ sigslot::signal1<const std::string&> SignalPublishPresenterResult;
+ sigslot::signal1<const std::string&> SignalPublishRecordingResult;
+ // Signal (task_id, mutee_nick)
+ sigslot::signal2<const std::string&,
+ const std::string&> SignalRemoteMuteResult;
+ // Signal (task_id, blockee_nick)
+ sigslot::signal2<const std::string&,
+ const std::string&> SignalMediaBlockResult;
+
+ // Signal (task_id, error stanza)
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishAudioMuteError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishVideoMuteError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishVideoPauseError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishPresenterError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishRecordingError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishMediaBlockError;
+ // Signal (task_id, mutee_nick, error stanza)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ const XmlElement*> SignalRemoteMuteError;
+ // Signal (task_id, blockee_nick, error stanza)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ const XmlElement*> SignalMediaBlockError;
+
+
+ private:
+ void OnPresenterRequestError(PubSubClient* client,
+ const XmlElement* stanza);
+ void OnMediaRequestError(PubSubClient* client,
+ const XmlElement* stanza);
+
+ void OnPresenterStateChange(const PubSubStateChange<bool>& change);
+ void OnPresenterPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnPresenterPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnAudioMuteStateChange(const PubSubStateChange<bool>& change);
+ void OnAudioMutePublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnAudioMutePublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnVideoMuteStateChange(const PubSubStateChange<bool>& change);
+ void OnVideoMutePublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnVideoMutePublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnVideoPauseStateChange(const PubSubStateChange<bool>& change);
+ void OnVideoPausePublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnVideoPausePublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnRecordingStateChange(const PubSubStateChange<bool>& change);
+ void OnRecordingPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnRecordingPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnMediaBlockStateChange(const PubSubStateChange<bool>& change);
+ void OnMediaBlockPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnMediaBlockPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ Jid mucjid_;
+ std::string nick_;
+ rtc::scoped_ptr<PubSubClient> media_client_;
+ rtc::scoped_ptr<PubSubClient> presenter_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > presenter_state_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > audio_mute_state_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > video_mute_state_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > video_pause_state_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > recording_state_client_;
+ rtc::scoped_ptr<PubSubStateClient<bool> > media_block_state_client_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_HANGOUTPUBSUBCLIENT_H_
diff --git a/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc
new file mode 100644
index 0000000000..7c6ea58f2d
--- /dev/null
+++ b/webrtc/libjingle/xmpp/hangoutpubsubclient_unittest.cc
@@ -0,0 +1,753 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/hangoutpubsubclient.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class TestHangoutPubSubListener : public sigslot::has_slots<> {
+ public:
+ TestHangoutPubSubListener() :
+ request_error_count(0),
+ publish_audio_mute_error_count(0),
+ publish_video_mute_error_count(0),
+ publish_video_pause_error_count(0),
+ publish_presenter_error_count(0),
+ publish_recording_error_count(0),
+ remote_mute_error_count(0) {
+ }
+
+ void OnPresenterStateChange(
+ const std::string& nick, bool was_presenting, bool is_presenting) {
+ last_presenter_nick = nick;
+ last_was_presenting = was_presenting;
+ last_is_presenting = is_presenting;
+ }
+
+ void OnAudioMuteStateChange(
+ const std::string& nick, bool was_muted, bool is_muted) {
+ last_audio_muted_nick = nick;
+ last_was_audio_muted = was_muted;
+ last_is_audio_muted = is_muted;
+ }
+
+ void OnVideoMuteStateChange(
+ const std::string& nick, bool was_muted, bool is_muted) {
+ last_video_muted_nick = nick;
+ last_was_video_muted = was_muted;
+ last_is_video_muted = is_muted;
+ }
+
+ void OnVideoPauseStateChange(
+ const std::string& nick, bool was_paused, bool is_paused) {
+ last_video_paused_nick = nick;
+ last_was_video_paused = was_paused;
+ last_is_video_paused = is_paused;
+ }
+
+ void OnRecordingStateChange(
+ const std::string& nick, bool was_recording, bool is_recording) {
+ last_recording_nick = nick;
+ last_was_recording = was_recording;
+ last_is_recording = is_recording;
+ }
+
+ void OnRemoteMute(
+ const std::string& mutee_nick,
+ const std::string& muter_nick,
+ bool should_mute_locally) {
+ last_mutee_nick = mutee_nick;
+ last_muter_nick = muter_nick;
+ last_should_mute = should_mute_locally;
+ }
+
+ void OnMediaBlock(
+ const std::string& blockee_nick,
+ const std::string& blocker_nick) {
+ last_blockee_nick = blockee_nick;
+ last_blocker_nick = blocker_nick;
+ }
+
+ void OnRequestError(const std::string& node, const buzz::XmlElement* stanza) {
+ ++request_error_count;
+ request_error_node = node;
+ }
+
+ void OnPublishAudioMuteError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_audio_mute_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishVideoMuteError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_video_mute_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishVideoPauseError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_video_pause_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishPresenterError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_presenter_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnPublishRecordingError(const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ ++publish_recording_error_count;
+ error_task_id = task_id;
+ }
+
+ void OnRemoteMuteResult(const std::string& task_id,
+ const std::string& mutee_nick) {
+ result_task_id = task_id;
+ remote_mute_mutee_nick = mutee_nick;
+ }
+
+ void OnRemoteMuteError(const std::string& task_id,
+ const std::string& mutee_nick,
+ const buzz::XmlElement* stanza) {
+ ++remote_mute_error_count;
+ error_task_id = task_id;
+ remote_mute_mutee_nick = mutee_nick;
+ }
+
+ void OnMediaBlockResult(const std::string& task_id,
+ const std::string& blockee_nick) {
+ result_task_id = task_id;
+ media_blockee_nick = blockee_nick;
+ }
+
+ void OnMediaBlockError(const std::string& task_id,
+ const std::string& blockee_nick,
+ const buzz::XmlElement* stanza) {
+ ++media_block_error_count;
+ error_task_id = task_id;
+ media_blockee_nick = blockee_nick;
+ }
+
+ std::string last_presenter_nick;
+ bool last_is_presenting;
+ bool last_was_presenting;
+ std::string last_audio_muted_nick;
+ bool last_is_audio_muted;
+ bool last_was_audio_muted;
+ std::string last_video_muted_nick;
+ bool last_is_video_muted;
+ bool last_was_video_muted;
+ std::string last_video_paused_nick;
+ bool last_is_video_paused;
+ bool last_was_video_paused;
+ std::string last_recording_nick;
+ bool last_is_recording;
+ bool last_was_recording;
+ std::string last_mutee_nick;
+ std::string last_muter_nick;
+ bool last_should_mute;
+ std::string last_blockee_nick;
+ std::string last_blocker_nick;
+
+ int request_error_count;
+ std::string request_error_node;
+ int publish_audio_mute_error_count;
+ int publish_video_mute_error_count;
+ int publish_video_pause_error_count;
+ int publish_presenter_error_count;
+ int publish_recording_error_count;
+ int remote_mute_error_count;
+ std::string result_task_id;
+ std::string error_task_id;
+ std::string remote_mute_mutee_nick;
+ int media_block_error_count;
+ std::string media_blockee_nick;
+};
+
+class HangoutPubSubClientTest : public testing::Test {
+ public:
+ HangoutPubSubClientTest() :
+ pubsubjid("room@domain.com"),
+ nick("me") {
+
+ runner.reset(new rtc::FakeTaskRunner());
+ xmpp_client = new buzz::FakeXmppClient(runner.get());
+ client.reset(new buzz::HangoutPubSubClient(xmpp_client, pubsubjid, nick));
+ listener.reset(new TestHangoutPubSubListener());
+ client->SignalPresenterStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPresenterStateChange);
+ client->SignalAudioMuteStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnAudioMuteStateChange);
+ client->SignalVideoMuteStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnVideoMuteStateChange);
+ client->SignalVideoPauseStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnVideoPauseStateChange);
+ client->SignalRecordingStateChange.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRecordingStateChange);
+ client->SignalRemoteMute.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMute);
+ client->SignalMediaBlock.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlock);
+ client->SignalRequestError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRequestError);
+ client->SignalPublishAudioMuteError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishAudioMuteError);
+ client->SignalPublishVideoMuteError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishVideoMuteError);
+ client->SignalPublishVideoPauseError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishVideoPauseError);
+ client->SignalPublishPresenterError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishPresenterError);
+ client->SignalPublishRecordingError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnPublishRecordingError);
+ client->SignalRemoteMuteResult.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMuteResult);
+ client->SignalRemoteMuteError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnRemoteMuteError);
+ client->SignalMediaBlockResult.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlockResult);
+ client->SignalMediaBlockError.connect(
+ listener.get(), &TestHangoutPubSubListener::OnMediaBlockError);
+ }
+
+ rtc::scoped_ptr<rtc::FakeTaskRunner> runner;
+ // xmpp_client deleted by deleting runner.
+ buzz::FakeXmppClient* xmpp_client;
+ rtc::scoped_ptr<buzz::HangoutPubSubClient> client;
+ rtc::scoped_ptr<TestHangoutPubSubListener> listener;
+ buzz::Jid pubsubjid;
+ std::string nick;
+};
+
+TEST_F(HangoutPubSubClientTest, TestRequest) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ client->RequestAll();
+ std::string expected_presenter_request =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"google:presenter\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ std::string expected_media_request =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"google:muc#media\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_request, xmpp_client->sent_stanzas()[0]->Str());
+ EXPECT_EQ(expected_media_request, xmpp_client->sent_stanzas()[1]->Str());
+
+ std::string presenter_response =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='google:presenter'>"
+ " <item id='12344'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " <item id='12345'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='o'/>"
+ " </item>"
+ // Some clients are "bad" in that they'll jam multiple states in
+ // all at once. We have to deal with it.
+ " <item id='12346'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(presenter_response));
+ EXPECT_EQ("presenting-nick", listener->last_presenter_nick);
+ EXPECT_FALSE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ std::string media_response =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick'>"
+ " <audio-mute nick='muted-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='video-mute:video-muted-nick'>"
+ " <video-mute nick='video-muted-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='video-pause:video-paused-nick'>"
+ " <video-pause nick='video-paused-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='recording:recording-nick'>"
+ " <recording nick='recording-nick' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(media_response));
+ EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+ EXPECT_FALSE(listener->last_was_audio_muted);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+
+ EXPECT_EQ("video-muted-nick", listener->last_video_muted_nick);
+ EXPECT_FALSE(listener->last_was_video_muted);
+ EXPECT_TRUE(listener->last_is_video_muted);
+
+ EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick);
+ EXPECT_FALSE(listener->last_was_video_paused);
+ EXPECT_TRUE(listener->last_is_video_paused);
+
+ EXPECT_EQ("recording-nick", listener->last_recording_nick);
+ EXPECT_FALSE(listener->last_was_recording);
+ EXPECT_TRUE(listener->last_is_recording);
+
+ std::string incoming_presenter_resets_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <item id='12348'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='o'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_resets_message));
+ EXPECT_EQ("presenting-nick", listener->last_presenter_nick);
+ //EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_FALSE(listener->last_is_presenting);
+
+ std::string incoming_presenter_retracts_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <retract id='12344'/>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_retracts_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_FALSE(listener->last_is_presenting);
+
+ std::string incoming_media_retracts_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick'>"
+ " </item>"
+ " <retract id='video-mute:video-muted-nick'/>"
+ " <retract id='video-pause:video-paused-nick'/>"
+ " <retract id='recording:recording-nick'/>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_retracts_message));
+ EXPECT_EQ("muted-nick", listener->last_audio_muted_nick);
+ EXPECT_TRUE(listener->last_was_audio_muted);
+ EXPECT_FALSE(listener->last_is_audio_muted);
+
+ EXPECT_EQ("video-paused-nick", listener->last_video_paused_nick);
+ EXPECT_TRUE(listener->last_was_video_paused);
+ EXPECT_FALSE(listener->last_is_video_paused);
+
+ EXPECT_EQ("recording-nick", listener->last_recording_nick);
+ EXPECT_TRUE(listener->last_was_recording);
+ EXPECT_FALSE(listener->last_is_recording);
+
+ std::string incoming_presenter_changes_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:presenter'>"
+ " <item id='presenting-nick2'>"
+ " <presenter xmlns='google:presenter' nick='presenting-nick2'/>"
+ " <pre:presentation-item xmlns:pre='google:presenter'"
+ " pre:presentation-type='s'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_FALSE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+ EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+ EXPECT_TRUE(listener->last_was_presenting);
+ EXPECT_TRUE(listener->last_is_presenting);
+
+ std::string incoming_media_changes_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:muted-nick2'>"
+ " <audio-mute nick='muted-nick2' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='video-pause:video-paused-nick2'>"
+ " <video-pause nick='video-paused-nick2' xmlns='google:muc#media'/>"
+ " </item>"
+ " <item id='recording:recording-nick2'>"
+ " <recording nick='recording-nick2' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_changes_message));
+ EXPECT_EQ("muted-nick2", listener->last_audio_muted_nick);
+ EXPECT_FALSE(listener->last_was_audio_muted);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+
+ EXPECT_EQ("video-paused-nick2", listener->last_video_paused_nick);
+ EXPECT_FALSE(listener->last_was_video_paused);
+ EXPECT_TRUE(listener->last_is_video_paused);
+
+ EXPECT_EQ("recording-nick2", listener->last_recording_nick);
+ EXPECT_FALSE(listener->last_was_recording);
+ EXPECT_TRUE(listener->last_is_recording);
+
+ std::string incoming_remote_mute_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:mutee' publisher='room@domain.com/muter'>"
+ " <audio-mute nick='mutee' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ listener->last_is_audio_muted = false;
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_remote_mute_message));
+ EXPECT_EQ("mutee", listener->last_mutee_nick);
+ EXPECT_EQ("muter", listener->last_muter_nick);
+ EXPECT_FALSE(listener->last_should_mute);
+ EXPECT_EQ("mutee", listener->last_audio_muted_nick);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+
+ std::string incoming_remote_mute_me_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='audio-mute:me' publisher='room@domain.com/muter'>"
+ " <audio-mute nick='me' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ listener->last_is_audio_muted = false;
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_remote_mute_me_message));
+ EXPECT_EQ("me", listener->last_mutee_nick);
+ EXPECT_EQ("muter", listener->last_muter_nick);
+ EXPECT_TRUE(listener->last_should_mute);
+ EXPECT_EQ("me", listener->last_audio_muted_nick);
+ EXPECT_TRUE(listener->last_is_audio_muted);
+
+ std::string incoming_media_block_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='google:muc#media'>"
+ " <item id='block:blocker:blockee'"
+ " publisher='room@domain.com/blocker'>"
+ " <block nick='blockee' xmlns='google:muc#media'/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(
+ buzz::XmlElement::ForStr(incoming_media_block_message));
+ EXPECT_EQ("blockee", listener->last_blockee_nick);
+ EXPECT_EQ("blocker", listener->last_blocker_nick);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRequestError) {
+ client->RequestAll();
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->request_error_count);
+ EXPECT_EQ("google:presenter", listener->request_error_node);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublish) {
+ client->PublishPresenterState(true);
+ std::string expected_presenter_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:presenter\">"
+ "<item id=\"me\">"
+ "<presenter xmlns=\"google:presenter\""
+ " nick=\"me\"/>"
+ "<pre:presentation-item"
+ " pre:presentation-type=\"s\" xmlns:pre=\"google:presenter\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_iq,
+ xmpp_client->sent_stanzas()[0]->Str());
+
+ client->PublishAudioMuteState(true);
+ std::string expected_audio_mute_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"audio-mute:me\">"
+ "<audio-mute xmlns=\"google:muc#media\" nick=\"me\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(2U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_audio_mute_iq, xmpp_client->sent_stanzas()[1]->Str());
+
+ client->PublishVideoPauseState(true);
+ std::string expected_video_pause_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"video-pause:me\">"
+ "<video-pause xmlns=\"google:muc#media\" nick=\"me\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(3U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_video_pause_iq, xmpp_client->sent_stanzas()[2]->Str());
+
+ client->PublishRecordingState(true);
+ std::string expected_recording_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"recording:me\">"
+ "<recording xmlns=\"google:muc#media\" nick=\"me\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(4U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_recording_iq, xmpp_client->sent_stanzas()[3]->Str());
+
+ client->RemoteMute("mutee");
+ std::string expected_remote_mute_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"audio-mute:mutee\">"
+ "<audio-mute xmlns=\"google:muc#media\" nick=\"mutee\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(5U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_remote_mute_iq, xmpp_client->sent_stanzas()[4]->Str());
+
+ client->PublishPresenterState(false);
+ std::string expected_presenter_retract_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:presenter\">"
+ "<item id=\"me\">"
+ "<presenter xmlns=\"google:presenter\""
+ " nick=\"me\"/>"
+ "<pre:presentation-item"
+ " pre:presentation-type=\"o\" xmlns:pre=\"google:presenter\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(6U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_presenter_retract_iq,
+ xmpp_client->sent_stanzas()[5]->Str());
+
+ client->PublishAudioMuteState(false);
+ std::string expected_audio_mute_retract_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"google:muc#media\" notify=\"true\">"
+ "<item id=\"audio-mute:me\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(7U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_audio_mute_retract_iq,
+ xmpp_client->sent_stanzas()[6]->Str());
+
+ client->PublishVideoPauseState(false);
+ std::string expected_video_pause_retract_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"google:muc#media\" notify=\"true\">"
+ "<item id=\"video-pause:me\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(8U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_video_pause_retract_iq,
+ xmpp_client->sent_stanzas()[7]->Str());
+
+ client->BlockMedia("blockee");
+ std::string expected_media_block_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"google:muc#media\">"
+ "<item id=\"block:me:blockee\">"
+ "<block xmlns=\"google:muc#media\" nick=\"blockee\"/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(9U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_media_block_iq, xmpp_client->sent_stanzas()[8]->Str());
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishPresenterError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishPresenterState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_presenter_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+
+TEST_F(HangoutPubSubClientTest, TestPublishAudioMuteError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishAudioMuteState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_audio_mute_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishVideoPauseError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishVideoPauseState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_video_pause_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRecordingError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->PublishRecordingState(true);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->publish_recording_error_count);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishRemoteMuteResult) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->RemoteMute("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+ EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestRemoteMuteError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->RemoteMute("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->remote_mute_error_count);
+ EXPECT_EQ("joe", listener->remote_mute_mutee_nick);
+ EXPECT_EQ("0", listener->error_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestPublishMediaBlockResult) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->BlockMedia("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ("joe", listener->media_blockee_nick);
+ EXPECT_EQ("0", listener->result_task_id);
+}
+
+TEST_F(HangoutPubSubClientTest, TestMediaBlockError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'/>";
+
+ client->BlockMedia("joe");
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->remote_mute_error_count);
+ EXPECT_EQ("joe", listener->media_blockee_nick);
+ EXPECT_EQ("0", listener->error_task_id);
+}
diff --git a/webrtc/libjingle/xmpp/iqtask.cc b/webrtc/libjingle/xmpp/iqtask.cc
new file mode 100644
index 0000000000..a7a41ee519
--- /dev/null
+++ b/webrtc/libjingle/xmpp/iqtask.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+
+namespace buzz {
+
+static const int kDefaultIqTimeoutSecs = 15;
+
+IqTask::IqTask(XmppTaskParentInterface* parent,
+ const std::string& verb,
+ const buzz::Jid& to,
+ buzz::XmlElement* el)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+ to_(to),
+ stanza_(MakeIq(verb, to_, task_id())) {
+ stanza_->AddElement(el);
+ set_timeout_seconds(kDefaultIqTimeoutSecs);
+}
+
+int IqTask::ProcessStart() {
+ buzz::XmppReturnStatus ret = SendStanza(stanza_.get());
+ // TODO: HandleError(NULL) if SendStanza fails?
+ return (ret == buzz::XMPP_RETURN_OK) ? STATE_RESPONSE : STATE_ERROR;
+}
+
+bool IqTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, to_, task_id()))
+ return false;
+
+ if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT &&
+ stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) {
+ return false;
+ }
+
+ QueueStanza(stanza);
+ return true;
+}
+
+int IqTask::ProcessResponse() {
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ bool success = (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT);
+ if (success) {
+ HandleResult(stanza);
+ } else {
+ SignalError(this, stanza->FirstNamed(QN_ERROR));
+ }
+ return STATE_DONE;
+}
+
+int IqTask::OnTimeout() {
+ SignalError(this, NULL);
+ return XmppTask::OnTimeout();
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/iqtask.h b/webrtc/libjingle/xmpp/iqtask.h
new file mode 100644
index 0000000000..1d50c38394
--- /dev/null
+++ b/webrtc/libjingle/xmpp/iqtask.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_IQTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_IQTASK_H_
+
+#include <string>
+
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class IqTask : public XmppTask {
+ public:
+ IqTask(XmppTaskParentInterface* parent,
+ const std::string& verb, const Jid& to,
+ XmlElement* el);
+ virtual ~IqTask() {}
+
+ const XmlElement* stanza() const { return stanza_.get(); }
+
+ sigslot::signal2<IqTask*,
+ const XmlElement*> SignalError;
+
+ protected:
+ virtual void HandleResult(const XmlElement* element) = 0;
+
+ private:
+ virtual int ProcessStart();
+ virtual bool HandleStanza(const XmlElement* stanza);
+ virtual int ProcessResponse();
+ virtual int OnTimeout();
+
+ Jid to_;
+ rtc::scoped_ptr<XmlElement> stanza_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_IQTASK_H_
diff --git a/webrtc/libjingle/xmpp/jid.cc b/webrtc/libjingle/xmpp/jid.cc
new file mode 100644
index 0000000000..ad05380568
--- /dev/null
+++ b/webrtc/libjingle/xmpp/jid.cc
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/jid.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+#include <string>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/logging.h"
+
+namespace buzz {
+
+Jid::Jid() {
+}
+
+Jid::Jid(const std::string& jid_string) {
+ if (jid_string.empty())
+ return;
+
+ // First find the slash and slice off that part
+ size_t slash = jid_string.find('/');
+ resource_name_ = (slash == std::string::npos ? STR_EMPTY :
+ jid_string.substr(slash + 1));
+
+ // Now look for the node
+ size_t at = jid_string.find('@');
+ size_t domain_begin;
+ if (at < slash && at != std::string::npos) {
+ node_name_ = jid_string.substr(0, at);
+ domain_begin = at + 1;
+ } else {
+ domain_begin = 0;
+ }
+
+ // Now take what is left as the domain
+ size_t domain_length = (slash == std::string::npos) ?
+ (jid_string.length() - domain_begin) : (slash - domain_begin);
+ domain_name_ = jid_string.substr(domain_begin, domain_length);
+
+ ValidateOrReset();
+}
+
+Jid::Jid(const std::string& node_name,
+ const std::string& domain_name,
+ const std::string& resource_name)
+ : node_name_(node_name),
+ domain_name_(domain_name),
+ resource_name_(resource_name) {
+ ValidateOrReset();
+}
+
+void Jid::ValidateOrReset() {
+ bool valid_node;
+ bool valid_domain;
+ bool valid_resource;
+
+ node_name_ = PrepNode(node_name_, &valid_node);
+ domain_name_ = PrepDomain(domain_name_, &valid_domain);
+ resource_name_ = PrepResource(resource_name_, &valid_resource);
+
+ if (!valid_node || !valid_domain || !valid_resource) {
+ node_name_.clear();
+ domain_name_.clear();
+ resource_name_.clear();
+ }
+}
+
+std::string Jid::Str() const {
+ if (!IsValid())
+ return STR_EMPTY;
+
+ std::string ret;
+
+ if (!node_name_.empty())
+ ret = node_name_ + "@";
+
+ ASSERT(domain_name_ != STR_EMPTY);
+ ret += domain_name_;
+
+ if (!resource_name_.empty())
+ ret += "/" + resource_name_;
+
+ return ret;
+}
+
+Jid::~Jid() {
+}
+
+bool Jid::IsEmpty() const {
+ return (node_name_.empty() && domain_name_.empty() &&
+ resource_name_.empty());
+}
+
+bool Jid::IsValid() const {
+ return !domain_name_.empty();
+}
+
+bool Jid::IsBare() const {
+ if (IsEmpty()) {
+ LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid.";
+ return true;
+ }
+ return IsValid() && resource_name_.empty();
+}
+
+bool Jid::IsFull() const {
+ return IsValid() && !resource_name_.empty();
+}
+
+Jid Jid::BareJid() const {
+ if (!IsValid())
+ return Jid();
+ if (!IsFull())
+ return *this;
+ return Jid(node_name_, domain_name_, STR_EMPTY);
+}
+
+bool Jid::BareEquals(const Jid& other) const {
+ return other.node_name_ == node_name_ &&
+ other.domain_name_ == domain_name_;
+}
+
+void Jid::CopyFrom(const Jid& jid) {
+ this->node_name_ = jid.node_name_;
+ this->domain_name_ = jid.domain_name_;
+ this->resource_name_ = jid.resource_name_;
+}
+
+bool Jid::operator==(const Jid& other) const {
+ return other.node_name_ == node_name_ &&
+ other.domain_name_ == domain_name_ &&
+ other.resource_name_ == resource_name_;
+}
+
+int Jid::Compare(const Jid& other) const {
+ int compare_result;
+ compare_result = node_name_.compare(other.node_name_);
+ if (0 != compare_result)
+ return compare_result;
+ compare_result = domain_name_.compare(other.domain_name_);
+ if (0 != compare_result)
+ return compare_result;
+ compare_result = resource_name_.compare(other.resource_name_);
+ return compare_result;
+}
+
+// --- JID parsing code: ---
+
+// Checks and normalizes the node part of a JID.
+std::string Jid::PrepNode(const std::string& node, bool* valid) {
+ *valid = false;
+ std::string result;
+
+ for (std::string::const_iterator i = node.begin(); i < node.end(); ++i) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ result += PrepNodeAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement the correct stringprep protocol for these
+ result += tolower(ch);
+ }
+ if (!char_valid) {
+ return STR_EMPTY;
+ }
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a node.
+char Jid::PrepNodeAscii(char ch, bool* valid) {
+ *valid = true;
+ switch (ch) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+ case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+ case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+ case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ return (char)(ch + ('a' - 'A'));
+
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case ' ': case '&': case '/': case ':': case '<': case '>': case '@':
+ case '\"': case '\'':
+ case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+
+// Checks and normalizes the resource part of a JID.
+std::string Jid::PrepResource(const std::string& resource, bool* valid) {
+ *valid = false;
+ std::string result;
+
+ for (std::string::const_iterator i = resource.begin();
+ i < resource.end(); ++i) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ result += PrepResourceAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement the correct stringprep protocol for these
+ result += ch;
+ }
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+// Returns the appropriate mapping for an ASCII character in a resource.
+char Jid::PrepResourceAscii(char ch, bool* valid) {
+ *valid = true;
+ switch (ch) {
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+// Checks and normalizes the domain part of a JID.
+std::string Jid::PrepDomain(const std::string& domain, bool* valid) {
+ *valid = false;
+ std::string result;
+
+ // TODO: if the domain contains a ':', then we should parse it
+ // as an IPv6 address rather than giving an error about illegal domain.
+ PrepDomain(domain, &result, valid);
+ if (!*valid) {
+ return STR_EMPTY;
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+
+// Checks and normalizes an IDNA domain.
+void Jid::PrepDomain(const std::string& domain, std::string* buf, bool* valid) {
+ *valid = false;
+ std::string::const_iterator last = domain.begin();
+ for (std::string::const_iterator i = domain.begin(); i < domain.end(); ++i) {
+ bool label_valid = true;
+ char ch = *i;
+ switch (ch) {
+ case 0x002E:
+#if 0 // FIX: This isn't UTF-8-aware.
+ case 0x3002:
+ case 0xFF0E:
+ case 0xFF61:
+#endif
+ PrepDomainLabel(last, i, buf, &label_valid);
+ *buf += '.';
+ last = i + 1;
+ break;
+ }
+ if (!label_valid) {
+ return;
+ }
+ }
+ PrepDomainLabel(last, domain.end(), buf, valid);
+}
+
+// Checks and normalizes a domain label.
+void Jid::PrepDomainLabel(
+ std::string::const_iterator start, std::string::const_iterator end,
+ std::string* buf, bool* valid) {
+ *valid = false;
+
+ int start_len = static_cast<int>(buf->length());
+ for (std::string::const_iterator i = start; i < end; ++i) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ *buf += PrepDomainLabelAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement ToASCII for these
+ *buf += ch;
+ }
+ if (!char_valid) {
+ return;
+ }
+ }
+
+ int count = static_cast<int>(buf->length() - start_len);
+ if (count == 0) {
+ return;
+ }
+ else if (count > 63) {
+ return;
+ }
+
+ // Is this check needed? See comment in PrepDomainLabelAscii.
+ if ((*buf)[start_len] == '-') {
+ return;
+ }
+ if ((*buf)[buf->length() - 1] == '-') {
+ return;
+ }
+ *valid = true;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a domain label.
+char Jid::PrepDomainLabelAscii(char ch, bool* valid) {
+ *valid = true;
+ // TODO: A literal reading of the spec seems to say that we do
+ // not need to check for these illegal characters (an "internationalized
+ // domain label" runs ToASCII with UseSTD3... set to false). But that
+ // can't be right. We should at least be checking that there are no '/'
+ // or '@' characters in the domain. Perhaps we should see what others
+ // do in this case.
+
+ switch (ch) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+ case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+ case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+ case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ return (char)(ch + ('a' - 'A'));
+
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D:
+ case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29:
+ case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A:
+ case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40:
+ case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60:
+ case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/jid.h b/webrtc/libjingle/xmpp/jid.h
new file mode 100644
index 0000000000..94ad7c8338
--- /dev/null
+++ b/webrtc/libjingle/xmpp/jid.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_JID_H_
+#define WEBRTC_LIBJINGLE_XMPP_JID_H_
+
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlconstants.h"
+#include "webrtc/base/basictypes.h"
+
+namespace buzz {
+
+// The Jid class encapsulates and provides parsing help for Jids. A Jid
+// consists of three parts: the node, the domain and the resource, e.g.:
+//
+// node@domain/resource
+//
+// The node and resource are both optional. A valid jid is defined to have
+// a domain. A bare jid is defined to not have a resource and a full jid
+// *does* have a resource.
+class Jid {
+public:
+ explicit Jid();
+ explicit Jid(const std::string& jid_string);
+ explicit Jid(const std::string& node_name,
+ const std::string& domain_name,
+ const std::string& resource_name);
+ ~Jid();
+
+ const std::string & node() const { return node_name_; }
+ const std::string & domain() const { return domain_name_; }
+ const std::string & resource() const { return resource_name_; }
+
+ std::string Str() const;
+ Jid BareJid() const;
+
+ bool IsEmpty() const;
+ bool IsValid() const;
+ bool IsBare() const;
+ bool IsFull() const;
+
+ bool BareEquals(const Jid& other) const;
+ void CopyFrom(const Jid& jid);
+ bool operator==(const Jid& other) const;
+ bool operator!=(const Jid& other) const { return !operator==(other); }
+
+ bool operator<(const Jid& other) const { return Compare(other) < 0; };
+ bool operator>(const Jid& other) const { return Compare(other) > 0; };
+
+ int Compare(const Jid & other) const;
+
+private:
+ void ValidateOrReset();
+
+ static std::string PrepNode(const std::string& node, bool* valid);
+ static char PrepNodeAscii(char ch, bool* valid);
+ static std::string PrepResource(const std::string& start, bool* valid);
+ static char PrepResourceAscii(char ch, bool* valid);
+ static std::string PrepDomain(const std::string& domain, bool* valid);
+ static void PrepDomain(const std::string& domain,
+ std::string* buf, bool* valid);
+ static void PrepDomainLabel(
+ std::string::const_iterator start, std::string::const_iterator end,
+ std::string* buf, bool* valid);
+ static char PrepDomainLabelAscii(char ch, bool *valid);
+
+ std::string node_name_;
+ std::string domain_name_;
+ std::string resource_name_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_JID_H_
diff --git a/webrtc/libjingle/xmpp/jid_unittest.cc b/webrtc/libjingle/xmpp/jid_unittest.cc
new file mode 100644
index 0000000000..e22f6a2644
--- /dev/null
+++ b/webrtc/libjingle/xmpp/jid_unittest.cc
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/base/gunit.h"
+
+using buzz::Jid;
+
+TEST(JidTest, TestDomain) {
+ Jid jid("dude");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("dude", jid.Str());
+ EXPECT_EQ("dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomain) {
+ Jid jid("walter@dude");
+ EXPECT_EQ("walter", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("walter@dude", jid.Str());
+ EXPECT_EQ("walter@dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestDomainResource) {
+ Jid jid("dude/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("bowlingalley", jid.resource());
+ EXPECT_EQ("dude/bowlingalley", jid.Str());
+ EXPECT_EQ("dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeDomainResource) {
+ Jid jid("walter@dude/bowlingalley");
+ EXPECT_EQ("walter", jid.node());
+ EXPECT_EQ("dude", jid.domain());
+ EXPECT_EQ("bowlingalley", jid.resource());
+ EXPECT_EQ("walter@dude/bowlingalley", jid.Str());
+ EXPECT_EQ("walter@dude", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestNode) {
+ Jid jid("walter@");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestResource) {
+ Jid jid("/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestNodeResource) {
+ Jid jid("walter@/bowlingalley");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("", jid.domain());
+ EXPECT_EQ("", jid.resource());
+ EXPECT_EQ("", jid.Str());
+ EXPECT_EQ("", jid.BareJid().Str());
+ EXPECT_FALSE(jid.IsValid());
+ EXPECT_TRUE(jid.IsBare());
+ EXPECT_FALSE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky) {
+ Jid jid("bowling@muchat/walter@dude");
+ EXPECT_EQ("bowling", jid.node());
+ EXPECT_EQ("muchat", jid.domain());
+ EXPECT_EQ("walter@dude", jid.resource());
+ EXPECT_EQ("bowling@muchat/walter@dude", jid.Str());
+ EXPECT_EQ("bowling@muchat", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
+
+TEST(JidTest, TestFunky2) {
+ Jid jid("muchat/walter@dude");
+ EXPECT_EQ("", jid.node());
+ EXPECT_EQ("muchat", jid.domain());
+ EXPECT_EQ("walter@dude", jid.resource());
+ EXPECT_EQ("muchat/walter@dude", jid.Str());
+ EXPECT_EQ("muchat", jid.BareJid().Str());
+ EXPECT_TRUE(jid.IsValid());
+ EXPECT_FALSE(jid.IsBare());
+ EXPECT_TRUE(jid.IsFull());
+}
diff --git a/webrtc/libjingle/xmpp/jingleinfotask.cc b/webrtc/libjingle/xmpp/jingleinfotask.cc
new file mode 100644
index 0000000000..a5a07121bd
--- /dev/null
+++ b/webrtc/libjingle/xmpp/jingleinfotask.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/jingleinfotask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/socketaddress.h"
+
+namespace buzz {
+
+class JingleInfoTask::JingleInfoGetTask : public XmppTask {
+ public:
+ explicit JingleInfoGetTask(XmppTaskParentInterface* parent)
+ : XmppTask(parent, XmppEngine::HL_SINGLE),
+ done_(false) {}
+
+ virtual int ProcessStart() {
+ rtc::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<rtc::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(rtc::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/webrtc/libjingle/xmpp/jingleinfotask.h b/webrtc/libjingle/xmpp/jingleinfotask.h
new file mode 100644
index 0000000000..6ed701de2f
--- /dev/null
+++ b/webrtc/libjingle/xmpp/jingleinfotask.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_
+
+#include <vector>
+
+#include "webrtc/p2p/client/httpportallocator.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/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<rtc::SocketAddress> &>
+ SignalJingleInfo;
+
+ protected:
+ class JingleInfoGetTask;
+ friend class JingleInfoGetTask;
+
+ virtual bool HandleStanza(const XmlElement * stanza);
+};
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_JINGLEINFOTASK_H_
diff --git a/webrtc/libjingle/xmpp/module.h b/webrtc/libjingle/xmpp/module.h
new file mode 100644
index 0000000000..fa26df34c5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/module.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MODULE_H_
+#define WEBRTC_LIBJINGLE_XMPP_MODULE_H_
+
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class XmppEngine;
+
+//! 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 // WEBRTC_LIBJINGLE_XMPP_MODULE_H_
diff --git a/webrtc/libjingle/xmpp/moduleimpl.cc b/webrtc/libjingle/xmpp/moduleimpl.cc
new file mode 100644
index 0000000000..b5337a642e
--- /dev/null
+++ b/webrtc/libjingle/xmpp/moduleimpl.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/moduleimpl.h"
+#include "webrtc/base/common.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/webrtc/libjingle/xmpp/moduleimpl.h b/webrtc/libjingle/xmpp/moduleimpl.h
new file mode 100644
index 0000000000..5a7c6e360f
--- /dev/null
+++ b/webrtc/libjingle/xmpp/moduleimpl.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_
+#define WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_
+
+#include "webrtc/libjingle/xmpp/module.h"
+#include "webrtc/libjingle/xmpp/xmppengine.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 // WEBRTC_LIBJINGLE_XMPP_MODULEIMPL_H_
diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.cc b/webrtc/libjingle/xmpp/mucroomconfigtask.cc
new file mode 100644
index 0000000000..08b10650a9
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomconfigtask.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/mucroomconfigtask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace buzz {
+
+MucRoomConfigTask::MucRoomConfigTask(
+ XmppTaskParentInterface* parent,
+ const Jid& room_jid,
+ const std::string& room_name,
+ const std::vector<std::string>& room_features)
+ : IqTask(parent, STR_SET, room_jid,
+ MakeRequest(room_name, room_features)),
+ room_jid_(room_jid) {
+}
+
+XmlElement* MucRoomConfigTask::MakeRequest(
+ const std::string& room_name,
+ const std::vector<std::string>& room_features) {
+ buzz::XmlElement* owner_query = new
+ buzz::XmlElement(buzz::QN_MUC_OWNER_QUERY, true);
+
+ buzz::XmlElement* x_form = new buzz::XmlElement(buzz::QN_XDATA_X, true);
+ x_form->SetAttr(buzz::QN_TYPE, buzz::STR_FORM);
+
+ buzz::XmlElement* roomname_field =
+ new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+ roomname_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_ROOMNAME);
+ roomname_field->SetAttr(buzz::QN_TYPE, buzz::STR_TEXT_SINGLE);
+
+ buzz::XmlElement* roomname_value =
+ new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+ roomname_value->SetBodyText(room_name);
+
+ roomname_field->AddElement(roomname_value);
+ x_form->AddElement(roomname_field);
+
+ buzz::XmlElement* features_field =
+ new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+ features_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_FEATURES);
+ features_field->SetAttr(buzz::QN_TYPE, buzz::STR_LIST_MULTI);
+
+ for (std::vector<std::string>::const_iterator feature = room_features.begin();
+ feature != room_features.end(); ++feature) {
+ buzz::XmlElement* features_value =
+ new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+ features_value->SetBodyText(*feature);
+ features_field->AddElement(features_value);
+ }
+
+ x_form->AddElement(features_field);
+ owner_query->AddElement(x_form);
+ return owner_query;
+}
+
+void MucRoomConfigTask::HandleResult(const XmlElement* element) {
+ SignalResult(this);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask.h b/webrtc/libjingle/xmpp/mucroomconfigtask.h
new file mode 100644
index 0000000000..d297d027f2
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomconfigtask.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_
+
+#include <string>
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+namespace buzz {
+
+// This task configures the muc room for document sharing and other enterprise
+// specific goodies.
+class MucRoomConfigTask : public IqTask {
+ public:
+ MucRoomConfigTask(XmppTaskParentInterface* parent,
+ const Jid& room_jid,
+ const std::string& room_name,
+ const std::vector<std::string>& room_features);
+
+ // Room configuration does not return any reasonable error
+ // values. The First config request configures the room, subseqent
+ // ones are just ignored by server and server returns empty
+ // response.
+ sigslot::signal1<MucRoomConfigTask*> SignalResult;
+
+ const Jid& room_jid() const { return room_jid_; }
+
+ protected:
+ virtual void HandleResult(const XmlElement* stanza);
+
+ private:
+ static XmlElement* MakeRequest(const std::string& room_name,
+ const std::vector<std::string>& room_features);
+ Jid room_jid_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMCONFIGTASK_H_
diff --git a/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc
new file mode 100644
index 0000000000..a86dd14f59
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomconfigtask_unittest.cc
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/mucroomconfigtask.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class MucRoomConfigListener : public sigslot::has_slots<> {
+ public:
+ MucRoomConfigListener() : result_count(0), error_count(0) {}
+
+ void OnResult(buzz::MucRoomConfigTask*) {
+ ++result_count;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ int result_count;
+ int error_count;
+};
+
+class MucRoomConfigTaskTest : public testing::Test {
+ public:
+ MucRoomConfigTaskTest() :
+ room_jid("muc-jid-ponies@domain.com"),
+ room_name("ponies") {
+ }
+
+ virtual void SetUp() {
+ runner = new rtc::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomConfigListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ rtc::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomConfigListener* listener;
+ buzz::Jid room_jid;
+ std::string room_name;
+};
+
+TEST_F(MucRoomConfigTaskTest, TestConfigEnterprise) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ std::vector<std::string> room_features;
+ room_features.push_back("feature1");
+ room_features.push_back("feature2");
+ buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+ xmpp_client, room_jid, "ponies", room_features);
+ EXPECT_EQ(room_jid, task->room_jid());
+
+ task->SignalResult.connect(listener, &MucRoomConfigListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"muc-jid-ponies@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"http://jabber.org/protocol/muc#owner\">"
+ "<x xmlns=\"jabber:x:data\" type=\"form\">"
+ "<field var=\"muc#roomconfig_roomname\" type=\"text-single\">"
+ "<value>ponies</value>"
+ "</field>"
+ "<field var=\"muc#roomconfig_features\" type=\"list-multi\">"
+ "<value>feature1</value>"
+ "<value>feature2</value>"
+ "</field>"
+ "</x>"
+ "</query>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' id='0' type='result'"
+ " from='muc-jid-ponies@domain.com'>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomConfigTaskTest, TestError) {
+ std::vector<std::string> room_features;
+ buzz::MucRoomConfigTask* task = new buzz::MucRoomConfigTask(
+ xmpp_client, room_jid, "ponies", room_features);
+ task->SignalError.connect(listener, &MucRoomConfigListener::OnError);
+ task->Start();
+
+ std::string error_iq =
+ "<iq xmlns='jabber:client' id='0' type='error'"
+ " from='muc-jid-ponies@domain.com'>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc
new file mode 100644
index 0000000000..05a56716b9
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomDiscoveryTask::MucRoomDiscoveryTask(
+ XmppTaskParentInterface* parent,
+ const Jid& room_jid)
+ : IqTask(parent, STR_GET, room_jid,
+ new buzz::XmlElement(buzz::QN_DISCO_INFO_QUERY)) {
+}
+
+void MucRoomDiscoveryTask::HandleResult(const XmlElement* stanza) {
+ const XmlElement* query = stanza->FirstNamed(QN_DISCO_INFO_QUERY);
+ if (query == NULL) {
+ SignalError(this, NULL);
+ return;
+ }
+
+ std::set<std::string> features;
+ std::map<std::string, std::string> extended_info;
+ const XmlElement* identity = query->FirstNamed(QN_DISCO_IDENTITY);
+ if (identity == NULL || !identity->HasAttr(QN_NAME)) {
+ SignalResult(this, false, "", "", features, extended_info);
+ return;
+ }
+
+ const std::string name(identity->Attr(QN_NAME));
+
+ // Get the conversation id
+ const XmlElement* conversation =
+ identity->FirstNamed(QN_GOOGLE_MUC_HANGOUT_CONVERSATION_ID);
+ std::string conversation_id;
+ if (conversation != NULL) {
+ conversation_id = conversation->BodyText();
+ }
+
+ for (const XmlElement* feature = query->FirstNamed(QN_DISCO_FEATURE);
+ feature != NULL; feature = feature->NextNamed(QN_DISCO_FEATURE)) {
+ features.insert(feature->Attr(QN_VAR));
+ }
+
+ const XmlElement* data_x = query->FirstNamed(QN_XDATA_X);
+ if (data_x != NULL) {
+ for (const XmlElement* field = data_x->FirstNamed(QN_XDATA_FIELD);
+ field != NULL; field = field->NextNamed(QN_XDATA_FIELD)) {
+ const std::string key(field->Attr(QN_VAR));
+ extended_info[key] = field->Attr(QN_XDATA_VALUE);
+ }
+ }
+
+ SignalResult(this, true, name, conversation_id, features, extended_info);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask.h b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h
new file mode 100644
index 0000000000..3e332bd297
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_
+
+#include <map>
+#include <string>
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+namespace buzz {
+
+// This task requests the feature capabilities of the room. It is based on
+// XEP-0030, and extended using XEP-0004.
+class MucRoomDiscoveryTask : public IqTask {
+ public:
+ MucRoomDiscoveryTask(XmppTaskParentInterface* parent,
+ const Jid& room_jid);
+
+ // Signal (exists, name, conversationId, features, extended_info)
+ sigslot::signal6<MucRoomDiscoveryTask*,
+ bool,
+ const std::string&,
+ const std::string&,
+ const std::set<std::string>&,
+ const std::map<std::string, std::string>& > SignalResult;
+
+ protected:
+ virtual void HandleResult(const XmlElement* stanza);
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMDISCOVERYTASK_H_
diff --git a/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc
new file mode 100644
index 0000000000..cdb50c21b9
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomdiscoverytask_unittest.cc
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/mucroomdiscoverytask.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class MucRoomDiscoveryListener : public sigslot::has_slots<> {
+ public:
+ MucRoomDiscoveryListener() : error_count(0) {}
+
+ void OnResult(buzz::MucRoomDiscoveryTask* task,
+ bool exists,
+ const std::string& name,
+ const std::string& conversation_id,
+ const std::set<std::string>& features,
+ const std::map<std::string, std::string>& extended_info) {
+ last_exists = exists;
+ last_name = name;
+ last_conversation_id = conversation_id;
+ last_features = features;
+ last_extended_info = extended_info;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ bool last_exists;
+ std::string last_name;
+ std::string last_conversation_id;
+ std::set<std::string> last_features;
+ std::map<std::string, std::string> last_extended_info;
+ int error_count;
+};
+
+class MucRoomDiscoveryTaskTest : public testing::Test {
+ public:
+ MucRoomDiscoveryTaskTest() :
+ room_jid("muc-jid-ponies@domain.com"),
+ room_name("ponies"),
+ conversation_id("test_conversation_id") {
+ }
+
+ virtual void SetUp() {
+ runner = new rtc::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomDiscoveryListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ rtc::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomDiscoveryListener* listener;
+ buzz::Jid room_jid;
+ std::string room_name;
+ std::string conversation_id;
+};
+
+TEST_F(MucRoomDiscoveryTaskTest, TestDiscovery) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask(
+ xmpp_client, room_jid);
+ task->SignalResult.connect(listener, &MucRoomDiscoveryListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"muc-jid-ponies@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<info:query xmlns:info=\"http://jabber.org/protocol/disco#info\"/>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ("", listener->last_name);
+ EXPECT_EQ("", listener->last_conversation_id);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client'"
+ " from='muc-jid-ponies@domain.com' id='0' type='result'>"
+ " <info:query xmlns:info='http://jabber.org/protocol/disco#info'>"
+ " <info:identity name='ponies'>"
+ " <han:conversation-id xmlns:han='google:muc#hangout'>"
+ "test_conversation_id</han:conversation-id>"
+ " </info:identity>"
+ " <info:feature var='feature1'/>"
+ " <info:feature var='feature2'/>"
+ " <data:x xmlns:data='jabber:x:data'>"
+ " <data:field var='var1' data:value='value1' />"
+ " <data:field var='var2' data:value='value2' />"
+ " </data:x>"
+ " </info:query>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(true, listener->last_exists);
+ EXPECT_EQ(room_name, listener->last_name);
+ EXPECT_EQ(conversation_id, listener->last_conversation_id);
+ EXPECT_EQ(2U, listener->last_features.size());
+ EXPECT_EQ(1U, listener->last_features.count("feature1"));
+ EXPECT_EQ(2U, listener->last_extended_info.size());
+ EXPECT_EQ("value1", listener->last_extended_info["var1"]);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomDiscoveryTaskTest, TestMissingName) {
+ buzz::MucRoomDiscoveryTask* task = new buzz::MucRoomDiscoveryTask(
+ xmpp_client, room_jid);
+ task->SignalError.connect(listener, &MucRoomDiscoveryListener::OnError);
+ task->Start();
+
+ std::string error_iq =
+ "<iq xmlns='jabber:client'"
+ " from='muc-jid-ponies@domain.com' id='0' type='result'>"
+ " <info:query xmlns:info='http://jabber.org/protocol/disco#info'>"
+ " <info:identity />"
+ " </info:query>"
+ "</iq>";
+ EXPECT_EQ(0, listener->error_count);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+ EXPECT_EQ(0, listener->error_count);
+}
diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.cc b/webrtc/libjingle/xmpp/mucroomlookuptask.cc
new file mode 100644
index 0000000000..8c0a4d7856
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomlookuptask.cc
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/mucroomlookuptask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+
+
+namespace buzz {
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForRoomName(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& room_name,
+ const std::string& room_domain) {
+ return new MucRoomLookupTask(parent, lookup_server_jid,
+ MakeNameQuery(room_name, room_domain));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const Jid& room_jid) {
+ return new MucRoomLookupTask(parent, lookup_server_jid,
+ MakeJidQuery(room_jid));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& hangout_id) {
+ return new MucRoomLookupTask(parent, lookup_server_jid,
+ MakeHangoutIdQuery(hangout_id));
+}
+
+MucRoomLookupTask*
+MucRoomLookupTask::CreateLookupTaskForExternalId(
+ XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& external_id,
+ const std::string& type) {
+ return new MucRoomLookupTask(parent, lookup_server_jid,
+ MakeExternalIdQuery(external_id, type));
+}
+
+MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ XmlElement* query)
+ : IqTask(parent, STR_SET, lookup_server_jid, query) {
+}
+
+XmlElement* MucRoomLookupTask::MakeNameQuery(
+ const std::string& room_name, const std::string& room_domain) {
+ XmlElement* name_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false);
+ name_elem->SetBodyText(room_name);
+
+ XmlElement* domain_elem = new XmlElement(QN_SEARCH_ROOM_DOMAIN, false);
+ domain_elem->SetBodyText(room_domain);
+
+ XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true);
+ query->AddElement(name_elem);
+ query->AddElement(domain_elem);
+ return query;
+}
+
+XmlElement* MucRoomLookupTask::MakeJidQuery(const Jid& room_jid) {
+ XmlElement* jid_elem = new XmlElement(QN_SEARCH_ROOM_JID);
+ jid_elem->SetBodyText(room_jid.Str());
+
+ XmlElement* query = new XmlElement(QN_SEARCH_QUERY);
+ query->AddElement(jid_elem);
+ return query;
+}
+
+XmlElement* MucRoomLookupTask::MakeExternalIdQuery(
+ const std::string& external_id, const std::string& type) {
+ XmlElement* external_id_elem = new XmlElement(QN_SEARCH_EXTERNAL_ID);
+ external_id_elem->SetAttr(QN_TYPE, type);
+ external_id_elem->SetBodyText(external_id);
+
+ XmlElement* query = new XmlElement(QN_SEARCH_QUERY);
+ query->AddElement(external_id_elem);
+ return query;
+}
+
+// Construct a stanza to lookup the muc jid for a given hangout id. eg:
+//
+// <query xmlns="jabber:iq:search">
+// <hangout-id>0b48ad092c893a53b7bfc87422caf38e93978798e</hangout-id>
+// </query>
+XmlElement* MucRoomLookupTask::MakeHangoutIdQuery(
+ const std::string& hangout_id) {
+ XmlElement* hangout_id_elem = new XmlElement(QN_SEARCH_HANGOUT_ID, false);
+ hangout_id_elem->SetBodyText(hangout_id);
+
+ XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true);
+ query->AddElement(hangout_id_elem);
+ return query;
+}
+
+// Handle a response like the following:
+//
+// <query xmlns="jabber:iq:search">
+// <item jid="muvc-private-chat-guid@groupchat.google.com">
+// <room-name>0b48ad092c893a53b7bfc87422caf38e93978798e</room-name>
+// <room-domain>hangout.google.com</room-domain>
+// </item>
+// </query>
+void MucRoomLookupTask::HandleResult(const XmlElement* stanza) {
+ const XmlElement* query_elem = stanza->FirstNamed(QN_SEARCH_QUERY);
+ if (query_elem == NULL) {
+ SignalError(this, stanza);
+ return;
+ }
+
+ const XmlElement* item_elem = query_elem->FirstNamed(QN_SEARCH_ITEM);
+ if (item_elem == NULL) {
+ SignalError(this, stanza);
+ return;
+ }
+
+ MucRoomInfo room;
+ room.jid = Jid(item_elem->Attr(buzz::QN_JID));
+ if (!room.jid.IsValid()) {
+ SignalError(this, stanza);
+ return;
+ }
+
+ const XmlElement* room_name_elem =
+ item_elem->FirstNamed(QN_SEARCH_ROOM_NAME);
+ if (room_name_elem != NULL) {
+ room.name = room_name_elem->BodyText();
+ }
+
+ const XmlElement* room_domain_elem =
+ item_elem->FirstNamed(QN_SEARCH_ROOM_DOMAIN);
+ if (room_domain_elem != NULL) {
+ room.domain = room_domain_elem->BodyText();
+ }
+
+ const XmlElement* hangout_id_elem =
+ item_elem->FirstNamed(QN_SEARCH_HANGOUT_ID);
+ if (hangout_id_elem != NULL) {
+ room.hangout_id = hangout_id_elem->BodyText();
+ }
+
+ SignalResult(this, room);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask.h b/webrtc/libjingle/xmpp/mucroomlookuptask.h
new file mode 100644
index 0000000000..d87b3da20b
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomlookuptask.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_
+
+#include <string>
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+namespace buzz {
+
+struct MucRoomInfo {
+ Jid jid;
+ std::string name;
+ std::string domain;
+ std::string hangout_id;
+
+ std::string full_name() const {
+ return name + "@" + domain;
+ }
+};
+
+class MucRoomLookupTask : public IqTask {
+ public:
+ enum IdType {
+ ID_TYPE_CONVERSATION,
+ ID_TYPE_HANGOUT
+ };
+
+ static MucRoomLookupTask*
+ CreateLookupTaskForRoomName(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& room_name,
+ const std::string& room_domain);
+ static MucRoomLookupTask*
+ CreateLookupTaskForRoomJid(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const Jid& room_jid);
+ static MucRoomLookupTask*
+ CreateLookupTaskForHangoutId(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& hangout_id);
+ static MucRoomLookupTask*
+ CreateLookupTaskForExternalId(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ const std::string& external_id,
+ const std::string& type);
+
+ sigslot::signal2<MucRoomLookupTask*,
+ const MucRoomInfo&> SignalResult;
+
+ protected:
+ virtual void HandleResult(const XmlElement* element);
+
+ private:
+ MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
+ XmlElement* query);
+ static XmlElement* MakeNameQuery(const std::string& room_name,
+ const std::string& room_domain);
+ static XmlElement* MakeJidQuery(const Jid& room_jid);
+ static XmlElement* MakeHangoutIdQuery(const std::string& hangout_id);
+ static XmlElement* MakeExternalIdQuery(const std::string& external_id,
+ const std::string& type);
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMLOOKUPTASK_H_
diff --git a/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc
new file mode 100644
index 0000000000..da5b1458e5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomlookuptask_unittest.cc
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/mucroomlookuptask.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class MucRoomLookupListener : public sigslot::has_slots<> {
+ public:
+ MucRoomLookupListener() : error_count(0) {}
+
+ void OnResult(buzz::MucRoomLookupTask* task,
+ const buzz::MucRoomInfo& room) {
+ last_room = room;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ buzz::MucRoomInfo last_room;
+ int error_count;
+};
+
+class MucRoomLookupTaskTest : public testing::Test {
+ public:
+ MucRoomLookupTaskTest() :
+ lookup_server_jid("lookup@domain.com"),
+ room_jid("muc-jid-ponies@domain.com"),
+ room_name("ponies"),
+ room_domain("domain.com"),
+ room_full_name("ponies@domain.com"),
+ hangout_id("some_hangout_id") {
+ }
+
+ virtual void SetUp() {
+ runner = new rtc::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomLookupListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ rtc::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomLookupListener* listener;
+ buzz::Jid lookup_server_jid;
+ buzz::Jid room_jid;
+ std::string room_name;
+ std::string room_domain;
+ std::string room_full_name;
+ std::string hangout_id;
+};
+
+TEST_F(MucRoomLookupTaskTest, TestLookupName) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ buzz::MucRoomLookupTask* task =
+ buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"lookup@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:search\">"
+ "<room-name>ponies</room-name>"
+ "<room-domain>domain.com</room-domain>"
+ "</query>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ("", listener->last_room.name);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ " <query xmlns='jabber:iq:search'>"
+ " <item jid='muc-jid-ponies@domain.com'>"
+ " <room-name>ponies</room-name>"
+ " <room-domain>domain.com</room-domain>"
+ " </item>"
+ " </query>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(room_name, listener->last_room.name);
+ EXPECT_EQ(room_domain, listener->last_room.domain);
+ EXPECT_EQ(room_jid, listener->last_room.jid);
+ EXPECT_EQ(room_full_name, listener->last_room.full_name());
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestLookupHangoutId) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForHangoutId(
+ xmpp_client, lookup_server_jid, hangout_id);
+ task->SignalResult.connect(listener, &MucRoomLookupListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"lookup@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:search\">"
+ "<hangout-id>some_hangout_id</hangout-id>"
+ "</query>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ("", listener->last_room.name);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ " <query xmlns='jabber:iq:search'>"
+ " <item jid='muc-jid-ponies@domain.com'>"
+ " <room-name>some_hangout_id</room-name>"
+ " <room-domain>domain.com</room-domain>"
+ " </item>"
+ " </query>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(hangout_id, listener->last_room.name);
+ EXPECT_EQ(room_domain, listener->last_room.domain);
+ EXPECT_EQ(room_jid, listener->last_room.jid);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestError) {
+ buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+ task->Start();
+
+ std::string error_iq =
+ "<iq xmlns='jabber:client' id='0' type='error'"
+ " from='lookup@domain.com'>"
+ "</iq>";
+
+ EXPECT_EQ(0, listener->error_count);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(error_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(MucRoomLookupTaskTest, TestBadJid) {
+ buzz::MucRoomLookupTask* task = buzz::MucRoomLookupTask::CreateLookupTaskForRoomName(
+ xmpp_client, lookup_server_jid, room_name, room_domain);
+ task->SignalError.connect(listener, &MucRoomLookupListener::OnError);
+ task->Start();
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ " <query xmlns='jabber:iq:search'>"
+ " <item/>"
+ " </query>"
+ "</iq>";
+
+ EXPECT_EQ(0, listener->error_count);
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc
new file mode 100644
index 0000000000..79ccc29f55
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomUniqueHangoutIdTask::MucRoomUniqueHangoutIdTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid)
+ : IqTask(parent, STR_GET, lookup_server_jid, MakeUniqueRequestXml()) {
+}
+
+// Construct a stanza to request a unique room id. eg:
+//
+// <unique hangout-id="true" xmlns="http://jabber.org/protocol/muc#unique"/>
+XmlElement* MucRoomUniqueHangoutIdTask::MakeUniqueRequestXml() {
+ XmlElement* xml = new XmlElement(QN_MUC_UNIQUE_QUERY, false);
+ xml->SetAttr(QN_HANGOUT_ID, STR_TRUE);
+ return xml;
+}
+
+// Handle a response like the following:
+//
+// <unique hangout-id="hangout_id"
+// xmlns="http://jabber.org/protocol/muc#unique"/>
+// muvc-private-chat-guid@groupchat.google.com
+// </unique>
+void MucRoomUniqueHangoutIdTask::HandleResult(const XmlElement* stanza) {
+
+ const XmlElement* unique_elem = stanza->FirstNamed(QN_MUC_UNIQUE_QUERY);
+ if (unique_elem == NULL ||
+ !unique_elem->HasAttr(QN_HANGOUT_ID)) {
+ SignalError(this, stanza);
+ return;
+ }
+
+ std::string hangout_id = unique_elem->Attr(QN_HANGOUT_ID);
+
+ SignalResult(this, hangout_id);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h
new file mode 100644
index 0000000000..ac662503f1
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
+
+#include "webrtc/libjingle/xmpp/iqtask.h"
+
+namespace buzz {
+
+// Task to request a unique hangout id to be used when starting a hangout.
+// The protocol is described in https://docs.google.com/a/google.com/
+// document/d/1EFLT6rCYPDVdqQXSQliXwqB3iUkpZJ9B_MNFeOZgN7g/edit
+class MucRoomUniqueHangoutIdTask : public buzz::IqTask {
+ public:
+ MucRoomUniqueHangoutIdTask(buzz::XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid);
+ // signal(task, hangout_id)
+ sigslot::signal2<MucRoomUniqueHangoutIdTask*, const std::string&> SignalResult;
+
+ protected:
+ virtual void HandleResult(const buzz::XmlElement* stanza);
+
+ private:
+ static buzz::XmlElement* MakeUniqueRequestXml();
+
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_MUCROOMUNIQUEHANGOUTIDTASK_H_
diff --git a/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc
new file mode 100644
index 0000000000..2480528b41
--- /dev/null
+++ b/webrtc/libjingle/xmpp/mucroomuniquehangoutidtask_unittest.cc
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/mucroomuniquehangoutidtask.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class MucRoomUniqueHangoutIdListener : public sigslot::has_slots<> {
+ public:
+ MucRoomUniqueHangoutIdListener() : error_count(0) {}
+
+ void OnResult(buzz::MucRoomUniqueHangoutIdTask* task,
+ const std::string& hangout_id) {
+ last_hangout_id = hangout_id;
+ }
+
+ void OnError(buzz::IqTask* task,
+ const buzz::XmlElement* error) {
+ ++error_count;
+ }
+
+ std::string last_hangout_id;
+ int error_count;
+};
+
+class MucRoomUniqueHangoutIdTaskTest : public testing::Test {
+ public:
+ MucRoomUniqueHangoutIdTaskTest() :
+ lookup_server_jid("lookup@domain.com"),
+ hangout_id("some_hangout_id") {
+ }
+
+ virtual void SetUp() {
+ runner = new rtc::FakeTaskRunner();
+ xmpp_client = new buzz::FakeXmppClient(runner);
+ listener = new MucRoomUniqueHangoutIdListener();
+ }
+
+ virtual void TearDown() {
+ delete listener;
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ rtc::FakeTaskRunner* runner;
+ buzz::FakeXmppClient* xmpp_client;
+ MucRoomUniqueHangoutIdListener* listener;
+ buzz::Jid lookup_server_jid;
+ std::string hangout_id;
+};
+
+TEST_F(MucRoomUniqueHangoutIdTaskTest, Test) {
+ ASSERT_EQ(0U, xmpp_client->sent_stanzas().size());
+
+ buzz::MucRoomUniqueHangoutIdTask* task = new buzz::MucRoomUniqueHangoutIdTask(
+ xmpp_client, lookup_server_jid);
+ task->SignalResult.connect(listener, &MucRoomUniqueHangoutIdListener::OnResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"lookup@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<uni:unique hangout-id=\"true\" "
+ "xmlns:uni=\"http://jabber.org/protocol/muc#unique\"/>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ EXPECT_EQ("", listener->last_hangout_id);
+
+ std::string response_iq =
+ "<iq xmlns='jabber:client' from='lookup@domain.com' id='0' type='result'>"
+ "<unique hangout-id=\"some_hangout_id\" "
+ "xmlns=\"http://jabber.org/protocol/muc#unique\">"
+ "muvc-private-chat-00001234-5678-9abc-def0-123456789abc"
+ "</unique>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(response_iq));
+
+ EXPECT_EQ(hangout_id, listener->last_hangout_id);
+ EXPECT_EQ(0, listener->error_count);
+}
+
diff --git a/webrtc/libjingle/xmpp/pingtask.cc b/webrtc/libjingle/xmpp/pingtask.cc
new file mode 100644
index 0000000000..479dc23ff5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pingtask.cc
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/pingtask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace buzz {
+
+PingTask::PingTask(buzz::XmppTaskParentInterface* parent,
+ rtc::MessageQueue* message_queue,
+ uint32_t ping_period_millis,
+ uint32_t ping_timeout_millis)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+ message_queue_(message_queue),
+ ping_period_millis_(ping_period_millis),
+ ping_timeout_millis_(ping_timeout_millis),
+ next_ping_time_(0),
+ ping_response_deadline_(0) {
+ ASSERT(ping_period_millis >= ping_timeout_millis);
+}
+
+bool PingTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, Jid(STR_EMPTY), task_id())) {
+ return false;
+ }
+
+ if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT &&
+ stanza->Attr(buzz::QN_TYPE) != buzz::STR_ERROR) {
+ return false;
+ }
+
+ QueueStanza(stanza);
+ return true;
+}
+
+// This task runs indefinitely and remains in either the start or blocked
+// states.
+int PingTask::ProcessStart() {
+ if (ping_period_millis_ < ping_timeout_millis_) {
+ LOG(LS_ERROR) << "ping_period_millis should be >= ping_timeout_millis";
+ return STATE_ERROR;
+ }
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza != NULL) {
+ // Received a ping response of some sort (don't care what it is).
+ ping_response_deadline_ = 0;
+ }
+
+ uint32_t now = rtc::Time();
+
+ // If the ping timed out, signal.
+ if (ping_response_deadline_ != 0 && now >= ping_response_deadline_) {
+ SignalTimeout();
+ return STATE_ERROR;
+ }
+
+ // Send a ping if it's time.
+ if (now >= next_ping_time_) {
+ rtc::scoped_ptr<buzz::XmlElement> stanza(
+ MakeIq(buzz::STR_GET, Jid(STR_EMPTY), task_id()));
+ stanza->AddElement(new buzz::XmlElement(QN_PING));
+ SendStanza(stanza.get());
+
+ ping_response_deadline_ = now + ping_timeout_millis_;
+ next_ping_time_ = now + ping_period_millis_;
+
+ // Wake ourselves up when it's time to send another ping or when the ping
+ // times out (so we can fire a signal).
+ message_queue_->PostDelayed(ping_timeout_millis_, this);
+ message_queue_->PostDelayed(ping_period_millis_, this);
+ }
+
+ return STATE_BLOCKED;
+}
+
+void PingTask::OnMessage(rtc::Message* msg) {
+ // Get the task manager to run this task so we can send a ping or signal or
+ // process a ping response.
+ Wake();
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/pingtask.h b/webrtc/libjingle/xmpp/pingtask.h
new file mode 100644
index 0000000000..22fd94d721
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pingtask.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_
+
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/messagehandler.h"
+#include "webrtc/base/messagequeue.h"
+
+namespace buzz {
+
+// Task to periodically send pings to the server to ensure that the network
+// connection is valid, implementing XEP-0199.
+//
+// This is especially useful on cellular networks because:
+// 1. It keeps the connections alive through the cellular network's NATs or
+// proxies.
+// 2. It detects when the server has crashed or any other case in which the
+// connection has broken without a fin or reset packet being sent to us.
+class PingTask : public buzz::XmppTask, private rtc::MessageHandler {
+ public:
+ PingTask(buzz::XmppTaskParentInterface* parent,
+ rtc::MessageQueue* message_queue,
+ uint32_t ping_period_millis,
+ uint32_t ping_timeout_millis);
+
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+ virtual int ProcessStart();
+
+ // Raised if there is no response to a ping within ping_timeout_millis.
+ // The task is automatically aborted after a timeout.
+ sigslot::signal0<> SignalTimeout;
+
+ private:
+ // Implementation of MessageHandler.
+ virtual void OnMessage(rtc::Message* msg);
+
+ rtc::MessageQueue* message_queue_;
+ uint32_t ping_period_millis_;
+ uint32_t ping_timeout_millis_;
+ uint32_t next_ping_time_;
+ uint32_t ping_response_deadline_; // 0 if the response has been received
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PINGTASK_H_
diff --git a/webrtc/libjingle/xmpp/pingtask_unittest.cc b/webrtc/libjingle/xmpp/pingtask_unittest.cc
new file mode 100644
index 0000000000..b9aab6b3f2
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pingtask_unittest.cc
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/pingtask.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+class PingTaskTest;
+
+class PingXmppClient : public buzz::FakeXmppClient {
+ public:
+ PingXmppClient(rtc::TaskParent* parent, PingTaskTest* tst) :
+ FakeXmppClient(parent), test(tst) {
+ }
+
+ buzz::XmppReturnStatus SendStanza(const buzz::XmlElement* stanza);
+
+ private:
+ PingTaskTest* test;
+};
+
+class PingTaskTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ PingTaskTest() : respond_to_pings(true), timed_out(false) {
+ }
+
+ virtual void SetUp() {
+ runner = new rtc::FakeTaskRunner();
+ xmpp_client = new PingXmppClient(runner, this);
+ }
+
+ virtual void TearDown() {
+ // delete xmpp_client; Deleted by deleting runner.
+ delete runner;
+ }
+
+ void ConnectTimeoutSignal(buzz::PingTask* task) {
+ task->SignalTimeout.connect(this, &PingTaskTest::OnPingTimeout);
+ }
+
+ void OnPingTimeout() {
+ timed_out = true;
+ }
+
+ rtc::FakeTaskRunner* runner;
+ PingXmppClient* xmpp_client;
+ bool respond_to_pings;
+ bool timed_out;
+};
+
+buzz::XmppReturnStatus PingXmppClient::SendStanza(
+ const buzz::XmlElement* stanza) {
+ buzz::XmppReturnStatus result = FakeXmppClient::SendStanza(stanza);
+ if (test->respond_to_pings && (stanza->FirstNamed(buzz::QN_PING) != NULL)) {
+ std::string ping_response =
+ "<iq xmlns=\'jabber:client\' id='0' type='result'/>";
+ HandleStanza(buzz::XmlElement::ForStr(ping_response));
+ }
+ return result;
+}
+
+TEST_F(PingTaskTest, TestSuccess) {
+ uint32_t ping_period_millis = 100;
+ buzz::PingTask* task = new buzz::PingTask(xmpp_client,
+ rtc::Thread::Current(),
+ ping_period_millis, ping_period_millis / 10);
+ ConnectTimeoutSignal(task);
+ task->Start();
+ unsigned int expected_ping_count = 5U;
+ EXPECT_EQ_WAIT(xmpp_client->sent_stanzas().size(), expected_ping_count,
+ ping_period_millis * (expected_ping_count + 1));
+ EXPECT_FALSE(task->IsDone());
+ EXPECT_FALSE(timed_out);
+}
+
+TEST_F(PingTaskTest, TestTimeout) {
+ respond_to_pings = false;
+ uint32_t ping_timeout_millis = 200;
+ buzz::PingTask* task = new buzz::PingTask(xmpp_client,
+ rtc::Thread::Current(),
+ ping_timeout_millis * 10, ping_timeout_millis);
+ ConnectTimeoutSignal(task);
+ task->Start();
+ WAIT(false, ping_timeout_millis / 2);
+ EXPECT_FALSE(timed_out);
+ EXPECT_TRUE_WAIT(timed_out, ping_timeout_millis * 2);
+}
diff --git a/webrtc/libjingle/xmpp/plainsaslhandler.h b/webrtc/libjingle/xmpp/plainsaslhandler.h
new file mode 100644
index 0000000000..aa6a791ebc
--- /dev/null
+++ b/webrtc/libjingle/xmpp/plainsaslhandler.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_
+#define WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_
+
+#include <algorithm>
+#include "webrtc/libjingle/xmpp/saslhandler.h"
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h"
+#include "webrtc/base/cryptstring.h"
+
+namespace buzz {
+
+class PlainSaslHandler : public SaslHandler {
+public:
+ PlainSaslHandler(const Jid & jid, const rtc::CryptString & password,
+ bool allow_plain) : jid_(jid), password_(password),
+ allow_plain_(allow_plain) {}
+
+ virtual ~PlainSaslHandler() {}
+
+ // Should pick the best method according to this handler
+ // returns the empty string if none are suitable
+ virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+
+ if (!encrypted && !allow_plain_) {
+ return "";
+ }
+
+ std::vector<std::string>::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it == mechanisms.end()) {
+ return "";
+ }
+ else {
+ return "PLAIN";
+ }
+ }
+
+ // Creates a SaslMechanism for the given mechanism name (you own it
+ // once you get it). If not handled, return NULL.
+ virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) {
+ if (mechanism == "PLAIN") {
+ return new SaslPlainMechanism(jid_, password_);
+ }
+ return NULL;
+ }
+
+private:
+ Jid jid_;
+ rtc::CryptString password_;
+ bool allow_plain_;
+};
+
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PLAINSASLHANDLER_H_
diff --git a/webrtc/libjingle/xmpp/presenceouttask.cc b/webrtc/libjingle/xmpp/presenceouttask.cc
new file mode 100644
index 0000000000..aa19c9dd77
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presenceouttask.cc
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <time.h>
+#include <sstream>
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/presenceouttask.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+#include "webrtc/base/stringencode.h"
+
+namespace buzz {
+
+XmppReturnStatus
+PresenceOutTask::Send(const PresenceStatus & s) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement * presence = TranslateStatus(s);
+ QueueStanza(presence);
+ delete presence;
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+PresenceOutTask::SendDirected(const Jid & j, const PresenceStatus & s) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement * presence = TranslateStatus(s);
+ presence->AddAttr(QN_TO, j.Str());
+ QueueStanza(presence);
+ delete presence;
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement * presence = new XmlElement(QN_PRESENCE);
+ presence->AddAttr(QN_TO, jid.Str());
+ presence->AddAttr(QN_TYPE, "probe");
+
+ QueueStanza(presence);
+ delete presence;
+ return XMPP_RETURN_OK;
+}
+
+int
+PresenceOutTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ if (SendStanza(stanza) != XMPP_RETURN_OK)
+ return STATE_ERROR;
+
+ return STATE_START;
+}
+
+XmlElement *
+PresenceOutTask::TranslateStatus(const PresenceStatus & s) {
+ XmlElement * result = new XmlElement(QN_PRESENCE);
+ if (!s.available()) {
+ result->AddAttr(QN_TYPE, STR_UNAVAILABLE);
+ }
+ else {
+ if (s.show() != PresenceStatus::SHOW_ONLINE &&
+ s.show() != PresenceStatus::SHOW_OFFLINE) {
+ result->AddElement(new XmlElement(QN_SHOW));
+ switch (s.show()) {
+ default:
+ result->AddText(STR_SHOW_AWAY, 1);
+ break;
+ case PresenceStatus::SHOW_XA:
+ result->AddText(STR_SHOW_XA, 1);
+ break;
+ case PresenceStatus::SHOW_DND:
+ result->AddText(STR_SHOW_DND, 1);
+ break;
+ case PresenceStatus::SHOW_CHAT:
+ result->AddText(STR_SHOW_CHAT, 1);
+ break;
+ }
+ }
+
+ result->AddElement(new XmlElement(QN_STATUS));
+ result->AddText(s.status(), 1);
+
+ if (!s.nick().empty()) {
+ result->AddElement(new XmlElement(QN_NICKNAME));
+ result->AddText(s.nick(), 1);
+ }
+
+ std::string pri;
+ rtc::ToString(s.priority(), &pri);
+
+ result->AddElement(new XmlElement(QN_PRIORITY));
+ result->AddText(pri, 1);
+
+ if (s.know_capabilities()) {
+ result->AddElement(new XmlElement(QN_CAPS_C, true));
+ result->AddAttr(QN_NODE, s.caps_node(), 1);
+ result->AddAttr(QN_VER, s.version(), 1);
+
+ std::string caps;
+ caps.append(s.voice_capability() ? "voice-v1" : "");
+ caps.append(s.pmuc_capability() ? " pmuc-v1" : "");
+ caps.append(s.video_capability() ? " video-v1" : "");
+ caps.append(s.camera_capability() ? " camera-v1" : "");
+
+ result->AddAttr(QN_EXT, caps, 1);
+ }
+
+ // Put the delay mark on the presence according to JEP-0091
+ {
+ result->AddElement(new XmlElement(kQnDelayX, true));
+
+ // This here is why we *love* the C runtime
+ time_t current_time_seconds;
+ time(&current_time_seconds);
+ struct tm* current_time = gmtime(&current_time_seconds);
+ char output[256];
+ strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time);
+ result->AddAttr(kQnStamp, output, 1);
+ }
+ }
+
+ return result;
+}
+
+
+}
diff --git a/webrtc/libjingle/xmpp/presenceouttask.h b/webrtc/libjingle/xmpp/presenceouttask.h
new file mode 100644
index 0000000000..88869df3cc
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presenceouttask.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_
+
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class PresenceOutTask : public XmppTask {
+public:
+ explicit PresenceOutTask(XmppTaskParentInterface* parent)
+ : XmppTask(parent) {}
+ virtual ~PresenceOutTask() {}
+
+ XmppReturnStatus Send(const PresenceStatus & s);
+ XmppReturnStatus SendDirected(const Jid & j, const PresenceStatus & s);
+ XmppReturnStatus SendProbe(const Jid& jid);
+
+ virtual int ProcessStart();
+private:
+ XmlElement * TranslateStatus(const PresenceStatus & s);
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PRESENCEOUTTASK_H_
diff --git a/webrtc/libjingle/xmpp/presencereceivetask.cc b/webrtc/libjingle/xmpp/presencereceivetask.cc
new file mode 100644
index 0000000000..3ea7274c76
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presencereceivetask.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/presencereceivetask.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/stringencode.h"
+
+namespace buzz {
+
+static bool IsUtf8FirstByte(int c) {
+ return (((c)&0x80)==0) || // is single byte
+ ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+PresenceReceiveTask::PresenceReceiveTask(XmppTaskParentInterface* parent)
+ : XmppTask(parent, XmppEngine::HL_TYPE) {
+}
+
+PresenceReceiveTask::~PresenceReceiveTask() {
+ Stop();
+}
+
+int PresenceReceiveTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+
+ Jid from(stanza->Attr(QN_FROM));
+ HandlePresence(from, stanza);
+
+ return STATE_START;
+}
+
+bool PresenceReceiveTask::HandleStanza(const XmlElement * stanza) {
+ // Verify that this is a presence stanze
+ if (stanza->Name() != QN_PRESENCE) {
+ return false; // not sure if this ever happens.
+ }
+
+ // Queue it up
+ QueueStanza(stanza);
+
+ return true;
+}
+
+void PresenceReceiveTask::HandlePresence(const Jid& from,
+ const XmlElement* stanza) {
+ if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+ return;
+ }
+
+ PresenceStatus status;
+ DecodeStatus(from, stanza, &status);
+ PresenceUpdate(status);
+}
+
+void PresenceReceiveTask::DecodeStatus(const Jid& from,
+ const XmlElement* stanza,
+ PresenceStatus* presence_status) {
+ presence_status->set_jid(from);
+ if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+ presence_status->set_available(false);
+ } else {
+ presence_status->set_available(true);
+ const XmlElement * status_elem = stanza->FirstNamed(QN_STATUS);
+ if (status_elem != NULL) {
+ presence_status->set_status(status_elem->BodyText());
+
+ // Truncate status messages longer than 300 bytes
+ if (presence_status->status().length() > 300) {
+ size_t len = 300;
+
+ // Be careful not to split legal utf-8 chars in half
+ while (!IsUtf8FirstByte(presence_status->status()[len]) && len > 0) {
+ len -= 1;
+ }
+ std::string truncated(presence_status->status(), 0, len);
+ presence_status->set_status(truncated);
+ }
+ }
+
+ const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+ if (priority != NULL) {
+ int pri;
+ if (rtc::FromString(priority->BodyText(), &pri)) {
+ presence_status->set_priority(pri);
+ }
+ }
+
+ const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+ if (show == NULL || show->FirstChild() == NULL) {
+ presence_status->set_show(PresenceStatus::SHOW_ONLINE);
+ } else if (show->BodyText() == "away") {
+ presence_status->set_show(PresenceStatus::SHOW_AWAY);
+ } else if (show->BodyText() == "xa") {
+ presence_status->set_show(PresenceStatus::SHOW_XA);
+ } else if (show->BodyText() == "dnd") {
+ presence_status->set_show(PresenceStatus::SHOW_DND);
+ } else if (show->BodyText() == "chat") {
+ presence_status->set_show(PresenceStatus::SHOW_CHAT);
+ } else {
+ presence_status->set_show(PresenceStatus::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);
+
+ presence_status->set_know_capabilities(true);
+ presence_status->set_caps_node(node);
+ presence_status->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);
+ presence_status->set_sent_time(stamp);
+ }
+
+ const XmlElement* nick = stanza->FirstNamed(QN_NICKNAME);
+ if (nick) {
+ presence_status->set_nick(nick->BodyText());
+ }
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/presencereceivetask.h b/webrtc/libjingle/xmpp/presencereceivetask.h
new file mode 100644
index 0000000000..20e6c79328
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presencereceivetask.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_
+#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_
+
+#include "webrtc/base/sigslot.h"
+
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A task to receive presence status callbacks from the XMPP server.
+class PresenceReceiveTask : public XmppTask {
+ public:
+ // Arguments:
+ // parent a reference to task interface associated withe the XMPP client.
+ explicit PresenceReceiveTask(XmppTaskParentInterface* parent);
+
+ // Shuts down the thread associated with this task.
+ virtual ~PresenceReceiveTask();
+
+ // Starts pulling queued status messages and dispatching them to the
+ // PresenceUpdate() callback.
+ virtual int ProcessStart();
+
+ // Slot for presence message callbacks
+ sigslot::signal1<const PresenceStatus&> PresenceUpdate;
+
+ protected:
+ // Called by the XMPP engine when presence stanzas are received from the
+ // server.
+ virtual bool HandleStanza(const XmlElement * stanza);
+
+ private:
+ // Handles presence stanzas by converting the data to PresenceStatus
+ // objects and passing those along to the SignalStatusUpadate() callback.
+ void HandlePresence(const Jid& from, const XmlElement * stanza);
+
+ // Extracts presence information for the presence stanza sent form the
+ // server.
+ static void DecodeStatus(const Jid& from, const XmlElement * stanza,
+ PresenceStatus* status);
+};
+
+} // namespace buzz
+
+#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCERECEIVETASK_H_
diff --git a/webrtc/libjingle/xmpp/presencestatus.cc b/webrtc/libjingle/xmpp/presencestatus.cc
new file mode 100644
index 0000000000..da6c64f1a2
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presencestatus.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/presencestatus.h"
+
+namespace buzz {
+PresenceStatus::PresenceStatus()
+ : pri_(0),
+ show_(SHOW_NONE),
+ available_(false),
+ e_code_(0),
+ feedback_probation_(false),
+ know_capabilities_(false),
+ voice_capability_(false),
+ pmuc_capability_(false),
+ video_capability_(false),
+ camera_capability_(false) {
+}
+
+void PresenceStatus::UpdateWith(const PresenceStatus& new_value) {
+ if (!new_value.know_capabilities()) {
+ bool k = know_capabilities();
+ bool p = voice_capability();
+ std::string node = caps_node();
+ std::string v = version();
+
+ *this = new_value;
+
+ set_know_capabilities(k);
+ set_caps_node(node);
+ set_voice_capability(p);
+ set_version(v);
+ } else {
+ *this = new_value;
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/presencestatus.h b/webrtc/libjingle/xmpp/presencestatus.h
new file mode 100644
index 0000000000..0261c72b06
--- /dev/null
+++ b/webrtc/libjingle/xmpp/presencestatus.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_
+#define THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+
+namespace buzz {
+
+class PresenceStatus {
+public:
+ PresenceStatus();
+ ~PresenceStatus() {}
+
+ // These are arranged in "priority order", i.e., if we see
+ // two statuses at the same priority but with different Shows,
+ // we will show the one with the highest show in the following
+ // order.
+ enum Show {
+ SHOW_NONE = 0,
+ SHOW_OFFLINE = 1,
+ SHOW_XA = 2,
+ SHOW_AWAY = 3,
+ SHOW_DND = 4,
+ SHOW_ONLINE = 5,
+ SHOW_CHAT = 6,
+ };
+
+ const Jid& jid() const { return jid_; }
+ int priority() const { return pri_; }
+ Show show() const { return show_; }
+ const std::string& status() const { return status_; }
+ const std::string& nick() const { return nick_; }
+ bool available() const { return available_ ; }
+ int error_code() const { return e_code_; }
+ const std::string& error_string() const { return e_str_; }
+ bool know_capabilities() const { return know_capabilities_; }
+ bool voice_capability() const { return voice_capability_; }
+ bool pmuc_capability() const { return pmuc_capability_; }
+ bool video_capability() const { return video_capability_; }
+ bool camera_capability() const { return camera_capability_; }
+ const std::string& caps_node() const { return caps_node_; }
+ const std::string& version() const { return version_; }
+ bool feedback_probation() const { return feedback_probation_; }
+ const std::string& sent_time() const { return sent_time_; }
+
+ void set_jid(const Jid& jid) { jid_ = jid; }
+ void set_priority(int pri) { pri_ = pri; }
+ void set_show(Show show) { show_ = show; }
+ void set_status(const std::string& status) { status_ = status; }
+ void set_nick(const std::string& nick) { nick_ = nick; }
+ void set_available(bool a) { available_ = a; }
+ void set_error(int e_code, const std::string e_str)
+ { e_code_ = e_code; e_str_ = e_str; }
+ void set_know_capabilities(bool f) { know_capabilities_ = f; }
+ void set_voice_capability(bool f) { voice_capability_ = f; }
+ void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
+ void set_video_capability(bool f) { video_capability_ = f; }
+ void set_camera_capability(bool f) { camera_capability_ = f; }
+ void set_caps_node(const std::string& f) { caps_node_ = f; }
+ void set_version(const std::string& v) { version_ = v; }
+ void set_feedback_probation(bool f) { feedback_probation_ = f; }
+ void set_sent_time(const std::string& time) { sent_time_ = time; }
+
+ void UpdateWith(const PresenceStatus& new_value);
+
+ bool HasQuietStatus() const {
+ if (status_.empty())
+ return false;
+ return !(QuietStatus().empty());
+ }
+
+ // Knowledge of other clients' silly automatic status strings -
+ // Don't show these.
+ std::string QuietStatus() const {
+ if (jid_.resource().find("Psi") != std::string::npos) {
+ if (status_ == "Online" ||
+ status_.find("Auto Status") != std::string::npos)
+ return STR_EMPTY;
+ }
+ if (jid_.resource().find("Gaim") != std::string::npos) {
+ if (status_ == "Sorry, I ran out for a bit!")
+ return STR_EMPTY;
+ }
+ return TrimStatus(status_);
+ }
+
+ std::string ExplicitStatus() const {
+ std::string result = QuietStatus();
+ if (result.empty()) {
+ result = ShowStatus();
+ }
+ return result;
+ }
+
+ std::string ShowStatus() const {
+ std::string result;
+ if (!available()) {
+ result = "Offline";
+ }
+ else {
+ switch (show()) {
+ case SHOW_AWAY:
+ case SHOW_XA:
+ result = "Idle";
+ break;
+ case SHOW_DND:
+ result = "Busy";
+ break;
+ case SHOW_CHAT:
+ result = "Chatty";
+ break;
+ default:
+ result = "Available";
+ break;
+ }
+ }
+ return result;
+ }
+
+ static std::string TrimStatus(const std::string& st) {
+ std::string s(st);
+ int j = 0;
+ bool collapsing = true;
+ for (unsigned int i = 0; i < s.length(); i+= 1) {
+ if (s[i] <= ' ' && s[i] >= 0) {
+ if (collapsing) {
+ continue;
+ }
+ else {
+ s[j] = ' ';
+ j += 1;
+ collapsing = true;
+ }
+ }
+ else {
+ s[j] = s[i];
+ j += 1;
+ collapsing = false;
+ }
+ }
+ if (collapsing && j > 0) {
+ j -= 1;
+ }
+ s.erase(j, s.length());
+ return s;
+ }
+
+private:
+ Jid jid_;
+ int pri_;
+ Show show_;
+ std::string status_;
+ std::string nick_;
+ bool available_;
+ int e_code_;
+ std::string e_str_;
+ bool feedback_probation_;
+
+ // capabilities (valid only if know_capabilities_
+ bool know_capabilities_;
+ bool voice_capability_;
+ bool pmuc_capability_;
+ bool video_capability_;
+ bool camera_capability_;
+ std::string caps_node_;
+ std::string version_;
+
+ std::string sent_time_; // from the jabber:x:delay element
+};
+
+class MucPresenceStatus : public PresenceStatus {
+};
+
+} // namespace buzz
+
+
+#endif // THIRD_PARTY_LIBJINGLE_FILES_WEBRTC_LIBJINGLE_XMPP_PRESENCESTATUS_H_
+
diff --git a/webrtc/libjingle/xmpp/prexmppauth.h b/webrtc/libjingle/xmpp/prexmppauth.h
new file mode 100644
index 0000000000..3a1e61064d
--- /dev/null
+++ b/webrtc/libjingle/xmpp/prexmppauth.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_
+#define WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_
+
+#include "webrtc/libjingle/xmpp/saslhandler.h"
+#include "webrtc/base/cryptstring.h"
+#include "webrtc/base/sigslot.h"
+
+namespace rtc {
+ class SocketAddress;
+}
+
+namespace buzz {
+
+class Jid;
+class SaslMechanism;
+
+class CaptchaChallenge {
+ public:
+ CaptchaChallenge() : captcha_needed_(false) {}
+ CaptchaChallenge(const std::string& token, const std::string& url)
+ : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) {
+ }
+
+ bool captcha_needed() const { return captcha_needed_; }
+ const std::string& captcha_token() const { return captcha_token_; }
+
+ // This url is relative to the gaia server. Once we have better tools
+ // for cracking URLs, we should probably make this a full URL
+ const std::string& captcha_image_url() const { return captcha_image_url_; }
+
+ private:
+ bool captcha_needed_;
+ std::string captcha_token_;
+ std::string captcha_image_url_;
+};
+
+class PreXmppAuth : public SaslHandler {
+public:
+ virtual ~PreXmppAuth() {}
+
+ virtual void StartPreXmppAuth(
+ const Jid& jid,
+ const rtc::SocketAddress& server,
+ const rtc::CryptString& pass,
+ const std::string& auth_mechanism,
+ const std::string& auth_token) = 0;
+
+ sigslot::signal0<> SignalAuthDone;
+
+ virtual bool IsAuthDone() const = 0;
+ virtual bool IsAuthorized() const = 0;
+ virtual bool HadError() const = 0;
+ virtual int GetError() const = 0;
+ virtual CaptchaChallenge GetCaptchaChallenge() const = 0;
+ virtual std::string GetAuthMechanism() const = 0;
+ virtual std::string GetAuthToken() const = 0;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PREXMPPAUTH_H_
diff --git a/webrtc/libjingle/xmpp/pubsub_task.cc b/webrtc/libjingle/xmpp/pubsub_task.cc
new file mode 100644
index 0000000000..f30c051814
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsub_task.cc
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/pubsub_task.h"
+
+#include <map>
+#include <string>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/common.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;
+ rtc::scoped_ptr<buzz::XmlElement> get_iq_request(
+ MakeIq(buzz::STR_GET, pubsub_node_jid_, task_id()));
+ if (!get_iq_request) {
+ 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/webrtc/libjingle/xmpp/pubsub_task.h b/webrtc/libjingle/xmpp/pubsub_task.h
new file mode 100644
index 0000000000..b1923a0eb6
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsub_task.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_
+
+#include <map>
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/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 // WEBRTC_LIBJINGLE_XMPP_PUBSUB_TASK_H_
diff --git a/webrtc/libjingle/xmpp/pubsubclient.cc b/webrtc/libjingle/xmpp/pubsubclient.cc
new file mode 100644
index 0000000000..41e4e983d3
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubclient.cc
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/pubsubclient.h"
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubtasks.h"
+
+namespace buzz {
+
+void PubSubClient::RequestItems() {
+ PubSubRequestTask* request_task =
+ new PubSubRequestTask(parent_, pubsubjid_, node_);
+ request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult);
+ request_task->SignalError.connect(this, &PubSubClient::OnRequestError);
+
+ PubSubReceiveTask* receive_task =
+ new PubSubReceiveTask(parent_, pubsubjid_, node_);
+ receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate);
+
+ receive_task->Start();
+ request_task->Start();
+}
+
+void PubSubClient::PublishItem(
+ const std::string& itemid, XmlElement* payload, std::string* task_id_out) {
+ std::vector<XmlElement*> children;
+ children.push_back(payload);
+ PublishItem(itemid, children, task_id_out);
+}
+
+void PubSubClient::PublishItem(
+ const std::string& itemid, const std::vector<XmlElement*>& children,
+ std::string* task_id_out) {
+ PubSubPublishTask* publish_task =
+ new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children);
+ publish_task->SignalError.connect(this, &PubSubClient::OnPublishError);
+ publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult);
+ publish_task->Start();
+ if (task_id_out) {
+ *task_id_out = publish_task->task_id();
+ }
+}
+
+void PubSubClient::RetractItem(
+ const std::string& itemid, std::string* task_id_out) {
+ PubSubRetractTask* retract_task =
+ new PubSubRetractTask(parent_, pubsubjid_, node_, itemid);
+ retract_task->SignalError.connect(this, &PubSubClient::OnRetractError);
+ retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult);
+ retract_task->Start();
+ if (task_id_out) {
+ *task_id_out = retract_task->task_id();
+ }
+}
+
+void PubSubClient::OnRequestResult(PubSubRequestTask* task,
+ const std::vector<PubSubItem>& items) {
+ SignalItems(this, items);
+}
+
+void PubSubClient::OnRequestError(IqTask* task,
+ const XmlElement* stanza) {
+ SignalRequestError(this, stanza);
+}
+
+void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task,
+ const std::vector<PubSubItem>& items) {
+ SignalItems(this, items);
+}
+
+const XmlElement* GetItemFromStanza(const XmlElement* stanza) {
+ if (stanza != NULL) {
+ const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB);
+ if (pubsub != NULL) {
+ const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH);
+ if (publish != NULL) {
+ return publish->FirstNamed(QN_PUBSUB_ITEM);
+ }
+ }
+ }
+ return NULL;
+}
+
+void PubSubClient::OnPublishResult(PubSubPublishTask* task) {
+ const XmlElement* item = GetItemFromStanza(task->stanza());
+ SignalPublishResult(this, task->task_id(), item);
+}
+
+void PubSubClient::OnPublishError(IqTask* task,
+ const XmlElement* error_stanza) {
+ PubSubPublishTask* publish_task =
+ static_cast<PubSubPublishTask*>(task);
+ const XmlElement* item = GetItemFromStanza(publish_task->stanza());
+ SignalPublishError(this, publish_task->task_id(), item, error_stanza);
+}
+
+void PubSubClient::OnRetractResult(PubSubRetractTask* task) {
+ SignalRetractResult(this, task->task_id());
+}
+
+void PubSubClient::OnRetractError(IqTask* task,
+ const XmlElement* stanza) {
+ PubSubRetractTask* retract_task =
+ static_cast<PubSubRetractTask*>(task);
+ SignalRetractError(this, retract_task->task_id(), stanza);
+}
+
+
+const std::string PubSubClient::GetPublisherNickFromPubSubItem(
+ const XmlElement* item_elem) {
+ if (item_elem == NULL) {
+ return "";
+ }
+
+ return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
+}
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/pubsubclient.h b/webrtc/libjingle/xmpp/pubsubclient.h
new file mode 100644
index 0000000000..3044b9dc35
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubclient.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_
+#define WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubtasks.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sigslotrepeater.h"
+#include "webrtc/base/task.h"
+
+// Easy to use clients built on top of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// An easy-to-use pubsub client that handles the three tasks of
+// getting, publishing, and listening for updates. Tied to a specific
+// pubsub jid and node. All you have to do is RequestItems, listen
+// for SignalItems and PublishItems.
+class PubSubClient : public sigslot::has_slots<> {
+ public:
+ PubSubClient(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : parent_(parent),
+ pubsubjid_(pubsubjid),
+ node_(node) {}
+
+ const std::string& node() const { return node_; }
+
+ // Requests the <pubsub><items>, which will be returned via
+ // SignalItems, or SignalRequestError if there is a failure. Should
+ // auto-subscribe.
+ void RequestItems();
+ // Fired when either <pubsub><items> are returned or when
+ // <event><items> are received.
+ sigslot::signal2<PubSubClient*,
+ const std::vector<PubSubItem>&> SignalItems;
+ // Signal (this, error stanza)
+ sigslot::signal2<PubSubClient*,
+ const XmlElement*> SignalRequestError;
+ // Signal (this, task_id, item, error stanza)
+ sigslot::signal4<PubSubClient*,
+ const std::string&,
+ const XmlElement*,
+ const XmlElement*> SignalPublishError;
+ // Signal (this, task_id, item)
+ sigslot::signal3<PubSubClient*,
+ const std::string&,
+ const XmlElement*> SignalPublishResult;
+ // Signal (this, task_id, error stanza)
+ sigslot::signal3<PubSubClient*,
+ const std::string&,
+ const XmlElement*> SignalRetractError;
+ // Signal (this, task_id)
+ sigslot::signal2<PubSubClient*,
+ const std::string&> SignalRetractResult;
+
+ // Publish an item. Takes ownership of payload.
+ void PublishItem(const std::string& itemid,
+ XmlElement* payload,
+ std::string* task_id_out);
+ // Publish an item. Takes ownership of children.
+ void PublishItem(const std::string& itemid,
+ const std::vector<XmlElement*>& children,
+ std::string* task_id_out);
+ // Retract (delete) an item.
+ void RetractItem(const std::string& itemid,
+ std::string* task_id_out);
+
+ // Get the publisher nick if it exists from the pubsub item.
+ const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem);
+
+ private:
+ void OnRequestError(IqTask* task,
+ const XmlElement* stanza);
+ void OnRequestResult(PubSubRequestTask* task,
+ const std::vector<PubSubItem>& items);
+ void OnReceiveUpdate(PubSubReceiveTask* task,
+ const std::vector<PubSubItem>& items);
+ void OnPublishResult(PubSubPublishTask* task);
+ void OnPublishError(IqTask* task,
+ const XmlElement* stanza);
+ void OnRetractResult(PubSubRetractTask* task);
+ void OnRetractError(IqTask* task,
+ const XmlElement* stanza);
+
+ XmppTaskParentInterface* parent_;
+ Jid pubsubjid_;
+ std::string node_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBCLIENT_H_
diff --git a/webrtc/libjingle/xmpp/pubsubclient_unittest.cc b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc
new file mode 100644
index 0000000000..3815ef8a50
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubclient_unittest.cc
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubclient.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+struct HandledPubSubItem {
+ std::string itemid;
+ std::string payload;
+};
+
+class TestPubSubItemsListener : public sigslot::has_slots<> {
+ public:
+ TestPubSubItemsListener() : error_count(0) {}
+
+ void OnItems(buzz::PubSubClient*,
+ const std::vector<buzz::PubSubItem>& items) {
+ for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ HandledPubSubItem handled_item;
+ handled_item.itemid = item->itemid;
+ if (item->elem->FirstElement() != NULL) {
+ handled_item.payload = item->elem->FirstElement()->Str();
+ }
+ this->items.push_back(handled_item);
+ }
+ }
+
+ void OnRequestError(buzz::PubSubClient* client,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ }
+
+ void OnPublishResult(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* item) {
+ result_task_id = task_id;
+ }
+
+ void OnPublishError(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* item,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ error_task_id = task_id;
+ }
+
+ void OnRetractResult(buzz::PubSubClient* client,
+ const std::string& task_id) {
+ result_task_id = task_id;
+ }
+
+ void OnRetractError(buzz::PubSubClient* client,
+ const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ error_count++;
+ error_task_id = task_id;
+ }
+
+ std::vector<HandledPubSubItem> items;
+ int error_count;
+ std::string error_task_id;
+ std::string result_task_id;
+};
+
+class PubSubClientTest : public testing::Test {
+ public:
+ PubSubClientTest() :
+ pubsubjid("room@domain.com"),
+ node("topic"),
+ itemid("key") {
+ runner.reset(new rtc::FakeTaskRunner());
+ xmpp_client = new buzz::FakeXmppClient(runner.get());
+ client.reset(new buzz::PubSubClient(xmpp_client, pubsubjid, node));
+ listener.reset(new TestPubSubItemsListener());
+ client->SignalItems.connect(
+ listener.get(), &TestPubSubItemsListener::OnItems);
+ client->SignalRequestError.connect(
+ listener.get(), &TestPubSubItemsListener::OnRequestError);
+ client->SignalPublishResult.connect(
+ listener.get(), &TestPubSubItemsListener::OnPublishResult);
+ client->SignalPublishError.connect(
+ listener.get(), &TestPubSubItemsListener::OnPublishError);
+ client->SignalRetractResult.connect(
+ listener.get(), &TestPubSubItemsListener::OnRetractResult);
+ client->SignalRetractError.connect(
+ listener.get(), &TestPubSubItemsListener::OnRetractError);
+ }
+
+ rtc::scoped_ptr<rtc::FakeTaskRunner> runner;
+ // xmpp_client deleted by deleting runner.
+ buzz::FakeXmppClient* xmpp_client;
+ rtc::scoped_ptr<buzz::PubSubClient> client;
+ rtc::scoped_ptr<TestPubSubItemsListener> listener;
+ buzz::Jid pubsubjid;
+ std::string node;
+ std::string itemid;
+};
+
+TEST_F(PubSubClientTest, TestRequest) {
+ client->RequestItems();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"topic\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0a/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1a/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ("<pub:value0a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ("<pub:value1a xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[1].payload);
+
+ std::string items_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0b/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1b/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+ ASSERT_EQ(4U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[2].itemid);
+ EXPECT_EQ("<eve:value0b"
+ " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[2].payload);
+ EXPECT_EQ("key1", listener->items[3].itemid);
+ EXPECT_EQ("<eve:value1b"
+ " xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[3].payload);
+}
+
+TEST_F(PubSubClientTest, TestRequestError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ client->RequestItems();
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubClientTest, TestPublish) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::string task_id;
+ client->PublishItem(itemid, payload, &task_id);
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"topic\">"
+ "<item id=\"key\">"
+ "<value/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestPublishError) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::string task_id;
+ client->PublishItem(itemid, payload, &task_id);
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+ EXPECT_EQ(task_id, listener->error_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetract) {
+ std::string task_id;
+ client->RetractItem(itemid, &task_id);
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"topic\" notify=\"true\">"
+ "<item id=\"key\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, xmpp_client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, xmpp_client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(task_id, listener->result_task_id);
+}
+
+TEST_F(PubSubClientTest, TestRetractError) {
+ std::string task_id;
+ client->RetractItem(itemid, &task_id);
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ xmpp_client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+ EXPECT_EQ(1, listener->error_count);
+ EXPECT_EQ(task_id, listener->error_task_id);
+}
diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.cc b/webrtc/libjingle/xmpp/pubsubstateclient.cc
new file mode 100644
index 0000000000..1fae25dfb7
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubstateclient.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/pubsubstateclient.h"
+
+namespace buzz {
+
+std::string PublishedNickKeySerializer::GetKey(
+ const std::string& publisher_nick, const std::string& published_nick) {
+ return published_nick;
+}
+
+std::string PublisherAndPublishedNicksKeySerializer::GetKey(
+ const std::string& publisher_nick, const std::string& published_nick) {
+ return publisher_nick + ":" + published_nick;
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/pubsubstateclient.h b/webrtc/libjingle/xmpp/pubsubstateclient.h
new file mode 100644
index 0000000000..ffb794af4c
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubstateclient.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
+#define WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubclient.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/sigslotrepeater.h"
+
+namespace buzz {
+
+// To handle retracts correctly, we need to remember certain details
+// about an item. We could just cache the entire XML element, but
+// that would take more memory and require re-parsing.
+struct StateItemInfo {
+ std::string published_nick;
+ std::string publisher_nick;
+};
+
+// Represents a PubSub state change. Usually, the key is the nick,
+// but not always. It's a per-state-type thing. Look below on how keys are
+// computed.
+template <typename C>
+struct PubSubStateChange {
+ // The nick of the user changing the state.
+ std::string publisher_nick;
+ // The nick of the user whose state is changing.
+ std::string published_nick;
+ C old_state;
+ C new_state;
+};
+
+// Knows how to handle specific states and XML.
+template <typename C>
+class PubSubStateSerializer {
+ public:
+ virtual ~PubSubStateSerializer() {}
+ virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
+ virtual void Parse(const XmlElement* state_elem, C* state_out) = 0;
+};
+
+// Knows how to create "keys" for states, which determines their
+// uniqueness. Most states are per-nick, but block is
+// per-blocker-and-blockee. This is independent of itemid, especially
+// in the case of presenter state.
+class PubSubStateKeySerializer {
+ public:
+ virtual ~PubSubStateKeySerializer() {}
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick) = 0;
+};
+
+class PublishedNickKeySerializer : public PubSubStateKeySerializer {
+ public:
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick);
+};
+
+class PublisherAndPublishedNicksKeySerializer
+ : public PubSubStateKeySerializer {
+ public:
+ virtual std::string GetKey(const std::string& publisher_nick,
+ const std::string& published_nick);
+};
+
+// Adapts PubSubClient to be specifically suited for pub sub call
+// states. Signals state changes and keeps track of keys, which are
+// normally nicks.
+template <typename C>
+class PubSubStateClient : public sigslot::has_slots<> {
+ public:
+ // Gets ownership of the serializers, but not the client.
+ PubSubStateClient(const std::string& publisher_nick,
+ PubSubClient* client,
+ const QName& state_name,
+ C default_state,
+ PubSubStateKeySerializer* key_serializer,
+ PubSubStateSerializer<C>* state_serializer)
+ : publisher_nick_(publisher_nick),
+ client_(client),
+ state_name_(state_name),
+ default_state_(default_state) {
+ key_serializer_.reset(key_serializer);
+ state_serializer_.reset(state_serializer);
+ client_->SignalItems.connect(
+ this, &PubSubStateClient<C>::OnItems);
+ client_->SignalPublishResult.connect(
+ this, &PubSubStateClient<C>::OnPublishResult);
+ client_->SignalPublishError.connect(
+ this, &PubSubStateClient<C>::OnPublishError);
+ client_->SignalRetractResult.connect(
+ this, &PubSubStateClient<C>::OnRetractResult);
+ client_->SignalRetractError.connect(
+ this, &PubSubStateClient<C>::OnRetractError);
+ }
+
+ virtual ~PubSubStateClient() {}
+
+ virtual void Publish(const std::string& published_nick,
+ const C& state,
+ std::string* task_id_out) {
+ std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
+ std::string itemid = state_name_.LocalPart() + ":" + key;
+ if (StatesEqual(state, default_state_)) {
+ client_->RetractItem(itemid, task_id_out);
+ } else {
+ XmlElement* state_elem = state_serializer_->Write(state_name_, state);
+ state_elem->AddAttr(QN_NICK, published_nick);
+ client_->PublishItem(itemid, state_elem, task_id_out);
+ }
+ }
+
+ sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
+ // Signal (task_id, item). item is NULL for retract.
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishResult;
+ // Signal (task_id, item, error stanza). item is NULL for retract.
+ sigslot::signal3<const std::string&,
+ const XmlElement*,
+ const XmlElement*> SignalPublishError;
+
+ protected:
+ // return false if retracted item (no info or state given)
+ virtual bool ParseStateItem(const PubSubItem& item,
+ StateItemInfo* info_out,
+ C* state_out) {
+ const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
+ if (state_elem == NULL) {
+ return false;
+ }
+
+ info_out->publisher_nick =
+ client_->GetPublisherNickFromPubSubItem(item.elem);
+ info_out->published_nick = state_elem->Attr(QN_NICK);
+ state_serializer_->Parse(state_elem, state_out);
+ return true;
+ }
+
+ virtual bool StatesEqual(const C& state1, const C& state2) {
+ return state1 == state2;
+ }
+
+ PubSubClient* client() { return client_; }
+ const QName& state_name() { return state_name_; }
+
+ private:
+ void OnItems(PubSubClient* pub_sub_client,
+ const std::vector<PubSubItem>& items) {
+ for (std::vector<PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ OnItem(*item);
+ }
+ }
+
+ void OnItem(const PubSubItem& item) {
+ const std::string& itemid = item.itemid;
+ StateItemInfo info;
+ C new_state;
+
+ bool retracted = !ParseStateItem(item, &info, &new_state);
+ if (retracted) {
+ bool known_itemid =
+ (info_by_itemid_.find(itemid) != info_by_itemid_.end());
+ if (!known_itemid) {
+ // Nothing to retract, and nothing to publish.
+ // Probably a different state type.
+ return;
+ } else {
+ info = info_by_itemid_[itemid];
+ info_by_itemid_.erase(itemid);
+ new_state = default_state_;
+ }
+ } else {
+ // TODO: Assert new key matches the known key. It
+ // shouldn't change!
+ info_by_itemid_[itemid] = info;
+ }
+
+ std::string key = key_serializer_->GetKey(
+ info.publisher_nick, info.published_nick);
+ bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
+ C old_state = has_old_state ? state_by_key_[key] : default_state_;
+ if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
+ // Nothing change, so don't bother signalling.
+ return;
+ }
+
+ if (retracted || StatesEqual(new_state, default_state_)) {
+ // We treat a default state similar to a retract.
+ state_by_key_.erase(key);
+ } else {
+ state_by_key_[key] = new_state;
+ }
+
+ PubSubStateChange<C> change;
+ if (!retracted) {
+ // Retracts do not have publisher information.
+ change.publisher_nick = info.publisher_nick;
+ }
+ change.published_nick = info.published_nick;
+ change.old_state = old_state;
+ change.new_state = new_state;
+ SignalStateChange(change);
+ }
+
+ void OnPublishResult(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const XmlElement* item) {
+ SignalPublishResult(task_id, item);
+ }
+
+ void OnPublishError(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const buzz::XmlElement* item,
+ const buzz::XmlElement* stanza) {
+ SignalPublishError(task_id, item, stanza);
+ }
+
+ void OnRetractResult(PubSubClient* pub_sub_client,
+ const std::string& task_id) {
+ // There's no point in differentiating between publish and retract
+ // errors, so we simplify by making them both signal a publish
+ // result.
+ const XmlElement* item = NULL;
+ SignalPublishResult(task_id, item);
+ }
+
+ void OnRetractError(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ // There's no point in differentiating between publish and retract
+ // errors, so we simplify by making them both signal a publish
+ // error.
+ const XmlElement* item = NULL;
+ SignalPublishError(task_id, item, stanza);
+ }
+
+ std::string publisher_nick_;
+ PubSubClient* client_;
+ const QName state_name_;
+ C default_state_;
+ rtc::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
+ rtc::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
+ // key => state
+ std::map<std::string, C> state_by_key_;
+ // itemid => StateItemInfo
+ std::map<std::string, StateItemInfo> info_by_itemid_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(PubSubStateClient);
+};
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBSTATECLIENT_H_
diff --git a/webrtc/libjingle/xmpp/pubsubtasks.cc b/webrtc/libjingle/xmpp/pubsubtasks.cc
new file mode 100644
index 0000000000..d6532598b5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubtasks.cc
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/pubsubtasks.h"
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/receivetask.h"
+
+// An implementation of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+namespace {
+
+bool IsPubSubEventItemsElem(const XmlElement* stanza,
+ const std::string& expected_node) {
+ if (stanza->Name() != QN_MESSAGE) {
+ return false;
+ }
+
+ const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+ if (event_elem == NULL) {
+ return false;
+ }
+
+ const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+ if (items_elem == NULL) {
+ return false;
+ }
+
+ const std::string& actual_node = items_elem->Attr(QN_NODE);
+ return (actual_node == expected_node);
+}
+
+
+// Creates <pubsub node="node"><items></pubsub>
+XmlElement* CreatePubSubItemsElem(const std::string& node) {
+ XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false);
+ items_elem->AddAttr(QN_NODE, node);
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false);
+ pubsub_elem->AddElement(items_elem);
+ return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubPublishItemElem(
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children) {
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+ XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false);
+ publish_elem->AddAttr(QN_NODE, node);
+ XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+ item_elem->AddAttr(QN_ID, itemid);
+ for (std::vector<XmlElement*>::const_iterator child = children.begin();
+ child != children.end(); ++child) {
+ item_elem->AddElement(*child);
+ }
+ publish_elem->AddElement(item_elem);
+ pubsub_elem->AddElement(publish_elem);
+ return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubRetractItemElem(const std::string& node,
+ const std::string& itemid) {
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+ XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false);
+ retract_elem->AddAttr(QN_NODE, node);
+ retract_elem->AddAttr(QN_NOTIFY, "true");
+ XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+ item_elem->AddAttr(QN_ID, itemid);
+ retract_elem->AddElement(item_elem);
+ pubsub_elem->AddElement(retract_elem);
+ return pubsub_elem;
+}
+
+void ParseItem(const XmlElement* item_elem,
+ std::vector<PubSubItem>* items) {
+ PubSubItem item;
+ item.itemid = item_elem->Attr(QN_ID);
+ item.elem = item_elem;
+ items->push_back(item);
+}
+
+// Right now, <retract>s are treated the same as items with empty
+// payloads. We may want to change it in the future, but right now
+// it's sufficient for our needs.
+void ParseRetract(const XmlElement* retract_elem,
+ std::vector<PubSubItem>* items) {
+ ParseItem(retract_elem, items);
+}
+
+void ParseEventItemsElem(const XmlElement* stanza,
+ std::vector<PubSubItem>* items) {
+ const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+ if (event_elem != NULL) {
+ const XmlElement* items_elem =
+ event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+ if (items_elem != NULL) {
+ for (const XmlElement* item_elem =
+ items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM);
+ item_elem != NULL;
+ item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) {
+ ParseItem(item_elem, items);
+ }
+ for (const XmlElement* retract_elem =
+ items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT);
+ retract_elem != NULL;
+ retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) {
+ ParseRetract(retract_elem, items);
+ }
+ }
+ }
+}
+
+void ParsePubSubItemsElem(const XmlElement* stanza,
+ std::vector<PubSubItem>* items) {
+ const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB);
+ if (pubsub_elem != NULL) {
+ const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS);
+ if (items_elem != NULL) {
+ for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM);
+ item_elem != NULL;
+ item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) {
+ ParseItem(item_elem, items);
+ }
+ }
+ }
+}
+
+} // namespace
+
+PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) {
+}
+
+void PubSubRequestTask::HandleResult(const XmlElement* stanza) {
+ std::vector<PubSubItem> items;
+ ParsePubSubItemsElem(stanza, &items);
+ SignalResult(this, items);
+}
+
+int PubSubReceiveTask::ProcessStart() {
+ if (SignalUpdate.is_empty()) {
+ return STATE_DONE;
+ }
+ return ReceiveTask::ProcessStart();
+}
+
+bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) {
+ return MatchStanzaFrom(stanza, pubsubjid_) &&
+ IsPubSubEventItemsElem(stanza, node_) && !SignalUpdate.is_empty();
+}
+
+void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) {
+ std::vector<PubSubItem> items;
+ ParseEventItemsElem(stanza, &items);
+ SignalUpdate(this, items);
+}
+
+PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children)
+ : IqTask(parent, STR_SET, pubsubjid,
+ CreatePubSubPublishItemElem(node, itemid, children)),
+ itemid_(itemid) {
+}
+
+void PubSubPublishTask::HandleResult(const XmlElement* stanza) {
+ SignalResult(this);
+}
+
+PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid)
+ : IqTask(parent, STR_SET, pubsubjid,
+ CreatePubSubRetractItemElem(node, itemid)),
+ itemid_(itemid) {
+}
+
+void PubSubRetractTask::HandleResult(const XmlElement* stanza) {
+ SignalResult(this);
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/pubsubtasks.h b/webrtc/libjingle/xmpp/pubsubtasks.h
new file mode 100644
index 0000000000..2f56fa87cc
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubtasks.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_
+#define WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_
+
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/iqtask.h"
+#include "webrtc/libjingle/xmpp/receivetask.h"
+#include "webrtc/base/sigslot.h"
+
+namespace buzz {
+
+// A PubSub itemid + payload. Useful for signaling items.
+struct PubSubItem {
+ std::string itemid;
+ // The entire <item>, owned by the stanza handler. To keep a
+ // reference after handling, make a copy.
+ const XmlElement* elem;
+};
+
+// An IqTask which gets a <pubsub><items> for a particular jid and
+// node, parses the items in the response and signals the items.
+class PubSubRequestTask : public IqTask {
+ public:
+ PubSubRequestTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node);
+
+ sigslot::signal2<PubSubRequestTask*,
+ const std::vector<PubSubItem>&> SignalResult;
+ // SignalError inherited by IqTask.
+ private:
+ virtual void HandleResult(const XmlElement* stanza);
+};
+
+// A ReceiveTask which listens for <event><items> of a particular
+// pubsub JID and node and then signals them items.
+class PubSubReceiveTask : public ReceiveTask {
+ public:
+ PubSubReceiveTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : ReceiveTask(parent),
+ pubsubjid_(pubsubjid),
+ node_(node) {
+ }
+
+ virtual int ProcessStart();
+ sigslot::signal2<PubSubReceiveTask*,
+ const std::vector<PubSubItem>&> SignalUpdate;
+
+ protected:
+ virtual bool WantsStanza(const XmlElement* stanza);
+ virtual void ReceiveStanza(const XmlElement* stanza);
+
+ private:
+ Jid pubsubjid_;
+ std::string node_;
+};
+
+// An IqTask which publishes a <pubsub><publish><item> to a particular
+// pubsub jid and node.
+class PubSubPublishTask : public IqTask {
+ public:
+ // Takes ownership of children
+ PubSubPublishTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children);
+
+ const std::string& itemid() const { return itemid_; }
+
+ sigslot::signal1<PubSubPublishTask*> SignalResult;
+
+ private:
+ // SignalError inherited by IqTask.
+ virtual void HandleResult(const XmlElement* stanza);
+
+ std::string itemid_;
+};
+
+// An IqTask which publishes a <pubsub><publish><retract> to a particular
+// pubsub jid and node.
+class PubSubRetractTask : public IqTask {
+ public:
+ PubSubRetractTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid);
+
+ const std::string& itemid() const { return itemid_; }
+
+ sigslot::signal1<PubSubRetractTask*> SignalResult;
+
+ private:
+ // SignalError inherited by IqTask.
+ virtual void HandleResult(const XmlElement* stanza);
+
+ std::string itemid_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_PUBSUBTASKS_H_
diff --git a/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc
new file mode 100644
index 0000000000..8062e58e18
--- /dev/null
+++ b/webrtc/libjingle/xmpp/pubsubtasks_unittest.cc
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/fakexmppclient.h"
+#include "webrtc/libjingle/xmpp/iqtask.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/pubsubtasks.h"
+#include "webrtc/base/faketaskrunner.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/sigslot.h"
+
+struct HandledPubSubItem {
+ std::string itemid;
+ std::string payload;
+};
+
+class TestPubSubTasksListener : public sigslot::has_slots<> {
+ public:
+ TestPubSubTasksListener() : result_count(0), error_count(0) {}
+
+ void OnReceiveUpdate(buzz::PubSubReceiveTask* task,
+ const std::vector<buzz::PubSubItem>& items) {
+ OnItems(items);
+ }
+
+ void OnRequestResult(buzz::PubSubRequestTask* task,
+ const std::vector<buzz::PubSubItem>& items) {
+ OnItems(items);
+ }
+
+ void OnItems(const std::vector<buzz::PubSubItem>& items) {
+ for (std::vector<buzz::PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ HandledPubSubItem handled_item;
+ handled_item.itemid = item->itemid;
+ if (item->elem->FirstElement() != NULL) {
+ handled_item.payload = item->elem->FirstElement()->Str();
+ }
+ this->items.push_back(handled_item);
+ }
+ }
+
+ void OnPublishResult(buzz::PubSubPublishTask* task) {
+ ++result_count;
+ }
+
+ void OnRetractResult(buzz::PubSubRetractTask* task) {
+ ++result_count;
+ }
+
+ void OnError(buzz::IqTask* task, const buzz::XmlElement* stanza) {
+ ++error_count;
+ }
+
+ std::vector<HandledPubSubItem> items;
+ int result_count;
+ int error_count;
+};
+
+class PubSubTasksTest : public testing::Test {
+ public:
+ PubSubTasksTest() :
+ pubsubjid("room@domain.com"),
+ node("topic"),
+ itemid("key") {
+ runner.reset(new rtc::FakeTaskRunner());
+ client = new buzz::FakeXmppClient(runner.get());
+ listener.reset(new TestPubSubTasksListener());
+ }
+
+ rtc::scoped_ptr<rtc::FakeTaskRunner> runner;
+ // Client deleted by deleting runner.
+ buzz::FakeXmppClient* client;
+ rtc::scoped_ptr<TestPubSubTasksListener> listener;
+ buzz::Jid pubsubjid;
+ std::string node;
+ std::string itemid;
+};
+
+TEST_F(PubSubTasksTest, TestRequest) {
+ buzz::PubSubRequestTask* task =
+ new buzz::PubSubRequestTask(client, pubsubjid, node);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRequestResult);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"get\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pub:pubsub xmlns:pub=\"http://jabber.org/protocol/pubsub\">"
+ "<pub:items node=\"topic\"/>"
+ "</pub:pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'>"
+ " <pubsub xmlns='http://jabber.org/protocol/pubsub'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1/>"
+ " </item>"
+ " </items>"
+ " </pubsub>"
+ "</iq>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ("<pub:value0 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ("<pub:value1 xmlns:pub=\"http://jabber.org/protocol/pubsub\"/>",
+ listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestRequestError) {
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ buzz::PubSubRequestTask* task =
+ new buzz::PubSubRequestTask(client, pubsubjid, node);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRequestResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestReceive) {
+ std::string items_message =
+ "<message xmlns='jabber:client' from='room@domain.com'>"
+ " <event xmlns='http://jabber.org/protocol/pubsub#event'>"
+ " <items node='topic'>"
+ " <item id='key0'>"
+ " <value0/>"
+ " </item>"
+ " <item id='key1'>"
+ " <value1/>"
+ " </item>"
+ " </items>"
+ " </event>"
+ "</message>";
+
+ buzz::PubSubReceiveTask* task =
+ new buzz::PubSubReceiveTask(client, pubsubjid, node);
+ task->SignalUpdate.connect(
+ listener.get(), &TestPubSubTasksListener::OnReceiveUpdate);
+ task->Start();
+ client->HandleStanza(buzz::XmlElement::ForStr(items_message));
+
+ ASSERT_EQ(2U, listener->items.size());
+ EXPECT_EQ("key0", listener->items[0].itemid);
+ EXPECT_EQ(
+ "<eve:value0 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[0].payload);
+ EXPECT_EQ("key1", listener->items[1].itemid);
+ EXPECT_EQ(
+ "<eve:value1 xmlns:eve=\"http://jabber.org/protocol/pubsub#event\"/>",
+ listener->items[1].payload);
+}
+
+TEST_F(PubSubTasksTest, TestPublish) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<publish node=\"topic\">"
+ "<item id=\"key\">"
+ "<value/>"
+ "</item>"
+ "</publish>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ std::vector<buzz::XmlElement*> children;
+ children.push_back(payload);
+ buzz::PubSubPublishTask* task =
+ new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnPublishResult);
+ task->Start();
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestPublishError) {
+ buzz::XmlElement* payload =
+ new buzz::XmlElement(buzz::QName(buzz::NS_PUBSUB, "value"));
+
+ std::vector<buzz::XmlElement*> children;
+ children.push_back(payload);
+ buzz::PubSubPublishTask* task =
+ new buzz::PubSubPublishTask(client, pubsubjid, node, itemid, children);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnPublishResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='error' from='room@domain.com'>"
+ " <error type='auth'>"
+ " <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>"
+ " </error>"
+ "</iq>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(0, listener->result_count);
+ EXPECT_EQ(1, listener->error_count);
+}
+
+TEST_F(PubSubTasksTest, TestRetract) {
+ buzz::PubSubRetractTask* task =
+ new buzz::PubSubRetractTask(client, pubsubjid, node, itemid);
+ task->SignalResult.connect(
+ listener.get(), &TestPubSubTasksListener::OnRetractResult);
+ task->SignalError.connect(
+ listener.get(), &TestPubSubTasksListener::OnError);
+ task->Start();
+
+ std::string expected_iq =
+ "<cli:iq type=\"set\" to=\"room@domain.com\" id=\"0\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<pubsub xmlns=\"http://jabber.org/protocol/pubsub\">"
+ "<retract node=\"topic\" notify=\"true\">"
+ "<item id=\"key\"/>"
+ "</retract>"
+ "</pubsub>"
+ "</cli:iq>";
+
+ ASSERT_EQ(1U, client->sent_stanzas().size());
+ EXPECT_EQ(expected_iq, client->sent_stanzas()[0]->Str());
+
+ std::string result_iq =
+ "<iq xmlns='jabber:client' id='0' type='result' from='room@domain.com'/>";
+
+ client->HandleStanza(buzz::XmlElement::ForStr(result_iq));
+
+ EXPECT_EQ(1, listener->result_count);
+ EXPECT_EQ(0, listener->error_count);
+}
diff --git a/webrtc/libjingle/xmpp/receivetask.cc b/webrtc/libjingle/xmpp/receivetask.cc
new file mode 100644
index 0000000000..9d4687cf54
--- /dev/null
+++ b/webrtc/libjingle/xmpp/receivetask.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/receivetask.h"
+
+namespace buzz {
+
+bool ReceiveTask::HandleStanza(const XmlElement* stanza) {
+ if (WantsStanza(stanza)) {
+ QueueStanza(stanza);
+ return true;
+ }
+
+ return false;
+}
+
+int ReceiveTask::ProcessStart() {
+ const XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ ReceiveStanza(stanza);
+ return STATE_START;
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/receivetask.h b/webrtc/libjingle/xmpp/receivetask.h
new file mode 100644
index 0000000000..b776746be9
--- /dev/null
+++ b/webrtc/libjingle/xmpp/receivetask.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_
+
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A base class for receiving stanzas. Override WantsStanza to
+// indicate that a stanza should be received and ReceiveStanza to
+// process it. Once started, ReceiveStanza will be called for all
+// stanzas that return true when passed to WantsStanza. This saves
+// you from having to remember how to setup the queueing and the task
+// states, etc.
+class ReceiveTask : public XmppTask {
+ public:
+ explicit ReceiveTask(XmppTaskParentInterface* parent) :
+ XmppTask(parent, XmppEngine::HL_TYPE) {}
+ virtual int ProcessStart();
+
+ protected:
+ virtual bool HandleStanza(const XmlElement* stanza);
+
+ // Return true if the stanza should be received.
+ virtual bool WantsStanza(const XmlElement* stanza) = 0;
+ // Process the received stanza.
+ virtual void ReceiveStanza(const XmlElement* stanza) = 0;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_RECEIVETASK_H_
diff --git a/webrtc/libjingle/xmpp/rostermodule.h b/webrtc/libjingle/xmpp/rostermodule.h
new file mode 100644
index 0000000000..85d5d34407
--- /dev/null
+++ b/webrtc/libjingle/xmpp/rostermodule.h
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_
+#define WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_
+
+#include "webrtc/libjingle/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,
+};
+
+enum XmppPresenceConnectionStatus {
+ XMPP_CONNECTION_STATUS_UNKNOWN = 0,
+ // Status set by the server while the user is being rung.
+ XMPP_CONNECTION_STATUS_CONNECTING = 1,
+ // Status set by the client when the user has accepted the ring but before
+ // the client has joined the call.
+ XMPP_CONNECTION_STATUS_JOINING = 2,
+ // Status set by the client as part of joining the call.
+ XMPP_CONNECTION_STATUS_CONNECTED = 3,
+ XMPP_CONNECTION_STATUS_HANGUP = 4,
+};
+
+//! 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 connection status
+ virtual XmppPresenceConnectionStatus connection_status() const = 0;
+
+ //! The focus obfuscated GAIA id
+ virtual const std::string google_user_id() const = 0;
+
+ //! The nickname in the presence
+ virtual const std::string nickname() const = 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:
+ virtual ~XmppRosterHandler() {}
+
+ //! 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 // WEBRTC_LIBJINGLE_XMPP_ROSTERMODULE_H_
diff --git a/webrtc/libjingle/xmpp/rostermodule_unittest.cc b/webrtc/libjingle/xmpp/rostermodule_unittest.cc
new file mode 100644
index 0000000000..1ae6c226a5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/rostermodule_unittest.cc
@@ -0,0 +1,832 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/rostermodule.h"
+#include "webrtc/libjingle/xmpp/util_unittest.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/gunit.h"
+#include "webrtc/base/scoped_ptr.h"
+
+#define TEST_OK(x) EXPECT_EQ((x),XMPP_RETURN_OK)
+#define TEST_BADARGUMENT(x) EXPECT_EQ((x),XMPP_RETURN_BADARGUMENT)
+
+
+namespace buzz {
+
+class RosterModuleTest;
+
+static void
+WriteString(std::ostream& os, const std::string& str) {
+ os<<str;
+}
+
+static void
+WriteSubscriptionState(std::ostream& os, XmppSubscriptionState state)
+{
+ switch (state) {
+ case XMPP_SUBSCRIPTION_NONE:
+ os<<"none";
+ break;
+ case XMPP_SUBSCRIPTION_NONE_ASKED:
+ os<<"none_asked";
+ break;
+ case XMPP_SUBSCRIPTION_TO:
+ os<<"to";
+ break;
+ case XMPP_SUBSCRIPTION_FROM:
+ os<<"from";
+ break;
+ case XMPP_SUBSCRIPTION_FROM_ASKED:
+ os<<"from_asked";
+ break;
+ case XMPP_SUBSCRIPTION_BOTH:
+ os<<"both";
+ break;
+ default:
+ os<<"unknown";
+ break;
+ }
+}
+
+static void
+WriteSubscriptionRequestType(std::ostream& os,
+ XmppSubscriptionRequestType type) {
+ switch(type) {
+ case XMPP_REQUEST_SUBSCRIBE:
+ os<<"subscribe";
+ break;
+ case XMPP_REQUEST_UNSUBSCRIBE:
+ os<<"unsubscribe";
+ break;
+ case XMPP_REQUEST_SUBSCRIBED:
+ os<<"subscribed";
+ break;
+ case XMPP_REQUEST_UNSUBSCRIBED:
+ os<<"unsubscribe";
+ break;
+ default:
+ os<<"unknown";
+ break;
+ }
+}
+
+static void
+WritePresenceShow(std::ostream& os, XmppPresenceShow show) {
+ switch(show) {
+ case XMPP_PRESENCE_AWAY:
+ os<<"away";
+ break;
+ case XMPP_PRESENCE_CHAT:
+ os<<"chat";
+ break;
+ case XMPP_PRESENCE_DND:
+ os<<"dnd";
+ break;
+ case XMPP_PRESENCE_XA:
+ os<<"xa";
+ break;
+ case XMPP_PRESENCE_DEFAULT:
+ os<<"[default]";
+ break;
+ default:
+ os<<"[unknown]";
+ break;
+ }
+}
+
+static void
+WritePresence(std::ostream& os, const XmppPresence* presence) {
+ if (presence == NULL) {
+ os<<"NULL";
+ return;
+ }
+
+ os<<"[Presence jid:";
+ WriteString(os, presence->jid().Str());
+ os<<" available:"<<presence->available();
+ os<<" presence_show:";
+ WritePresenceShow(os, presence->presence_show());
+ os<<" priority:"<<presence->priority();
+ os<<" status:";
+ WriteString(os, presence->status());
+ os<<"]"<<presence->raw_xml()->Str();
+}
+
+static void
+WriteContact(std::ostream& os, const XmppRosterContact* contact) {
+ if (contact == NULL) {
+ os<<"NULL";
+ return;
+ }
+
+ os<<"[Contact jid:";
+ WriteString(os, contact->jid().Str());
+ os<<" name:";
+ WriteString(os, contact->name());
+ os<<" subscription_state:";
+ WriteSubscriptionState(os, contact->subscription_state());
+ os<<" groups:[";
+ for(size_t i=0; i < contact->GetGroupCount(); ++i) {
+ os<<(i==0?"":", ");
+ WriteString(os, contact->GetGroup(i));
+ }
+ os<<"]]"<<contact->raw_xml()->Str();
+}
+
+//! This session handler saves all calls to a string. These are events and
+//! data delivered form the engine to application code.
+class XmppTestRosterHandler : public XmppRosterHandler {
+public:
+ XmppTestRosterHandler() {}
+ virtual ~XmppTestRosterHandler() {}
+
+ virtual void SubscriptionRequest(XmppRosterModule*,
+ const Jid& requesting_jid,
+ XmppSubscriptionRequestType type,
+ const XmlElement* raw_xml) {
+ ss_<<"[SubscriptionRequest Jid:" << requesting_jid.Str()<<" type:";
+ WriteSubscriptionRequestType(ss_, type);
+ ss_<<"]"<<raw_xml->Str();
+ }
+
+ //! Some type of presence error has occured
+ virtual void SubscriptionError(XmppRosterModule*,
+ const Jid& from,
+ const XmlElement* raw_xml) {
+ ss_<<"[SubscriptionError from:"<<from.Str()<<"]"<<raw_xml->Str();
+ }
+
+ virtual void RosterError(XmppRosterModule*,
+ const XmlElement* raw_xml) {
+ ss_<<"[RosterError]"<<raw_xml->Str();
+ }
+
+ //! 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*,
+ const XmppPresence* presence) {
+ ss_<<"[IncomingPresenceChanged presence:";
+ WritePresence(ss_, presence);
+ ss_<<"]";
+ }
+
+ //! 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) {
+ ss_<<"[ContactChanged old_contact:";
+ WriteContact(ss_, old_contact);
+ ss_<<" index:"<<index<<" new_contact:";
+ WriteContact(ss_, roster->GetRosterContact(index));
+ ss_<<"]";
+ }
+
+ //! 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) {
+ ss_<<"[ContactsAdded index:"<<index<<" number:"<<number;
+ for (size_t i = 0; i < number; ++i) {
+ ss_<<" "<<(index+i)<<":";
+ WriteContact(ss_, roster->GetRosterContact(index+i));
+ }
+ ss_<<"]";
+ }
+
+ //! A contact has been removed
+ //! This contact has been removed form the list.
+ virtual void ContactRemoved(XmppRosterModule*,
+ const XmppRosterContact* removed_contact,
+ size_t index) {
+ ss_<<"[ContactRemoved old_contact:";
+ WriteContact(ss_, removed_contact);
+ ss_<<" index:"<<index<<"]";
+ }
+
+ 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 RosterModuleTest : public testing::Test {
+public:
+ RosterModuleTest() {}
+ static void RunLogin(RosterModuleTest* obj, XmppEngine* engine,
+ XmppTestHandler* handler) {
+ // Needs to be similar to XmppEngineTest::RunLogin
+ }
+};
+
+TEST_F(RosterModuleTest, TestPresence) {
+ XmlElement* status = new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+ status->AddAttr(QN_STATUS, STR_PSTN_CONFERENCE_STATUS_CONNECTING);
+ XmlElement presence_xml(QN_PRESENCE);
+ presence_xml.AddElement(status);
+ rtc::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
+ presence->set_raw_xml(&presence_xml);
+ EXPECT_EQ(presence->connection_status(), XMPP_CONNECTION_STATUS_CONNECTING);
+}
+
+TEST_F(RosterModuleTest, TestOutgoingPresence) {
+ std::stringstream dump;
+
+ rtc::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+ XmppTestRosterHandler roster_handler;
+
+ rtc::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+ roster->set_roster_handler(&roster_handler);
+
+ // Configure the roster module
+ roster->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);
+ EXPECT_EQ("", handler.OutputActivity());
+
+ // Set some presence and broadcast it
+ TEST_OK(roster->outgoing_presence()->
+ set_available(XMPP_PRESENCE_AVAILABLE));
+ TEST_OK(roster->outgoing_presence()->set_priority(-37));
+ TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_DND));
+ TEST_OK(roster->outgoing_presence()->
+ set_status("I'm off to the races!<>&"));
+ TEST_OK(roster->BroadcastPresence());
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence>"
+ "<priority>-37</priority>"
+ "<show>dnd</show>"
+ "<status>I'm off to the races!&lt;&gt;&amp;</status>"
+ "</presence>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Try some more
+ TEST_OK(roster->outgoing_presence()->
+ set_available(XMPP_PRESENCE_UNAVAILABLE));
+ TEST_OK(roster->outgoing_presence()->set_priority(0));
+ TEST_OK(roster->outgoing_presence()->set_presence_show(XMPP_PRESENCE_XA));
+ TEST_OK(roster->outgoing_presence()->set_status("Gone fishin'"));
+ TEST_OK(roster->BroadcastPresence());
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence type=\"unavailable\">"
+ "<show>xa</show>"
+ "<status>Gone fishin'</status>"
+ "</presence>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Okay -- we are back on
+ TEST_OK(roster->outgoing_presence()->
+ set_available(XMPP_PRESENCE_AVAILABLE));
+ TEST_BADARGUMENT(roster->outgoing_presence()->set_priority(128));
+ TEST_OK(roster->outgoing_presence()->
+ set_presence_show(XMPP_PRESENCE_DEFAULT));
+ TEST_OK(roster->outgoing_presence()->set_status("Cookin' wit gas"));
+ TEST_OK(roster->BroadcastPresence());
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence>"
+ "<status>Cookin' wit gas</status>"
+ "</presence>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Set it via XML
+ XmlElement presence_input(QN_PRESENCE);
+ presence_input.AddAttr(QN_TYPE, "unavailable");
+ presence_input.AddElement(new XmlElement(QN_PRIORITY));
+ presence_input.AddText("42", 1);
+ presence_input.AddElement(new XmlElement(QN_STATUS));
+ presence_input.AddAttr(QN_XML_LANG, "es", 1);
+ presence_input.AddText("Hola Amigos!", 1);
+ presence_input.AddElement(new XmlElement(QN_STATUS));
+ presence_input.AddText("Hey there, friend!", 1);
+ TEST_OK(roster->outgoing_presence()->set_raw_xml(&presence_input));
+ TEST_OK(roster->BroadcastPresence());
+
+ WritePresence(dump, roster->outgoing_presence());
+ EXPECT_EQ(dump.str(),
+ "[Presence jid: available:0 presence_show:[default] "
+ "priority:42 status:Hey there, friend!]"
+ "<cli:presence type=\"unavailable\" xmlns:cli=\"jabber:client\">"
+ "<cli:priority>42</cli:priority>"
+ "<cli:status xml:lang=\"es\">Hola Amigos!</cli:status>"
+ "<cli:status>Hey there, friend!</cli:status>"
+ "</cli:presence>");
+ dump.str("");
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence type=\"unavailable\">"
+ "<priority>42</priority>"
+ "<status xml:lang=\"es\">Hola Amigos!</status>"
+ "<status>Hey there, friend!</status>"
+ "</presence>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Construct a directed presence
+ rtc::scoped_ptr<XmppPresence> directed_presence(XmppPresence::Create());
+ TEST_OK(directed_presence->set_available(XMPP_PRESENCE_AVAILABLE));
+ TEST_OK(directed_presence->set_priority(120));
+ TEST_OK(directed_presence->set_status("*very* available"));
+ TEST_OK(roster->SendDirectedPresence(directed_presence.get(),
+ Jid("myhoney@honey.net")));
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence to=\"myhoney@honey.net\">"
+ "<priority>120</priority>"
+ "<status>*very* available</status>"
+ "</presence>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+TEST_F(RosterModuleTest, TestIncomingPresence) {
+ rtc::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+ XmppTestRosterHandler roster_handler;
+
+ rtc::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+ roster->set_roster_handler(&roster_handler);
+
+ // Configure the roster module
+ roster->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);
+ EXPECT_EQ("", handler.OutputActivity());
+
+ // Load up with a bunch of data
+ std::string input;
+ input = "<presence from='maude@example.net/studio' "
+ "to='david@my-server/test'/>"
+ "<presence from='walter@example.net/home' "
+ "to='david@my-server/test'>"
+ "<priority>-10</priority>"
+ "<show>xa</show>"
+ "<status>Off bowling</status>"
+ "</presence>"
+ "<presence from='walter@example.net/alley' "
+ "to='david@my-server/test'>"
+ "<priority>20</priority>"
+ "<status>Looking for toes...</status>"
+ "</presence>"
+ "<presence from='donny@example.net/alley' "
+ "to='david@my-server/test'>"
+ "<priority>10</priority>"
+ "<status>Throwing rocks</status>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[IncomingPresenceChanged "
+ "presence:[Presence jid:maude@example.net/studio available:1 "
+ "presence_show:[default] priority:0 status:]"
+ "<cli:presence from=\"maude@example.net/studio\" "
+ "to=\"david@my-server/test\" "
+ "xmlns:cli=\"jabber:client\"/>]"
+ "[IncomingPresenceChanged "
+ "presence:[Presence jid:walter@example.net/home available:1 "
+ "presence_show:xa priority:-10 status:Off bowling]"
+ "<cli:presence from=\"walter@example.net/home\" "
+ "to=\"david@my-server/test\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<cli:priority>-10</cli:priority>"
+ "<cli:show>xa</cli:show>"
+ "<cli:status>Off bowling</cli:status>"
+ "</cli:presence>]"
+ "[IncomingPresenceChanged "
+ "presence:[Presence jid:walter@example.net/alley available:1 "
+ "presence_show:[default] "
+ "priority:20 status:Looking for toes...]"
+ "<cli:presence from=\"walter@example.net/alley\" "
+ "to=\"david@my-server/test\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<cli:priority>20</cli:priority>"
+ "<cli:status>Looking for toes...</cli:status>"
+ "</cli:presence>]"
+ "[IncomingPresenceChanged "
+ "presence:[Presence jid:donny@example.net/alley available:1 "
+ "presence_show:[default] priority:10 status:Throwing rocks]"
+ "<cli:presence from=\"donny@example.net/alley\" "
+ "to=\"david@my-server/test\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<cli:priority>10</cli:priority>"
+ "<cli:status>Throwing rocks</cli:status>"
+ "</cli:presence>]");
+ EXPECT_EQ(handler.OutputActivity(), "");
+ handler.SessionActivity(); // Ignore the session output
+
+ // Now look at the data structure we've built
+ EXPECT_EQ(roster->GetIncomingPresenceCount(), static_cast<size_t>(4));
+ EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("maude@example.net")),
+ static_cast<size_t>(1));
+ EXPECT_EQ(roster->GetIncomingPresenceForJidCount(Jid("walter@example.net")),
+ static_cast<size_t>(2));
+
+ const XmppPresence * presence;
+ presence = roster->GetIncomingPresenceForJid(Jid("walter@example.net"), 1);
+
+ std::stringstream dump;
+ WritePresence(dump, presence);
+ EXPECT_EQ(dump.str(),
+ "[Presence jid:walter@example.net/alley available:1 "
+ "presence_show:[default] priority:20 status:Looking for toes...]"
+ "<cli:presence from=\"walter@example.net/alley\" "
+ "to=\"david@my-server/test\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<cli:priority>20</cli:priority>"
+ "<cli:status>Looking for toes...</cli:status>"
+ "</cli:presence>");
+ dump.str("");
+
+ // Maude took off...
+ input = "<presence from='maude@example.net/studio' "
+ "to='david@my-server/test' "
+ "type='unavailable'>"
+ "<status>Stealing my rug back</status>"
+ "<priority>-10</priority>"
+ "</presence>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[IncomingPresenceChanged "
+ "presence:[Presence jid:maude@example.net/studio available:0 "
+ "presence_show:[default] priority:-10 "
+ "status:Stealing my rug back]"
+ "<cli:presence from=\"maude@example.net/studio\" "
+ "to=\"david@my-server/test\" type=\"unavailable\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<cli:status>Stealing my rug back</cli:status>"
+ "<cli:priority>-10</cli:priority>"
+ "</cli:presence>]");
+ EXPECT_EQ(handler.OutputActivity(), "");
+ handler.SessionActivity(); // Ignore the session output
+}
+
+TEST_F(RosterModuleTest, TestPresenceSubscription) {
+ rtc::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+ XmppTestRosterHandler roster_handler;
+
+ rtc::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+ roster->set_roster_handler(&roster_handler);
+
+ // Configure the roster module
+ roster->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);
+ EXPECT_EQ("", handler.OutputActivity());
+
+ // Test incoming requests
+ std::string input;
+ input =
+ "<presence from='maude@example.net' type='subscribe'/>"
+ "<presence from='maude@example.net' type='unsubscribe'/>"
+ "<presence from='maude@example.net' type='subscribed'/>"
+ "<presence from='maude@example.net' type='unsubscribed'/>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[SubscriptionRequest Jid:maude@example.net type:subscribe]"
+ "<cli:presence from=\"maude@example.net\" type=\"subscribe\" "
+ "xmlns:cli=\"jabber:client\"/>"
+ "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]"
+ "<cli:presence from=\"maude@example.net\" type=\"unsubscribe\" "
+ "xmlns:cli=\"jabber:client\"/>"
+ "[SubscriptionRequest Jid:maude@example.net type:subscribed]"
+ "<cli:presence from=\"maude@example.net\" type=\"subscribed\" "
+ "xmlns:cli=\"jabber:client\"/>"
+ "[SubscriptionRequest Jid:maude@example.net type:unsubscribe]"
+ "<cli:presence from=\"maude@example.net\" type=\"unsubscribed\" "
+ "xmlns:cli=\"jabber:client\"/>");
+ EXPECT_EQ(handler.OutputActivity(), "");
+ handler.SessionActivity(); // Ignore the session output
+
+ TEST_OK(roster->RequestSubscription(Jid("maude@example.net")));
+ TEST_OK(roster->CancelSubscription(Jid("maude@example.net")));
+ TEST_OK(roster->ApproveSubscriber(Jid("maude@example.net")));
+ TEST_OK(roster->CancelSubscriber(Jid("maude@example.net")));
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<presence to=\"maude@example.net\" type=\"subscribe\"/>"
+ "<presence to=\"maude@example.net\" type=\"unsubscribe\"/>"
+ "<presence to=\"maude@example.net\" type=\"subscribed\"/>"
+ "<presence to=\"maude@example.net\" type=\"unsubscribed\"/>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+TEST_F(RosterModuleTest, TestRosterReceive) {
+ rtc::scoped_ptr<XmppEngine> engine(XmppEngine::Create());
+ XmppTestHandler handler(engine.get());
+ XmppTestRosterHandler roster_handler;
+
+ rtc::scoped_ptr<XmppRosterModule> roster(XmppRosterModule::Create());
+ roster->set_roster_handler(&roster_handler);
+
+ // Configure the roster module
+ roster->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);
+ EXPECT_EQ("", handler.OutputActivity());
+
+ // Request a roster update
+ TEST_OK(roster->RequestRosterUpdate());
+
+ EXPECT_EQ(roster_handler.StrClear(),"");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"get\" id=\"2\">"
+ "<query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Prime the roster with a starting set
+ std::string input =
+ "<iq to='david@myserver/test' type='result' id='2'>"
+ "<query xmlns='jabber:iq:roster'>"
+ "<item jid='maude@example.net' "
+ "name='Maude Lebowski' "
+ "subscription='none' "
+ "ask='subscribe'>"
+ "<group>Business Partners</group>"
+ "</item>"
+ "<item jid='walter@example.net' "
+ "name='Walter Sobchak' "
+ "subscription='both'>"
+ "<group>Friends</group>"
+ "<group>Bowling Team</group>"
+ "<group>Bowling League</group>"
+ "</item>"
+ "<item jid='donny@example.net' "
+ "name='Donny' "
+ "subscription='both'>"
+ "<group>Friends</group>"
+ "<group>Bowling Team</group>"
+ "<group>Bowling League</group>"
+ "</item>"
+ "<item jid='jeffrey@example.net' "
+ "name='The Big Lebowski' "
+ "subscription='to'>"
+ "<group>Business Partners</group>"
+ "</item>"
+ "<item jid='jesus@example.net' "
+ "name='Jesus Quintana' "
+ "subscription='from'>"
+ "<group>Bowling League</group>"
+ "</item>"
+ "</query>"
+ "</iq>";
+
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[ContactsAdded index:0 number:5 "
+ "0:[Contact jid:maude@example.net name:Maude Lebowski "
+ "subscription_state:none_asked "
+ "groups:[Business Partners]]"
+ "<ros:item jid=\"maude@example.net\" name=\"Maude Lebowski\" "
+ "subscription=\"none\" ask=\"subscribe\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Business Partners</ros:group>"
+ "</ros:item> "
+ "1:[Contact jid:walter@example.net name:Walter Sobchak "
+ "subscription_state:both "
+ "groups:[Friends, Bowling Team, Bowling League]]"
+ "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+ "subscription=\"both\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Friends</ros:group>"
+ "<ros:group>Bowling Team</ros:group>"
+ "<ros:group>Bowling League</ros:group>"
+ "</ros:item> "
+ "2:[Contact jid:donny@example.net name:Donny "
+ "subscription_state:both "
+ "groups:[Friends, Bowling Team, Bowling League]]"
+ "<ros:item jid=\"donny@example.net\" name=\"Donny\" "
+ "subscription=\"both\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Friends</ros:group>"
+ "<ros:group>Bowling Team</ros:group>"
+ "<ros:group>Bowling League</ros:group>"
+ "</ros:item> "
+ "3:[Contact jid:jeffrey@example.net name:The Big Lebowski "
+ "subscription_state:to "
+ "groups:[Business Partners]]"
+ "<ros:item jid=\"jeffrey@example.net\" name=\"The Big Lebowski\" "
+ "subscription=\"to\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Business Partners</ros:group>"
+ "</ros:item> "
+ "4:[Contact jid:jesus@example.net name:Jesus Quintana "
+ "subscription_state:from groups:[Bowling League]]"
+ "<ros:item jid=\"jesus@example.net\" name=\"Jesus Quintana\" "
+ "subscription=\"from\" xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Bowling League</ros:group>"
+ "</ros:item>]");
+ EXPECT_EQ(handler.OutputActivity(), "");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Request that someone be added
+ rtc::scoped_ptr<XmppRosterContact> contact(XmppRosterContact::Create());
+ TEST_OK(contact->set_jid(Jid("brandt@example.net")));
+ TEST_OK(contact->set_name("Brandt"));
+ TEST_OK(contact->AddGroup("Business Partners"));
+ TEST_OK(contact->AddGroup("Watchers"));
+ TEST_OK(contact->AddGroup("Friends"));
+ TEST_OK(contact->RemoveGroup("Friends")); // Maybe not...
+ TEST_OK(roster->RequestRosterChange(contact.get()));
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"set\" id=\"3\">"
+ "<query xmlns=\"jabber:iq:roster\">"
+ "<item jid=\"brandt@example.net\" "
+ "name=\"Brandt\">"
+ "<group>Business Partners</group>"
+ "<group>Watchers</group>"
+ "</item>"
+ "</query>"
+ "</iq>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Get the push from the server
+ input =
+ "<iq type='result' to='david@my-server/test' id='3'/>"
+ "<iq type='set' id='server_1'>"
+ "<query xmlns='jabber:iq:roster'>"
+ "<item jid='brandt@example.net' "
+ "name='Brandt' "
+ "subscription='none'>"
+ "<group>Business Partners</group>"
+ "<group>Watchers</group>"
+ "</item>"
+ "</query>"
+ "</iq>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[ContactsAdded index:5 number:1 "
+ "5:[Contact jid:brandt@example.net name:Brandt "
+ "subscription_state:none "
+ "groups:[Business Partners, Watchers]]"
+ "<ros:item jid=\"brandt@example.net\" name=\"Brandt\" "
+ "subscription=\"none\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Business Partners</ros:group>"
+ "<ros:group>Watchers</ros:group>"
+ "</ros:item>]");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"result\" id=\"server_1\"/>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Get a contact update
+ input =
+ "<iq type='set' id='server_2'>"
+ "<query xmlns='jabber:iq:roster'>"
+ "<item jid='walter@example.net' "
+ "name='Walter Sobchak' "
+ "subscription='both'>"
+ "<group>Friends</group>"
+ "<group>Bowling Team</group>"
+ "<group>Bowling League</group>"
+ "<group>Not wrong, just an...</group>"
+ "</item>"
+ "</query>"
+ "</iq>";
+
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[ContactChanged "
+ "old_contact:[Contact jid:walter@example.net name:Walter Sobchak "
+ "subscription_state:both "
+ "groups:[Friends, Bowling Team, Bowling League]]"
+ "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+ "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Friends</ros:group>"
+ "<ros:group>Bowling Team</ros:group>"
+ "<ros:group>Bowling League</ros:group>"
+ "</ros:item> "
+ "index:1 "
+ "new_contact:[Contact jid:walter@example.net name:Walter Sobchak "
+ "subscription_state:both "
+ "groups:[Friends, Bowling Team, Bowling League, "
+ "Not wrong, just an...]]"
+ "<ros:item jid=\"walter@example.net\" name=\"Walter Sobchak\" "
+ "subscription=\"both\" xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Friends</ros:group>"
+ "<ros:group>Bowling Team</ros:group>"
+ "<ros:group>Bowling League</ros:group>"
+ "<ros:group>Not wrong, just an...</ros:group>"
+ "</ros:item>]");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"result\" id=\"server_2\"/>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Remove a contact
+ TEST_OK(roster->RequestRosterRemove(Jid("jesus@example.net")));
+
+ EXPECT_EQ(roster_handler.StrClear(), "");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"set\" id=\"4\">"
+ "<query xmlns=\"jabber:iq:roster\" jid=\"jesus@example.net\" "
+ "subscription=\"remove\"/>"
+ "</iq>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+
+ // Response from the server
+ input =
+ "<iq type='result' to='david@my-server/test' id='4'/>"
+ "<iq type='set' id='server_3'>"
+ "<query xmlns='jabber:iq:roster'>"
+ "<item jid='jesus@example.net' "
+ "subscription='remove'>"
+ "</item>"
+ "</query>"
+ "</iq>";
+ TEST_OK(engine->HandleInput(input.c_str(), input.length()));
+
+ EXPECT_EQ(roster_handler.StrClear(),
+ "[ContactRemoved "
+ "old_contact:[Contact jid:jesus@example.net name:Jesus Quintana "
+ "subscription_state:from groups:[Bowling League]]"
+ "<ros:item jid=\"jesus@example.net\" name=\"Jesus Quintana\" "
+ "subscription=\"from\" "
+ "xmlns:ros=\"jabber:iq:roster\">"
+ "<ros:group>Bowling League</ros:group>"
+ "</ros:item> index:4]");
+ EXPECT_EQ(handler.OutputActivity(),
+ "<iq type=\"result\" id=\"server_3\"/>");
+ EXPECT_EQ(handler.SessionActivity(), "");
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/rostermoduleimpl.cc b/webrtc/libjingle/xmpp/rostermoduleimpl.cc
new file mode 100644
index 0000000000..b9752896ef
--- /dev/null
+++ b/webrtc/libjingle/xmpp/rostermoduleimpl.cc
@@ -0,0 +1,1064 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/rostermoduleimpl.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/stringencode.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 char** 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_)
+ return Jid();
+
+ return Jid(raw_xml_->Attr(QN_FROM));
+}
+
+XmppPresenceAvailable
+XmppPresenceImpl::available() const {
+ if (!raw_xml_)
+ 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_)
+ 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_)
+ return XMPP_PRESENCE_DEFAULT;
+
+ XmppPresenceShow show = XMPP_PRESENCE_DEFAULT;
+ StringToPresenceShow(raw_xml_->TextNamed(QN_SHOW), &show);
+ return show;
+}
+
+XmppReturnStatus
+XmppPresenceImpl::set_presence_show(XmppPresenceShow show) {
+ if (!raw_xml_)
+ CreateRawXmlSkeleton();
+
+ const char* 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_)
+ return 0;
+
+ int raw_priority = 0;
+ if (!rtc::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_)
+ CreateRawXmlSkeleton();
+
+ if (priority < -128 || priority > 127)
+ return XMPP_RETURN_BADARGUMENT;
+
+ raw_xml_->ClearNamedChildren(QN_PRIORITY);
+ if (0 != priority) {
+ std::string priority_string;
+ if (rtc::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_)
+ 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_)
+ 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;
+}
+
+XmppPresenceConnectionStatus
+XmppPresenceImpl::connection_status() const {
+ if (!raw_xml_)
+ return XMPP_CONNECTION_STATUS_UNKNOWN;
+
+ XmlElement* con = raw_xml_->FirstNamed(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
+ if (con) {
+ std::string status = con->Attr(QN_ATTR_STATUS);
+ if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTING)
+ return XMPP_CONNECTION_STATUS_CONNECTING;
+ else if (status == STR_PSTN_CONFERENCE_STATUS_CONNECTED)
+ return XMPP_CONNECTION_STATUS_CONNECTED;
+ else if (status == STR_PSTN_CONFERENCE_STATUS_JOINING)
+ return XMPP_CONNECTION_STATUS_JOINING;
+ else if (status == STR_PSTN_CONFERENCE_STATUS_HANGUP)
+ return XMPP_CONNECTION_STATUS_HANGUP;
+ }
+
+ return XMPP_CONNECTION_STATUS_CONNECTED;
+}
+
+const std::string
+XmppPresenceImpl::google_user_id() const {
+ if (!raw_xml_)
+ return std::string();
+
+ XmlElement* muc_user_x = raw_xml_->FirstNamed(QN_MUC_USER_X);
+ if (muc_user_x) {
+ XmlElement* muc_user_item = muc_user_x->FirstNamed(QN_MUC_USER_ITEM);
+ if (muc_user_item) {
+ return muc_user_item->Attr(QN_GOOGLE_USER_ID);
+ }
+ }
+
+ return std::string();
+}
+
+const std::string
+XmppPresenceImpl::nickname() const {
+ if (!raw_xml_)
+ return std::string();
+
+ XmlElement* nickname = raw_xml_->FirstNamed(QN_NICKNAME);
+ if (nickname) {
+ return nickname->BodyText();
+ }
+
+ return std::string();
+}
+
+const XmlElement*
+XmppPresenceImpl::raw_xml() const {
+ if (!raw_xml_)
+ 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;
+
+ 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_)
+ 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_)
+ 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_)
+ return XMPP_SUBSCRIPTION_NONE;
+
+ XmppSubscriptionState state = XMPP_SUBSCRIPTION_NONE;
+
+ 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_)
+ 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_)
+ 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_)
+ 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_)
+ 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/webrtc/libjingle/xmpp/rostermoduleimpl.h b/webrtc/libjingle/xmpp/rostermoduleimpl.h
new file mode 100644
index 0000000000..6e3bd91c8a
--- /dev/null
+++ b/webrtc/libjingle/xmpp/rostermoduleimpl.h
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
+
+#include "webrtc/libjingle/xmpp/moduleimpl.h"
+#include "webrtc/libjingle/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 received 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 connection status
+ virtual XmppPresenceConnectionStatus connection_status() const;
+
+ //! The focus obfuscated GAIA id
+ virtual const std::string google_user_id() const;
+
+ //! The nickname in the presence
+ virtual const std::string nickname() const;
+
+ //! 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.
+ rtc::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_;
+ rtc::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;
+ rtc::scoped_ptr<JidPresenceVectorMap> incoming_presence_map_;
+ rtc::scoped_ptr<PresenceVector> incoming_presence_vector_;
+
+ typedef std::vector<XmppRosterContactImpl*> ContactVector;
+ rtc::scoped_ptr<ContactVector> contacts_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
diff --git a/webrtc/libjingle/xmpp/saslcookiemechanism.h b/webrtc/libjingle/xmpp/saslcookiemechanism.h
new file mode 100644
index 0000000000..7a912466a6
--- /dev/null
+++ b/webrtc/libjingle/xmpp/saslcookiemechanism.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_
+#define WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_
+
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/saslmechanism.h"
+
+namespace buzz {
+
+class SaslCookieMechanism : public SaslMechanism {
+
+public:
+ SaslCookieMechanism(const std::string & mechanism,
+ const std::string & username,
+ const std::string & cookie,
+ const std::string & token_service)
+ : mechanism_(mechanism),
+ username_(username),
+ cookie_(cookie),
+ token_service_(token_service) {}
+
+ SaslCookieMechanism(const std::string & mechanism,
+ const std::string & username,
+ const std::string & cookie)
+ : mechanism_(mechanism),
+ username_(username),
+ cookie_(cookie),
+ token_service_("") {}
+
+ virtual std::string GetMechanismName() { return mechanism_; }
+
+ virtual XmlElement * StartSaslAuth() {
+ // send initial request
+ XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+ el->AddAttr(QN_MECHANISM, mechanism_);
+ if (!token_service_.empty()) {
+ el->AddAttr(QN_GOOGLE_AUTH_SERVICE, token_service_);
+ }
+
+ std::string credential;
+ credential.append("\0", 1);
+ credential.append(username_);
+ credential.append("\0", 1);
+ credential.append(cookie_);
+ el->AddText(Base64Encode(credential));
+ return el;
+ }
+
+private:
+ std::string mechanism_;
+ std::string username_;
+ std::string cookie_;
+ std::string token_service_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_SASLCOOKIEMECHANISM_H_
diff --git a/webrtc/libjingle/xmpp/saslhandler.h b/webrtc/libjingle/xmpp/saslhandler.h
new file mode 100644
index 0000000000..f2e3844405
--- /dev/null
+++ b/webrtc/libjingle/xmpp/saslhandler.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_
+#define WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_
+
+#include <string>
+#include <vector>
+
+namespace buzz {
+
+class XmlElement;
+class SaslMechanism;
+
+// Creates mechanisms to deal with a given mechanism
+class SaslHandler {
+
+public:
+
+ // Intended to be subclassed
+ virtual ~SaslHandler() {}
+
+ // Should pick the best method according to this handler
+ // returns the empty string if none are suitable
+ virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) = 0;
+
+ // Creates a SaslMechanism for the given mechanism name (you own it
+ // once you get it).
+ // If not handled, return NULL.
+ virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_SASLHANDLER_H_
diff --git a/webrtc/libjingle/xmpp/saslmechanism.cc b/webrtc/libjingle/xmpp/saslmechanism.cc
new file mode 100644
index 0000000000..b4d6e9baa1
--- /dev/null
+++ b/webrtc/libjingle/xmpp/saslmechanism.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/saslmechanism.h"
+#include "webrtc/base/base64.h"
+
+using rtc::Base64;
+
+namespace buzz {
+
+XmlElement *
+SaslMechanism::StartSaslAuth() {
+ return new XmlElement(QN_SASL_AUTH, true);
+}
+
+XmlElement *
+SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) {
+ return new XmlElement(QN_SASL_ABORT, true);
+}
+
+void
+SaslMechanism::HandleSaslSuccess(const XmlElement * success) {
+}
+
+void
+SaslMechanism::HandleSaslFailure(const XmlElement * failure) {
+}
+
+std::string
+SaslMechanism::Base64Encode(const std::string & plain) {
+ return Base64::Encode(plain);
+}
+
+std::string
+SaslMechanism::Base64Decode(const std::string & encoded) {
+ return Base64::Decode(encoded, Base64::DO_LAX);
+}
+
+std::string
+SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) {
+ std::string result;
+ Base64::EncodeFromArray(plain, length, &result);
+ return result;
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/saslmechanism.h b/webrtc/libjingle/xmpp/saslmechanism.h
new file mode 100644
index 0000000000..9c392e5667
--- /dev/null
+++ b/webrtc/libjingle/xmpp/saslmechanism.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_
+#define WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_
+
+#include <string>
+
+namespace buzz {
+
+class XmlElement;
+
+
+// Defines a mechnanism to do SASL authentication.
+// Subclass instances should have a self-contained way to present
+// credentials.
+class SaslMechanism {
+
+public:
+
+ // Intended to be subclassed
+ virtual ~SaslMechanism() {}
+
+ // Should return the name of the SASL mechanism, e.g., "PLAIN"
+ virtual std::string GetMechanismName() = 0;
+
+ // Should generate the initial "auth" request. Default is just <auth/>.
+ virtual XmlElement * StartSaslAuth();
+
+ // Should respond to a SASL "<challenge>" request. Default is
+ // to abort (for mechanisms that do not do challenge-response)
+ virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge);
+
+ // Notification of a SASL "<success>". Sometimes information
+ // is passed on success.
+ virtual void HandleSaslSuccess(const XmlElement * success);
+
+ // Notification of a SASL "<failure>". Sometimes information
+ // for the user is passed on failure.
+ virtual void HandleSaslFailure(const XmlElement * failure);
+
+protected:
+ static std::string Base64Encode(const std::string & plain);
+ static std::string Base64Decode(const std::string & encoded);
+ static std::string Base64EncodeFromArray(const char * plain, size_t length);
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_SASLMECHANISM_H_
diff --git a/webrtc/libjingle/xmpp/saslplainmechanism.h b/webrtc/libjingle/xmpp/saslplainmechanism.h
new file mode 100644
index 0000000000..8e162e25b7
--- /dev/null
+++ b/webrtc/libjingle/xmpp/saslplainmechanism.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_
+#define WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_
+
+#include "webrtc/libjingle/xmpp/saslmechanism.h"
+#include "webrtc/base/cryptstring.h"
+
+namespace buzz {
+
+class SaslPlainMechanism : public SaslMechanism {
+
+public:
+ SaslPlainMechanism(const buzz::Jid user_jid, const rtc::CryptString & password) :
+ user_jid_(user_jid), password_(password) {}
+
+ virtual std::string GetMechanismName() { return "PLAIN"; }
+
+ virtual XmlElement * StartSaslAuth() {
+ // send initial request
+ XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+ el->AddAttr(QN_MECHANISM, "PLAIN");
+
+ rtc::FormatCryptString credential;
+ credential.Append("\0", 1);
+ credential.Append(user_jid_.node());
+ credential.Append("\0", 1);
+ credential.Append(&password_);
+ el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength()));
+ return el;
+ }
+
+private:
+ Jid user_jid_;
+ rtc::CryptString password_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_SASLPLAINMECHANISM_H_
diff --git a/webrtc/libjingle/xmpp/util_unittest.cc b/webrtc/libjingle/xmpp/util_unittest.cc
new file mode 100644
index 0000000000..330ab48ef4
--- /dev/null
+++ b/webrtc/libjingle/xmpp/util_unittest.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/util_unittest.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/gunit.h"
+
+namespace buzz {
+
+void XmppTestHandler::WriteOutput(const char * bytes, size_t len) {
+ output_ << std::string(bytes, len);
+}
+
+void XmppTestHandler::StartTls(const std::string & cname) {
+ output_ << "[START-TLS " << cname << "]";
+}
+
+void XmppTestHandler::CloseConnection() {
+ output_ << "[CLOSED]";
+}
+
+void XmppTestHandler::OnStateChange(int state) {
+ switch (static_cast<XmppEngine::State>(state)) {
+ case XmppEngine::STATE_START:
+ session_ << "[START]";
+ break;
+ case XmppEngine::STATE_OPENING:
+ session_ << "[OPENING]";
+ break;
+ case XmppEngine::STATE_OPEN:
+ session_ << "[OPEN]";
+ break;
+ case XmppEngine::STATE_CLOSED:
+ session_ << "[CLOSED]";
+ switch (engine_->GetError(NULL)) {
+ case XmppEngine::ERROR_NONE:
+ // do nothing
+ break;
+ case XmppEngine::ERROR_XML:
+ session_ << "[ERROR-XML]";
+ break;
+ case XmppEngine::ERROR_STREAM:
+ session_ << "[ERROR-STREAM]";
+ break;
+ case XmppEngine::ERROR_VERSION:
+ session_ << "[ERROR-VERSION]";
+ break;
+ case XmppEngine::ERROR_UNAUTHORIZED:
+ session_ << "[ERROR-UNAUTHORIZED]";
+ break;
+ case XmppEngine::ERROR_TLS:
+ session_ << "[ERROR-TLS]";
+ break;
+ case XmppEngine::ERROR_AUTH:
+ session_ << "[ERROR-AUTH]";
+ break;
+ case XmppEngine::ERROR_BIND:
+ session_ << "[ERROR-BIND]";
+ break;
+ case XmppEngine::ERROR_CONNECTION_CLOSED:
+ session_ << "[ERROR-CONNECTION-CLOSED]";
+ break;
+ case XmppEngine::ERROR_DOCUMENT_CLOSED:
+ session_ << "[ERROR-DOCUMENT-CLOSED]";
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+bool XmppTestHandler::HandleStanza(const XmlElement * stanza) {
+ stanza_ << stanza->Str();
+ return true;
+}
+
+std::string XmppTestHandler::OutputActivity() {
+ std::string result = output_.str();
+ output_.str("");
+ return result;
+}
+
+std::string XmppTestHandler::SessionActivity() {
+ std::string result = session_.str();
+ session_.str("");
+ return result;
+}
+
+std::string XmppTestHandler::StanzaActivity() {
+ std::string result = stanza_.str();
+ stanza_.str("");
+ return result;
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/util_unittest.h b/webrtc/libjingle/xmpp/util_unittest.h
new file mode 100644
index 0000000000..38009fb4e9
--- /dev/null
+++ b/webrtc/libjingle/xmpp/util_unittest.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_
+#define WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_
+
+#include <sstream>
+#include <string>
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+
+namespace buzz {
+
+// This class captures callbacks from engine.
+class XmppTestHandler : public XmppOutputHandler, public XmppSessionHandler,
+ public XmppStanzaHandler {
+ public:
+ explicit XmppTestHandler(XmppEngine* engine) : engine_(engine) {}
+ virtual ~XmppTestHandler() {}
+
+ void SetEngine(XmppEngine* engine);
+
+ // Output handler
+ virtual void WriteOutput(const char * bytes, size_t len);
+ virtual void StartTls(const std::string & cname);
+ virtual void CloseConnection();
+
+ // Session handler
+ virtual void OnStateChange(int state);
+
+ // Stanza handler
+ virtual bool HandleStanza(const XmlElement* stanza);
+
+ std::string OutputActivity();
+ std::string SessionActivity();
+ std::string StanzaActivity();
+
+ private:
+ XmppEngine* engine_;
+ std::stringstream output_;
+ std::stringstream session_;
+ std::stringstream stanza_;
+};
+
+} // namespace buzz
+
+inline std::ostream& operator<<(std::ostream& os, const buzz::Jid& jid) {
+ os << jid.Str();
+ return os;
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_UTIL_UNITTEST_H_
diff --git a/webrtc/libjingle/xmpp/xmpp.gyp b/webrtc/libjingle/xmpp/xmpp.gyp
new file mode 100644
index 0000000000..6b738e7f3c
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpp.gyp
@@ -0,0 +1,145 @@
+# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+{
+ 'includes': [ '../../build/common.gypi', ],
+ 'targets': [
+ {
+ 'target_name': 'rtc_xmpp',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(webrtc_root)/base/base.gyp:rtc_base',
+ '<(webrtc_root)/libjingle/xmllite/xmllite.gyp:rtc_xmllite',
+ ],
+ 'defines': [
+ 'FEATURE_ENABLE_SSL',
+ ],
+ 'cflags_cc!': [
+ '-Wnon-virtual-dtor',
+ ],
+ 'sources': [
+ 'asyncsocket.h',
+ 'chatroommodule.h',
+ 'chatroommoduleimpl.cc',
+ 'constants.cc',
+ 'constants.h',
+ 'discoitemsquerytask.cc',
+ 'discoitemsquerytask.h',
+ 'hangoutpubsubclient.cc',
+ 'hangoutpubsubclient.h',
+ 'iqtask.cc',
+ 'iqtask.h',
+ 'jid.cc',
+ 'jid.h',
+ 'module.h',
+ 'moduleimpl.cc',
+ 'moduleimpl.h',
+ 'mucroomconfigtask.cc',
+ 'mucroomconfigtask.h',
+ 'mucroomdiscoverytask.cc',
+ 'mucroomdiscoverytask.h',
+ 'mucroomlookuptask.cc',
+ 'mucroomlookuptask.h',
+ 'mucroomuniquehangoutidtask.cc',
+ 'mucroomuniquehangoutidtask.h',
+ 'pingtask.cc',
+ 'pingtask.h',
+ 'plainsaslhandler.h',
+ 'presenceouttask.cc',
+ 'presenceouttask.h',
+ 'presencereceivetask.cc',
+ 'presencereceivetask.h',
+ 'presencestatus.cc',
+ 'presencestatus.h',
+ 'prexmppauth.h',
+ 'pubsub_task.cc',
+ 'pubsub_task.h',
+ 'pubsubclient.cc',
+ 'pubsubclient.h',
+ 'pubsubstateclient.cc',
+ 'pubsubstateclient.h',
+ 'pubsubtasks.cc',
+ 'pubsubtasks.h',
+ 'receivetask.cc',
+ 'receivetask.h',
+ 'rostermodule.h',
+ 'rostermoduleimpl.cc',
+ 'rostermoduleimpl.h',
+ 'saslcookiemechanism.h',
+ 'saslhandler.h',
+ 'saslmechanism.cc',
+ 'saslmechanism.h',
+ 'saslplainmechanism.h',
+ 'xmppauth.cc',
+ 'xmppauth.h',
+ 'xmppclient.cc',
+ 'xmppclient.h',
+ 'xmppclientsettings.h',
+ 'xmppengine.h',
+ 'xmppengineimpl.cc',
+ 'xmppengineimpl.h',
+ 'xmppengineimpl_iq.cc',
+ 'xmpplogintask.cc',
+ 'xmpplogintask.h',
+ 'xmpppump.cc',
+ 'xmpppump.h',
+ 'xmppsocket.cc',
+ 'xmppsocket.h',
+ 'xmppstanzaparser.cc',
+ 'xmppstanzaparser.h',
+ 'xmpptask.cc',
+ 'xmpptask.h',
+ 'xmppthread.cc',
+ 'xmppthread.h',
+ ],
+ 'direct_dependent_settings': {
+ 'cflags_cc!': [
+ '-Wnon-virtual-dtor',
+ ],
+ 'defines': [
+ 'FEATURE_ENABLE_SSL',
+ 'FEATURE_ENABLE_VOICEMAIL',
+ ],
+ },
+ 'conditions': [
+ ['build_expat==1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/expat/expat.gyp:expat',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/third_party/expat/expat.gyp:expat',
+ ],
+ }],
+ ['build_with_chromium==0', {
+ 'defines': [
+ 'FEATURE_ENABLE_VOICEMAIL',
+ 'FEATURE_ENABLE_PSTN',
+ ],
+ }],
+ ['os_posix==1', {
+ 'configurations': {
+ 'Debug_Base': {
+ 'defines': [
+ # Chromium's build/common.gypi defines this for all posix
+ # _except_ for ios & mac. We want it there as well, e.g.
+ # because ASSERT and friends trigger off of it.
+ '_DEBUG',
+ ],
+ },
+ }
+ }],
+ ['OS=="android"', {
+ 'cflags!': [
+ '-Wextra',
+ '-Wall',
+ ],
+ }],
+ ],
+ }],
+}
+
diff --git a/webrtc/libjingle/xmpp/xmpp_tests.gypi b/webrtc/libjingle/xmpp/xmpp_tests.gypi
new file mode 100644
index 0000000000..f1dec1cd1c
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpp_tests.gypi
@@ -0,0 +1,37 @@
+# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+{
+ 'includes': [ '../../build/common.gypi', ],
+ 'targets': [
+ {
+ 'target_name': 'rtc_xmpp_unittest',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'sources': [
+ 'fakexmppclient.h',
+ 'hangoutpubsubclient_unittest.cc',
+ 'jid_unittest.cc',
+ 'mucroomconfigtask_unittest.cc',
+ 'mucroomdiscoverytask_unittest.cc',
+ 'mucroomlookuptask_unittest.cc',
+ 'mucroomuniquehangoutidtask_unittest.cc',
+ 'pingtask_unittest.cc',
+ 'pubsubclient_unittest.cc',
+ 'pubsubtasks_unittest.cc',
+ 'util_unittest.cc',
+ 'util_unittest.h',
+ 'xmppengine_unittest.cc',
+ 'xmpplogintask_unittest.cc',
+ 'xmppstanzaparser_unittest.cc',
+ ],
+ },
+ },
+ ],
+}
+
diff --git a/webrtc/libjingle/xmpp/xmppauth.cc b/webrtc/libjingle/xmpp/xmppauth.cc
new file mode 100644
index 0000000000..a3d2f67849
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppauth.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmppauth.h"
+
+#include <algorithm>
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/saslcookiemechanism.h"
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h"
+
+XmppAuth::XmppAuth() : done_(false) {
+}
+
+XmppAuth::~XmppAuth() {
+}
+
+void XmppAuth::StartPreXmppAuth(const buzz::Jid& jid,
+ const rtc::SocketAddress& server,
+ const rtc::CryptString& pass,
+ const std::string& auth_mechanism,
+ const std::string& auth_token) {
+ jid_ = jid;
+ passwd_ = pass;
+ auth_mechanism_ = auth_mechanism;
+ auth_token_ = auth_token;
+ done_ = true;
+
+ SignalAuthDone();
+}
+
+static bool contains(const std::vector<std::string>& strings,
+ const std::string& string) {
+ return std::find(strings.begin(), strings.end(), string) != strings.end();
+}
+
+std::string XmppAuth::ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms,
+ bool encrypted) {
+ // First try Oauth2.
+ if (GetAuthMechanism() == buzz::AUTH_MECHANISM_OAUTH2 &&
+ contains(mechanisms, buzz::AUTH_MECHANISM_OAUTH2)) {
+ return buzz::AUTH_MECHANISM_OAUTH2;
+ }
+
+ // A token is the weakest auth - 15s, service-limited, so prefer it.
+ if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_TOKEN &&
+ contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_TOKEN)) {
+ return buzz::AUTH_MECHANISM_GOOGLE_TOKEN;
+ }
+
+ // A cookie is the next weakest - 14 days.
+ if (GetAuthMechanism() == buzz::AUTH_MECHANISM_GOOGLE_COOKIE &&
+ contains(mechanisms, buzz::AUTH_MECHANISM_GOOGLE_COOKIE)) {
+ return buzz::AUTH_MECHANISM_GOOGLE_COOKIE;
+ }
+
+ // As a last resort, use plain authentication.
+ if (contains(mechanisms, buzz::AUTH_MECHANISM_PLAIN)) {
+ return buzz::AUTH_MECHANISM_PLAIN;
+ }
+
+ // No good mechanism found
+ return "";
+}
+
+buzz::SaslMechanism* XmppAuth::CreateSaslMechanism(
+ const std::string& mechanism) {
+ if (mechanism == buzz::AUTH_MECHANISM_OAUTH2) {
+ return new buzz::SaslCookieMechanism(
+ mechanism, jid_.Str(), auth_token_, "oauth2");
+ } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_TOKEN) {
+ return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_token_);
+ // } else if (mechanism == buzz::AUTH_MECHANISM_GOOGLE_COOKIE) {
+ // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_);
+ } else if (mechanism == buzz::AUTH_MECHANISM_PLAIN) {
+ return new buzz::SaslPlainMechanism(jid_, passwd_);
+ } else {
+ return NULL;
+ }
+}
diff --git a/webrtc/libjingle/xmpp/xmppauth.h b/webrtc/libjingle/xmpp/xmppauth.h
new file mode 100644
index 0000000000..dd363d17ca
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppauth.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_
+
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/prexmppauth.h"
+#include "webrtc/libjingle/xmpp/saslhandler.h"
+#include "webrtc/base/cryptstring.h"
+#include "webrtc/base/sigslot.h"
+
+class XmppAuth: public buzz::PreXmppAuth {
+public:
+ XmppAuth();
+ virtual ~XmppAuth();
+
+ // TODO: Just have one "secret" that is either pass or
+ // token?
+ virtual void StartPreXmppAuth(const buzz::Jid& jid,
+ const rtc::SocketAddress& server,
+ const rtc::CryptString& pass,
+ const std::string& auth_mechanism,
+ const std::string& auth_token);
+
+ virtual bool IsAuthDone() const { return done_; }
+ virtual bool IsAuthorized() const { return true; }
+ virtual bool HadError() const { return false; }
+ virtual int GetError() const { return 0; }
+ virtual buzz::CaptchaChallenge GetCaptchaChallenge() const {
+ return buzz::CaptchaChallenge();
+ }
+ virtual std::string GetAuthMechanism() const { return auth_mechanism_; }
+ virtual std::string GetAuthToken() const { return auth_token_; }
+
+ virtual std::string ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms,
+ bool encrypted);
+
+ virtual buzz::SaslMechanism * CreateSaslMechanism(
+ const std::string& mechanism);
+
+private:
+ buzz::Jid jid_;
+ rtc::CryptString passwd_;
+ std::string auth_mechanism_;
+ std::string auth_token_;
+ bool done_;
+};
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPAUTH_H_
+
diff --git a/webrtc/libjingle/xmpp/xmppclient.cc b/webrtc/libjingle/xmpp/xmppclient.cc
new file mode 100644
index 0000000000..7c2a5e693c
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppclient.cc
@@ -0,0 +1,424 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/plainsaslhandler.h"
+#include "webrtc/libjingle/xmpp/prexmppauth.h"
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/stringutils.h"
+#include "xmpptask.h"
+
+namespace buzz {
+
+class XmppClient::Private :
+ public sigslot::has_slots<>,
+ public XmppSessionHandler,
+ public XmppOutputHandler {
+public:
+
+ explicit Private(XmppClient* client) :
+ client_(client),
+ socket_(),
+ engine_(),
+ proxy_port_(0),
+ pre_engine_error_(XmppEngine::ERROR_NONE),
+ pre_engine_subcode_(0),
+ signal_closed_(false),
+ allow_plain_(false) {}
+
+ virtual ~Private() {
+ // We need to disconnect from socket_ before engine_ is destructed (by
+ // the auto-generated destructor code).
+ ResetSocket();
+ }
+
+ // the owner
+ XmppClient* const client_;
+
+ // the two main objects
+ rtc::scoped_ptr<AsyncSocket> socket_;
+ rtc::scoped_ptr<XmppEngine> engine_;
+ rtc::scoped_ptr<PreXmppAuth> pre_auth_;
+ rtc::CryptString pass_;
+ std::string auth_mechanism_;
+ std::string auth_token_;
+ rtc::SocketAddress server_;
+ std::string proxy_host_;
+ int proxy_port_;
+ XmppEngine::Error pre_engine_error_;
+ int pre_engine_subcode_;
+ CaptchaChallenge captcha_challenge_;
+ bool signal_closed_;
+ bool allow_plain_;
+
+ void ResetSocket() {
+ if (socket_) {
+ socket_->SignalConnected.disconnect(this);
+ socket_->SignalRead.disconnect(this);
+ socket_->SignalClosed.disconnect(this);
+ socket_.reset(NULL);
+ }
+ }
+
+ // implementations of interfaces
+ void OnStateChange(int state);
+ void WriteOutput(const char* bytes, size_t len);
+ void StartTls(const std::string& domainname);
+ void CloseConnection();
+
+ // slots for socket signals
+ void OnSocketConnected();
+ void OnSocketRead();
+ void OnSocketClosed();
+};
+
+bool IsTestServer(const std::string& server_name,
+ const std::string& test_server_domain) {
+ return (!test_server_domain.empty() &&
+ rtc::ends_with(server_name.c_str(),
+ test_server_domain.c_str()));
+}
+
+XmppReturnStatus XmppClient::Connect(
+ const XmppClientSettings& settings,
+ const std::string& lang, AsyncSocket* socket, PreXmppAuth* pre_auth) {
+ if (socket == NULL)
+ return XMPP_RETURN_BADARGUMENT;
+ if (d_->socket_)
+ return XMPP_RETURN_BADSTATE;
+
+ d_->socket_.reset(socket);
+
+ d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
+ d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
+ d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
+
+ d_->engine_.reset(XmppEngine::Create());
+ d_->engine_->SetSessionHandler(d_.get());
+ d_->engine_->SetOutputHandler(d_.get());
+ if (!settings.resource().empty()) {
+ d_->engine_->SetRequestedResource(settings.resource());
+ }
+ d_->engine_->SetTls(settings.use_tls());
+
+ // The talk.google.com server returns a certificate with common-name:
+ // CN="gmail.com" for @gmail.com accounts,
+ // CN="googlemail.com" for @googlemail.com accounts,
+ // CN="talk.google.com" for other accounts (such as @example.com),
+ // so we tweak the tls server setting for those other accounts to match the
+ // returned certificate CN of "talk.google.com".
+ // For other servers, we leave the strings empty, which causes the jid's
+ // domain to be used. We do the same for gmail.com and googlemail.com as the
+ // returned CN matches the account domain in those cases.
+ std::string server_name = settings.server().HostAsURIString();
+ if (server_name == buzz::STR_TALK_GOOGLE_COM ||
+ server_name == buzz::STR_TALKX_L_GOOGLE_COM ||
+ server_name == buzz::STR_XMPP_GOOGLE_COM ||
+ server_name == buzz::STR_XMPPX_L_GOOGLE_COM ||
+ IsTestServer(server_name, settings.test_server_domain())) {
+ if (settings.host() != STR_GMAIL_COM &&
+ settings.host() != STR_GOOGLEMAIL_COM) {
+ d_->engine_->SetTlsServer("", STR_TALK_GOOGLE_COM);
+ }
+ }
+
+ // Set language
+ d_->engine_->SetLanguage(lang);
+
+ d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
+
+ d_->pass_ = settings.pass();
+ d_->auth_mechanism_ = settings.auth_mechanism();
+ d_->auth_token_ = settings.auth_token();
+ d_->server_ = settings.server();
+ d_->proxy_host_ = settings.proxy_host();
+ d_->proxy_port_ = settings.proxy_port();
+ d_->allow_plain_ = settings.allow_plain();
+ d_->pre_auth_.reset(pre_auth);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppEngine::State XmppClient::GetState() const {
+ if (!d_->engine_)
+ return XmppEngine::STATE_NONE;
+ return d_->engine_->GetState();
+}
+
+XmppEngine::Error XmppClient::GetError(int* subcode) {
+ if (subcode) {
+ *subcode = 0;
+ }
+ if (!d_->engine_)
+ return XmppEngine::ERROR_NONE;
+ if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
+ if (subcode) {
+ *subcode = d_->pre_engine_subcode_;
+ }
+ return d_->pre_engine_error_;
+ }
+ return d_->engine_->GetError(subcode);
+}
+
+const XmlElement* XmppClient::GetStreamError() {
+ if (!d_->engine_) {
+ return NULL;
+ }
+ return d_->engine_->GetStreamError();
+}
+
+CaptchaChallenge XmppClient::GetCaptchaChallenge() {
+ if (!d_->engine_)
+ return CaptchaChallenge();
+ return d_->captcha_challenge_;
+}
+
+std::string XmppClient::GetAuthMechanism() {
+ if (!d_->engine_)
+ return "";
+ return d_->auth_mechanism_;
+}
+
+std::string XmppClient::GetAuthToken() {
+ if (!d_->engine_)
+ return "";
+ return d_->auth_token_;
+}
+
+int XmppClient::ProcessStart() {
+ // Should not happen, but was observed in crash reports
+ if (!d_->socket_) {
+ LOG(LS_ERROR) << "socket_ already reset";
+ return STATE_DONE;
+ }
+
+ if (d_->pre_auth_) {
+ d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
+ d_->pre_auth_->StartPreXmppAuth(
+ d_->engine_->GetUser(), d_->server_, d_->pass_,
+ d_->auth_mechanism_, d_->auth_token_);
+ d_->pass_.Clear(); // done with this;
+ return STATE_PRE_XMPP_LOGIN;
+ }
+ else {
+ d_->engine_->SetSaslHandler(new PlainSaslHandler(
+ d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
+ d_->pass_.Clear(); // done with this;
+ return STATE_START_XMPP_LOGIN;
+ }
+}
+
+void XmppClient::OnAuthDone() {
+ Wake();
+}
+
+int XmppClient::ProcessTokenLogin() {
+ // Should not happen, but was observed in crash reports
+ if (!d_->socket_) {
+ LOG(LS_ERROR) << "socket_ already reset";
+ return STATE_DONE;
+ }
+
+ // Don't know how this could happen, but crash reports show it as NULL
+ if (!d_->pre_auth_) {
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ // Wait until pre authentication is done is done
+ if (!d_->pre_auth_->IsAuthDone())
+ return STATE_BLOCKED;
+
+ if (!d_->pre_auth_->IsAuthorized()) {
+ // maybe split out a case when gaia is down?
+ if (d_->pre_auth_->HadError()) {
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+ d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
+ }
+ else {
+ d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
+ d_->pre_engine_subcode_ = 0;
+ d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
+ }
+ d_->pre_auth_.reset(NULL); // done with this
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ // Save auth token as a result
+
+ d_->auth_mechanism_ = d_->pre_auth_->GetAuthMechanism();
+ d_->auth_token_ = d_->pre_auth_->GetAuthToken();
+
+ // transfer ownership of pre_auth_ to engine
+ d_->engine_->SetSaslHandler(d_->pre_auth_.release());
+ return STATE_START_XMPP_LOGIN;
+}
+
+int XmppClient::ProcessStartXmppLogin() {
+ // Should not happen, but was observed in crash reports
+ if (!d_->socket_) {
+ LOG(LS_ERROR) << "socket_ already reset";
+ return STATE_DONE;
+ }
+
+ // Done with pre-connect tasks - connect!
+ if (!d_->socket_->Connect(d_->server_)) {
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ return STATE_RESPONSE;
+}
+
+int XmppClient::ProcessResponse() {
+ // Hang around while we are connected.
+ if (!delivering_signal_ &&
+ (!d_->engine_ || d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
+ return STATE_DONE;
+ return STATE_BLOCKED;
+}
+
+XmppReturnStatus XmppClient::Disconnect() {
+ if (!d_->socket_)
+ return XMPP_RETURN_BADSTATE;
+ Abort();
+ d_->engine_->Disconnect();
+ d_->ResetSocket();
+ return XMPP_RETURN_OK;
+}
+
+XmppClient::XmppClient(TaskParent* parent)
+ : XmppTaskParentInterface(parent),
+ delivering_signal_(false),
+ valid_(false) {
+ d_.reset(new Private(this));
+ valid_ = true;
+}
+
+XmppClient::~XmppClient() {
+ valid_ = false;
+}
+
+const Jid& XmppClient::jid() const {
+ return d_->engine_->FullJid();
+}
+
+
+std::string XmppClient::NextId() {
+ return d_->engine_->NextId();
+}
+
+XmppReturnStatus XmppClient::SendStanza(const XmlElement* stanza) {
+ return d_->engine_->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppClient::SendStanzaError(
+ const XmlElement* old_stanza, XmppStanzaError xse,
+ const std::string& message) {
+ return d_->engine_->SendStanzaError(old_stanza, xse, message);
+}
+
+XmppReturnStatus XmppClient::SendRaw(const std::string& text) {
+ return d_->engine_->SendRaw(text);
+}
+
+XmppEngine* XmppClient::engine() {
+ return d_->engine_.get();
+}
+
+void XmppClient::Private::OnSocketConnected() {
+ engine_->Connect();
+}
+
+void XmppClient::Private::OnSocketRead() {
+ char bytes[4096];
+ size_t bytes_read;
+ for (;;) {
+ // Should not happen, but was observed in crash reports
+ if (!socket_) {
+ LOG(LS_ERROR) << "socket_ already reset";
+ return;
+ }
+
+ if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
+ // TODO: deal with error information
+ return;
+ }
+
+ if (bytes_read == 0)
+ return;
+
+//#ifdef _DEBUG
+ client_->SignalLogInput(bytes, static_cast<int>(bytes_read));
+//#endif
+
+ engine_->HandleInput(bytes, bytes_read);
+ }
+}
+
+void XmppClient::Private::OnSocketClosed() {
+ int code = socket_->GetError();
+ engine_->ConnectionClosed(code);
+}
+
+void XmppClient::Private::OnStateChange(int state) {
+ if (state == XmppEngine::STATE_CLOSED) {
+ client_->EnsureClosed();
+ }
+ else {
+ client_->SignalStateChange((XmppEngine::State)state);
+ }
+ client_->Wake();
+}
+
+void XmppClient::Private::WriteOutput(const char* bytes, size_t len) {
+//#ifdef _DEBUG
+ client_->SignalLogOutput(bytes, static_cast<int>(len));
+//#endif
+
+ socket_->Write(bytes, len);
+ // TODO: deal with error information
+}
+
+void XmppClient::Private::StartTls(const std::string& domain) {
+#if defined(FEATURE_ENABLE_SSL)
+ socket_->StartTls(domain);
+#endif
+}
+
+void XmppClient::Private::CloseConnection() {
+ socket_->Close();
+}
+
+void XmppClient::AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) {
+ d_->engine_->AddStanzaHandler(task, level);
+}
+
+void XmppClient::RemoveXmppTask(XmppTask* task) {
+ d_->engine_->RemoveStanzaHandler(task);
+}
+
+void XmppClient::EnsureClosed() {
+ if (!d_->signal_closed_) {
+ d_->signal_closed_ = true;
+ delivering_signal_ = true;
+ SignalStateChange(XmppEngine::STATE_CLOSED);
+ delivering_signal_ = false;
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/xmppclient.h b/webrtc/libjingle/xmpp/xmppclient.h
new file mode 100644
index 0000000000..7b9eb7ab74
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppclient.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_
+
+#include <string>
+#include "webrtc/libjingle/xmpp/asyncsocket.h"
+#include "webrtc/libjingle/xmpp/xmppclientsettings.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/basicdefs.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/task.h"
+
+namespace buzz {
+
+class PreXmppAuth;
+class CaptchaChallenge;
+
+// Just some non-colliding number. Could have picked "1".
+#define XMPP_CLIENT_TASK_CODE 0x366c1e47
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPCLIENT
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task first. XmppClient is a parent task for XmppTasks.
+//
+// XmppClient is a task which is designed to be the parent task for
+// all tasks that depend on a single Xmpp connection. If you want to,
+// for example, listen for subscription requests forever, then your
+// listener should be a task that is a child of the XmppClient that owns
+// the connection you are using. XmppClient has all the utility methods
+// that basically drill through to XmppEngine.
+//
+// XmppClient is just a wrapper for XmppEngine, and if I were writing it
+// all over again, I would make XmppClient == XmppEngine. Why?
+// XmppEngine needs tasks too, for example it has an XmppLoginTask which
+// should just be the same kind of Task instead of an XmppEngine specific
+// thing. It would help do certain things like GAIA auth cleaner.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient : public XmppTaskParentInterface,
+ public XmppClientInterface,
+ public sigslot::has_slots<>
+{
+public:
+ explicit XmppClient(rtc::TaskParent * parent);
+ virtual ~XmppClient();
+
+ XmppReturnStatus Connect(const XmppClientSettings & settings,
+ const std::string & lang,
+ AsyncSocket * socket,
+ PreXmppAuth * preauth);
+
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ XmppReturnStatus Disconnect();
+
+ sigslot::signal1<XmppEngine::State> SignalStateChange;
+ XmppEngine::Error GetError(int *subcode);
+
+ // When there is a <stream:error> stanza, return the stanza
+ // so that they can be handled.
+ const XmlElement *GetStreamError();
+
+ // When there is an authentication error, we may have captcha info
+ // that the user can use to unlock their account
+ CaptchaChallenge GetCaptchaChallenge();
+
+ // When authentication is successful, this returns the service token
+ // (if we used GAIA authentication)
+ std::string GetAuthMechanism();
+ std::string GetAuthToken();
+
+ XmppReturnStatus SendRaw(const std::string & text);
+
+ XmppEngine* engine();
+
+ sigslot::signal2<const char *, int> SignalLogInput;
+ sigslot::signal2<const char *, int> SignalLogOutput;
+
+ // As XmppTaskParentIntreface
+ virtual XmppClientInterface* GetClient() { return this; }
+
+ // As XmppClientInterface
+ virtual XmppEngine::State GetState() const;
+ virtual const Jid& jid() const;
+ virtual std::string NextId();
+ virtual XmppReturnStatus SendStanza(const XmlElement *stanza);
+ virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text);
+ virtual void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel);
+ virtual void RemoveXmppTask(XmppTask *);
+
+ private:
+ friend class XmppTask;
+
+ void OnAuthDone();
+
+ // Internal state management
+ enum {
+ STATE_PRE_XMPP_LOGIN = STATE_NEXT,
+ STATE_START_XMPP_LOGIN = STATE_NEXT + 1,
+ };
+ int Process(int state) {
+ switch (state) {
+ case STATE_PRE_XMPP_LOGIN: return ProcessTokenLogin();
+ case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin();
+ default: return Task::Process(state);
+ }
+ }
+
+ std::string GetStateName(int state) const {
+ switch (state) {
+ case STATE_PRE_XMPP_LOGIN: return "PRE_XMPP_LOGIN";
+ case STATE_START_XMPP_LOGIN: return "START_XMPP_LOGIN";
+ default: return Task::GetStateName(state);
+ }
+ }
+
+ int ProcessTokenLogin();
+ int ProcessStartXmppLogin();
+ void EnsureClosed();
+
+ class Private;
+ friend class Private;
+ rtc::scoped_ptr<Private> d_;
+
+ bool delivering_signal_;
+ bool valid_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_
diff --git a/webrtc/libjingle/xmpp/xmppclientsettings.h b/webrtc/libjingle/xmpp/xmppclientsettings.h
new file mode 100644
index 0000000000..5b7572aa6e
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppclientsettings.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPCLIENTSETTINGS_H_
+
+#include "webrtc/p2p/base/port.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/cryptstring.h"
+
+namespace buzz {
+
+class XmppUserSettings {
+ public:
+ XmppUserSettings()
+ : use_tls_(buzz::TLS_DISABLED),
+ allow_plain_(false) {
+ }
+
+ void set_user(const std::string& user) { user_ = user; }
+ void set_host(const std::string& host) { host_ = host; }
+ void set_pass(const rtc::CryptString& pass) { pass_ = pass; }
+ void set_auth_token(const std::string& mechanism,
+ const std::string& token) {
+ auth_mechanism_ = mechanism;
+ auth_token_ = token;
+ }
+ void set_resource(const std::string& resource) { resource_ = resource; }
+ void set_use_tls(const TlsOptions use_tls) { use_tls_ = use_tls; }
+ void set_allow_plain(bool f) { allow_plain_ = f; }
+ void set_test_server_domain(const std::string& test_server_domain) {
+ test_server_domain_ = test_server_domain;
+ }
+ void set_token_service(const std::string& token_service) {
+ token_service_ = token_service;
+ }
+
+ const std::string& user() const { return user_; }
+ const std::string& host() const { return host_; }
+ const rtc::CryptString& pass() const { return pass_; }
+ const std::string& auth_mechanism() const { return auth_mechanism_; }
+ const std::string& auth_token() const { return auth_token_; }
+ const std::string& resource() const { return resource_; }
+ TlsOptions use_tls() const { return use_tls_; }
+ bool allow_plain() const { return allow_plain_; }
+ const std::string& test_server_domain() const { return test_server_domain_; }
+ const std::string& token_service() const { return token_service_; }
+
+ private:
+ std::string user_;
+ std::string host_;
+ rtc::CryptString pass_;
+ std::string auth_mechanism_;
+ std::string auth_token_;
+ std::string resource_;
+ TlsOptions use_tls_;
+ bool allow_plain_;
+ std::string test_server_domain_;
+ std::string token_service_;
+};
+
+class XmppClientSettings : public XmppUserSettings {
+ public:
+ XmppClientSettings()
+ : protocol_(cricket::PROTO_TCP),
+ proxy_(rtc::PROXY_NONE),
+ proxy_port_(80),
+ use_proxy_auth_(false) {
+ }
+
+ void set_server(const rtc::SocketAddress& server) {
+ server_ = server;
+ }
+ void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
+ void set_proxy(rtc::ProxyType f) { proxy_ = f; }
+ void set_proxy_host(const std::string& host) { proxy_host_ = host; }
+ void set_proxy_port(int port) { proxy_port_ = port; };
+ void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; }
+ void set_proxy_user(const std::string& user) { proxy_user_ = user; }
+ void set_proxy_pass(const rtc::CryptString& pass) { proxy_pass_ = pass; }
+
+ const rtc::SocketAddress& server() const { return server_; }
+ cricket::ProtocolType protocol() const { return protocol_; }
+ rtc::ProxyType proxy() const { return proxy_; }
+ const std::string& proxy_host() const { return proxy_host_; }
+ int proxy_port() const { return proxy_port_; }
+ bool use_proxy_auth() const { return use_proxy_auth_; }
+ const std::string& proxy_user() const { return proxy_user_; }
+ const rtc::CryptString& proxy_pass() const { return proxy_pass_; }
+
+ private:
+ rtc::SocketAddress server_;
+ cricket::ProtocolType protocol_;
+ rtc::ProxyType proxy_;
+ std::string proxy_host_;
+ int proxy_port_;
+ bool use_proxy_auth_;
+ std::string proxy_user_;
+ rtc::CryptString proxy_pass_;
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPCLIENT_H_
diff --git a/webrtc/libjingle/xmpp/xmppengine.h b/webrtc/libjingle/xmpp/xmppengine.h
new file mode 100644
index 0000000000..2bc2453ce6
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppengine.h
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_
+
+// also part of the API
+#include "webrtc/libjingle/xmllite/qname.h"
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+
+
+namespace buzz {
+
+class XmppEngine;
+class SaslHandler;
+typedef void * XmppIqCookie;
+
+//! XMPP stanza error codes.
+//! Used in XmppEngine.SendStanzaError().
+enum XmppStanzaError {
+ XSE_BAD_REQUEST,
+ XSE_CONFLICT,
+ XSE_FEATURE_NOT_IMPLEMENTED,
+ XSE_FORBIDDEN,
+ XSE_GONE,
+ XSE_INTERNAL_SERVER_ERROR,
+ XSE_ITEM_NOT_FOUND,
+ XSE_JID_MALFORMED,
+ XSE_NOT_ACCEPTABLE,
+ XSE_NOT_ALLOWED,
+ XSE_PAYMENT_REQUIRED,
+ XSE_RECIPIENT_UNAVAILABLE,
+ XSE_REDIRECT,
+ XSE_REGISTRATION_REQUIRED,
+ XSE_SERVER_NOT_FOUND,
+ XSE_SERVER_TIMEOUT,
+ XSE_RESOURCE_CONSTRAINT,
+ XSE_SERVICE_UNAVAILABLE,
+ XSE_SUBSCRIPTION_REQUIRED,
+ XSE_UNDEFINED_CONDITION,
+ XSE_UNEXPECTED_REQUEST,
+};
+
+// XmppReturnStatus
+// This is used by API functions to synchronously return status.
+enum XmppReturnStatus {
+ XMPP_RETURN_OK,
+ XMPP_RETURN_BADARGUMENT,
+ XMPP_RETURN_BADSTATE,
+ XMPP_RETURN_PENDING,
+ XMPP_RETURN_UNEXPECTED,
+ XMPP_RETURN_NOTYETIMPLEMENTED,
+};
+
+// TlsOptions
+// This is used by API to identify TLS setting.
+enum TlsOptions {
+ TLS_DISABLED,
+ TLS_ENABLED,
+ TLS_REQUIRED
+};
+
+//! Callback for socket output for an XmppEngine connection.
+//! Register via XmppEngine.SetOutputHandler. An XmppEngine
+//! can call back to this handler while it is processing
+//! Connect, SendStanza, SendIq, Disconnect, or HandleInput.
+class XmppOutputHandler {
+public:
+ virtual ~XmppOutputHandler() {}
+
+ //! Deliver the specified bytes to the XMPP socket.
+ virtual void WriteOutput(const char * bytes, size_t len) = 0;
+
+ //! Initiate TLS encryption on the socket.
+ //! The implementation must verify that the SSL
+ //! certificate matches the given domainname.
+ virtual void StartTls(const std::string & domainname) = 0;
+
+ //! Called when engine wants the connecton closed.
+ virtual void CloseConnection() = 0;
+};
+
+//! Callback to deliver engine state change notifications
+//! to the object managing the engine.
+class XmppSessionHandler {
+public:
+ virtual ~XmppSessionHandler() {}
+ //! Called when engine changes state. Argument is new state.
+ virtual void OnStateChange(int state) = 0;
+};
+
+//! Callback to deliver stanzas to an Xmpp application module.
+//! Register via XmppEngine.SetDefaultSessionHandler or via
+//! XmppEngine.AddSessionHAndler.
+class XmppStanzaHandler {
+public:
+ virtual ~XmppStanzaHandler() {}
+ //! Process the given stanza.
+ //! The handler must return true if it has handled the stanza.
+ //! A false return value causes the stanza to be passed on to
+ //! the next registered handler.
+ virtual bool HandleStanza(const XmlElement * stanza) = 0;
+};
+
+//! Callback to deliver iq responses (results and errors).
+//! Register while sending an iq via XmppEngine.SendIq.
+//! Iq responses are routed to matching XmppIqHandlers in preference
+//! to sending to any registered SessionHandlers.
+class XmppIqHandler {
+public:
+ virtual ~XmppIqHandler() {}
+ //! Called to handle the iq response.
+ //! The response may be either a result or an error, and will have
+ //! an 'id' that matches the request and a 'from' that matches the
+ //! 'to' of the request. Called no more than once; once this is
+ //! called, the handler is automatically unregistered.
+ virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0;
+};
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput. Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngine {
+public:
+ static XmppEngine * Create();
+ virtual ~XmppEngine() {}
+
+ //! Error codes. See GetError().
+ enum Error {
+ ERROR_NONE = 0, //!< No error
+ ERROR_XML, //!< Malformed XML or encoding error
+ ERROR_STREAM, //!< XMPP stream error - see GetStreamError()
+ ERROR_VERSION, //!< XMPP version error
+ ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials)
+ ERROR_TLS, //!< TLS could not be negotiated
+ ERROR_AUTH, //!< Authentication could not be negotiated
+ ERROR_BIND, //!< Resource or session binding could not be negotiated
+ ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler.
+ ERROR_DOCUMENT_CLOSED, //!< Closed by </stream:stream>
+ ERROR_SOCKET, //!< Socket error
+ ERROR_NETWORK_TIMEOUT, //!< Some sort of timeout (eg., we never got the roster)
+ ERROR_MISSING_USERNAME //!< User has a Google Account but no nickname
+ };
+
+ //! States. See GetState().
+ enum State {
+ STATE_NONE = 0, //!< Nonexistent state
+ STATE_START, //!< Initial state.
+ STATE_OPENING, //!< Exchanging stream headers, authenticating and so on.
+ STATE_OPEN, //!< Authenticated and bound.
+ STATE_CLOSED, //!< Session closed, possibly due to error.
+ };
+
+ // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+ //! Registers the handler for socket output
+ virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0;
+
+ //! Provides socket input to the engine
+ virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0;
+
+ //! Advises the engine that the socket has closed
+ virtual XmppReturnStatus ConnectionClosed(int subcode) = 0;
+
+ // SESSION SETUP ---------------------------------------------------------
+
+ //! Indicates the (bare) JID for the user to use.
+ virtual XmppReturnStatus SetUser(const Jid & jid)= 0;
+
+ //! Get the login (bare) JID.
+ virtual const Jid & GetUser() = 0;
+
+ //! Provides different methods for credentials for login.
+ //! Takes ownership of this object; deletes when login is done
+ virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
+
+ //! Sets whether TLS will be used within the connection (default true).
+ virtual XmppReturnStatus SetTls(TlsOptions useTls) = 0;
+
+ //! Sets an alternate domain from which we allows TLS certificates.
+ //! This is for use in the case where a we want to allow a proxy to
+ //! serve up its own certificate rather than one owned by the underlying
+ //! domain.
+ virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname,
+ const std::string & proxy_domain) = 0;
+
+ //! Gets whether TLS will be used within the connection.
+ virtual TlsOptions GetTls() = 0;
+
+ //! Sets the request resource name, if any (optional).
+ //! Note that the resource name may be overridden by the server; after
+ //! binding, the actual resource name is available as part of FullJid().
+ virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0;
+
+ //! Gets the request resource name.
+ virtual const std::string & GetRequestedResource() = 0;
+
+ //! Sets language
+ virtual void SetLanguage(const std::string & lang) = 0;
+
+ // SESSION MANAGEMENT ---------------------------------------------------
+
+ //! Set callback for state changes.
+ virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0;
+
+ //! Initiates the XMPP connection.
+ //! After supplying connection settings, call this once to initiate,
+ //! (optionally) encrypt, authenticate, and bind the connection.
+ virtual XmppReturnStatus Connect() = 0;
+
+ //! The current engine state.
+ virtual State GetState() = 0;
+
+ //! Returns true if the connection is encrypted (under TLS)
+ virtual bool IsEncrypted() = 0;
+
+ //! The error code.
+ //! Consult this after XmppOutputHandler.OnClose().
+ virtual Error GetError(int *subcode) = 0;
+
+ //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+ //! Notice the stanza returned is owned by the XmppEngine and
+ //! is deleted when the engine is destroyed.
+ virtual const XmlElement * GetStreamError() = 0;
+
+ //! Closes down the connection.
+ //! Sends CloseConnection to output, and disconnects and registered
+ //! session handlers. After Disconnect completes, it is guaranteed
+ //! that no further callbacks will be made.
+ virtual XmppReturnStatus Disconnect() = 0;
+
+ // APPLICATION USE -------------------------------------------------------
+
+ enum HandlerLevel {
+ HL_NONE = 0,
+ HL_PEEK, //!< Sees messages before all other processing; cannot abort
+ HL_SINGLE, //!< Watches for a single message, e.g., by id and sender
+ HL_SENDER, //!< Watches for a type of message from a specific sender
+ HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs
+ HL_ALL, //!< Watches all messages - gets last shot
+ HL_COUNT, //!< Count of handler levels
+ };
+
+ //! Adds a listener for session events.
+ //! Stanza delivery is chained to session handlers; the first to
+ //! return 'true' is the last to get each stanza.
+ virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0;
+
+ //! Removes a listener for session events.
+ virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0;
+
+ //! Sends a stanza to the server.
+ virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0;
+
+ //! Sends raw text to the server
+ virtual XmppReturnStatus SendRaw(const std::string & text) = 0;
+
+ //! Sends an iq to the server, and registers a callback for the result.
+ //! Returns the cookie passed to the result handler.
+ virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+ XmppIqHandler* iq_handler,
+ XmppIqCookie* cookie) = 0;
+
+ //! Unregisters an iq callback handler given its cookie.
+ //! No callback will come to this handler after it's unregistered.
+ virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler** iq_handler) = 0;
+
+
+ //! Forms and sends an error in response to the given stanza.
+ //! Swaps to and from, sets type to "error", and adds error information
+ //! based on the passed code. Text is optional and may be STR_EMPTY.
+ virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text) = 0;
+
+ //! The fullly bound JID.
+ //! This JID is only valid after binding has succeeded. If the value
+ //! is JID_NULL, the binding has not succeeded.
+ virtual const Jid & FullJid() = 0;
+
+ //! The next unused iq id for this connection.
+ //! Call this when building iq stanzas, to ensure that each iq
+ //! gets its own unique id.
+ virtual std::string NextId() = 0;
+
+};
+
+}
+
+
+// Move these to a better location
+
+#define XMPP_FAILED(x) \
+ ( (x) == buzz::XMPP_RETURN_OK ? false : true) \
+
+
+#define XMPP_SUCCEEDED(x) \
+ ( (x) == buzz::XMPP_RETURN_OK ? true : false) \
+
+#define IFR(x) \
+ do { \
+ xmpp_status = (x); \
+ if (XMPP_FAILED(xmpp_status)) { \
+ return xmpp_status; \
+ } \
+ } while (false) \
+
+
+#define IFC(x) \
+ do { \
+ xmpp_status = (x); \
+ if (XMPP_FAILED(xmpp_status)) { \
+ goto Cleanup; \
+ } \
+ } while (false) \
+
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPENGINE_H_
diff --git a/webrtc/libjingle/xmpp/xmppengine_unittest.cc b/webrtc/libjingle/xmpp/xmppengine_unittest.cc
new file mode 100644
index 0000000000..7af151ba48
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppengine_unittest.cc
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/plainsaslhandler.h"
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h"
+#include "webrtc/libjingle/xmpp/util_unittest.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/gunit.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppIqCookie;
+using buzz::XmppIqHandler;
+using buzz::XmppTestHandler;
+using buzz::QN_ID;
+using buzz::QN_IQ;
+using buzz::QN_TYPE;
+using buzz::QN_ROSTER_QUERY;
+using buzz::XMPP_RETURN_OK;
+using buzz::XMPP_RETURN_BADARGUMENT;
+
+// XmppEngineTestIqHandler
+// This class grabs the response to an IQ stanza and stores it in a string.
+class XmppEngineTestIqHandler : public XmppIqHandler {
+ public:
+ virtual void IqResponse(XmppIqCookie, const XmlElement * stanza) {
+ ss_ << stanza->Str();
+ }
+
+ std::string IqResponseActivity() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+ private:
+ std::stringstream ss_;
+};
+
+class XmppEngineTest : public testing::Test {
+ public:
+ XmppEngine* engine() { return engine_.get(); }
+ XmppTestHandler* handler() { return handler_.get(); }
+ virtual void SetUp() {
+ engine_.reset(XmppEngine::Create());
+ handler_.reset(new XmppTestHandler(engine_.get()));
+
+ Jid jid("david@my-server");
+ rtc::InsecureCryptStringImpl pass;
+ pass.password() = "david";
+ engine_->SetSessionHandler(handler_.get());
+ engine_->SetOutputHandler(handler_.get());
+ engine_->AddStanzaHandler(handler_.get());
+ engine_->SetUser(jid);
+ engine_->SetSaslHandler(
+ new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true));
+ }
+ virtual void TearDown() {
+ handler_.reset();
+ engine_.reset();
+ }
+ void RunLogin();
+
+ private:
+ rtc::scoped_ptr<XmppEngine> engine_;
+ rtc::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppEngineTest::RunLogin() {
+ // Connect
+ EXPECT_EQ(XmppEngine::STATE_START, engine()->GetState());
+ engine()->Connect();
+ EXPECT_EQ(XmppEngine::STATE_OPENING, engine()->GetState());
+
+ EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ std::string input =
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input =
+ "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+ "<required/>"
+ "</starttls>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[START-TLS my-server]"
+ "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input =
+ "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" "
+ "auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+ "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>"
+ "david@my-server/test</jid></bind></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+ "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+ handler_->OutputActivity());
+
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+
+ input = "<iq type='result' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ(Jid("david@my-server/test"), engine()->FullJid());
+}
+
+// TestSuccessfulLogin()
+// This function simply tests to see if a login works. This includes
+// encryption and authentication
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndDisconnect) {
+ RunLogin();
+ engine()->Disconnect();
+ EXPECT_EQ("</stream:stream>[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppEngineTest, TestSuccessfulLoginAndConnectionClosed) {
+ RunLogin();
+ engine()->ConnectionClosed(0);
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-CONNECTION-CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+
+// TestNotXmpp()
+// This tests the error case when connecting to a non XMPP service
+TEST_F(XmppEngineTest, TestNotXmpp) {
+ // Connect
+ engine()->Connect();
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler()->OutputActivity());
+
+ // Send garbage response (courtesy of apache)
+ std::string input = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[OPENING][CLOSED][ERROR-XML]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestPassthrough()
+// This tests that arbitrary stanzas can be passed to the server through
+// the engine.
+TEST_F(XmppEngineTest, TestPassthrough) {
+ // Queue up an app stanza
+ XmlElement application_stanza(QName("test", "app-stanza"));
+ application_stanza.AddText("this-is-a-test");
+ engine()->SendStanza(&application_stanza);
+
+ // Do the whole login handshake
+ RunLogin();
+
+ EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+ "</test:app-stanza>", handler()->OutputActivity());
+
+ // do another stanza
+ XmlElement roster_get(QN_IQ);
+ roster_get.AddAttr(QN_TYPE, "get");
+ roster_get.AddAttr(QN_ID, engine()->NextId());
+ roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ engine()->SendStanza(&roster_get);
+ EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+
+ // now say the server ends the stream
+ engine()->HandleInput("</stream:stream>", 16);
+ EXPECT_EQ("[CLOSED][ERROR-DOCUMENT-CLOSED]", handler()->SessionActivity());
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+// TestIqCallback()
+// This tests the routing of Iq stanzas and responses.
+TEST_F(XmppEngineTest, TestIqCallback) {
+ XmppEngineTestIqHandler iq_response;
+ XmppIqCookie cookie;
+
+ // Do the whole login handshake
+ RunLogin();
+
+ // Build an iq request
+ XmlElement roster_get(QN_IQ);
+ roster_get.AddAttr(QN_TYPE, "get");
+ roster_get.AddAttr(QN_ID, engine()->NextId());
+ roster_get.AddElement(new XmlElement(QN_ROSTER_QUERY, true));
+ engine()->SendIq(&roster_get, &iq_response, &cookie);
+ EXPECT_EQ("<iq type=\"get\" id=\"2\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+
+ // now say the server responds to the iq
+ std::string input = "<iq type='result' id='2'>"
+ "<query xmlns='jabber:iq:roster'><item>foo</item>"
+ "</query></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("<cli:iq type=\"result\" id=\"2\" xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:roster\"><item>foo</item></query>"
+ "</cli:iq>", iq_response.IqResponseActivity());
+
+ EXPECT_EQ(XMPP_RETURN_BADARGUMENT, engine()->RemoveIqHandler(cookie, NULL));
+
+ // Do it again with another id to test cancel
+ roster_get.SetAttr(QN_ID, engine()->NextId());
+ engine()->SendIq(&roster_get, &iq_response, &cookie);
+ EXPECT_EQ("<iq type=\"get\" id=\"3\"><query xmlns=\"jabber:iq:roster\"/>"
+ "</iq>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+
+ // cancel the handler this time
+ EXPECT_EQ(XMPP_RETURN_OK, engine()->RemoveIqHandler(cookie, NULL));
+
+ // now say the server responds to the iq: the iq handler should not get it.
+ input = "<iq type='result' id='3'><query xmlns='jabber:iq:roster'><item>bar"
+ "</item></query></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<cli:iq type=\"result\" id=\"3\" xmlns:cli=\"jabber:client\">"
+ "<query xmlns=\"jabber:iq:roster\"><item>bar</item></query>"
+ "</cli:iq>", handler()->StanzaActivity());
+ EXPECT_EQ("", iq_response.IqResponseActivity());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+}
diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.cc b/webrtc/libjingle/xmpp/xmppengineimpl.cc
new file mode 100644
index 0000000000..e77a1d9349
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppengineimpl.cc
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmllite/xmlprinter.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/saslhandler.h"
+#include "webrtc/libjingle/xmpp/xmpplogintask.h"
+#include "webrtc/base/common.h"
+
+namespace buzz {
+
+XmppEngine* XmppEngine::Create() {
+ return new XmppEngineImpl();
+}
+
+
+XmppEngineImpl::XmppEngineImpl()
+ : stanza_parse_handler_(this),
+ stanza_parser_(&stanza_parse_handler_),
+ engine_entered_(0),
+ password_(),
+ requested_resource_(STR_EMPTY),
+ tls_option_(buzz::TLS_REQUIRED),
+ login_task_(new XmppLoginTask(this)),
+ next_id_(0),
+ state_(STATE_START),
+ encrypted_(false),
+ error_code_(ERROR_NONE),
+ subcode_(0),
+ stream_error_(),
+ raised_reset_(false),
+ output_handler_(NULL),
+ session_handler_(NULL),
+ iq_entries_(new IqEntryVector()),
+ sasl_handler_(),
+ output_(new std::stringstream()) {
+ for (int i = 0; i < HL_COUNT; i+= 1) {
+ stanza_handlers_[i].reset(new StanzaHandlerVector());
+ }
+
+ // Add XMPP namespaces to XML namespaces stack.
+ xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
+ xmlns_stack_.AddXmlns("", "jabber:client");
+}
+
+XmppEngineImpl::~XmppEngineImpl() {
+ DeleteIqCookies();
+}
+
+XmppReturnStatus XmppEngineImpl::SetOutputHandler(
+ XmppOutputHandler* output_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ output_handler_ = output_handler;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetSessionHandler(
+ XmppSessionHandler* session_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ session_handler_ = session_handler;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::HandleInput(
+ const char* bytes, size_t len) {
+ if (state_ < STATE_OPENING || state_ > STATE_OPEN)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ // TODO: The return value of the xml parser is not checked.
+ stanza_parser_.Parse(bytes, len, false);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
+ if (state_ != STATE_CLOSED) {
+ EnterExit ee(this);
+ // If told that connection closed and not already closed,
+ // then connection was unpexectedly dropped.
+ if (subcode) {
+ SignalError(ERROR_SOCKET, subcode);
+ } else {
+ SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode
+ }
+ }
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+ tls_option_ = use_tls;
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetTlsServer(
+ const std::string& tls_server_hostname,
+ const std::string& tls_server_domain) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ tls_server_hostname_ = tls_server_hostname;
+ tls_server_domain_= tls_server_domain;
+
+ return XMPP_RETURN_OK;
+}
+
+TlsOptions XmppEngineImpl::GetTls() {
+ return tls_option_;
+}
+
+XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ user_jid_ = jid;
+
+ return XMPP_RETURN_OK;
+}
+
+const Jid& XmppEngineImpl::GetUser() {
+ return user_jid_;
+}
+
+XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ sasl_handler_.reset(sasl_handler);
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SetRequestedResource(
+ const std::string& resource) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ requested_resource_ = resource;
+
+ return XMPP_RETURN_OK;
+}
+
+const std::string& XmppEngineImpl::GetRequestedResource() {
+ return requested_resource_;
+}
+
+XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
+ XmppStanzaHandler* stanza_handler,
+ XmppEngine::HandlerLevel level) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ stanza_handlers_[level]->push_back(stanza_handler);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
+ XmppStanzaHandler* stanza_handler) {
+ bool found = false;
+
+ for (int level = 0; level < HL_COUNT; level += 1) {
+ StanzaHandlerVector::iterator new_end =
+ std::remove(stanza_handlers_[level]->begin(),
+ stanza_handlers_[level]->end(),
+ stanza_handler);
+
+ if (new_end != stanza_handlers_[level]->end()) {
+ stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
+ found = true;
+ }
+ }
+
+ if (!found)
+ return XMPP_RETURN_BADARGUMENT;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::Connect() {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ // get the login task started
+ state_ = STATE_OPENING;
+ if (login_task_) {
+ login_task_->IncomingStanza(NULL, false);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ if (login_task_) {
+ // still handshaking - then outbound stanzas are queued
+ login_task_->OutgoingStanza(element);
+ } else {
+ // handshake done - send straight through
+ InternalSendStanza(element);
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
+ if (state_ == STATE_CLOSED || login_task_)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ (*output_) << text;
+
+ return XMPP_RETURN_OK;
+}
+
+std::string XmppEngineImpl::NextId() {
+ std::stringstream ss;
+ ss << next_id_++;
+ return ss.str();
+}
+
+XmppReturnStatus XmppEngineImpl::Disconnect() {
+ if (state_ != STATE_CLOSED) {
+ EnterExit ee(this);
+ if (state_ == STATE_OPEN)
+ *output_ << "</stream:stream>";
+ state_ = STATE_CLOSED;
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+void XmppEngineImpl::IncomingStart(const XmlElement* start) {
+ if (HasError() || raised_reset_)
+ return;
+
+ if (login_task_) {
+ // start-stream should go to login task
+ login_task_->IncomingStanza(start, true);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ }
+ else {
+ // if not logging in, it's an error to see a start
+ SignalError(ERROR_XML, 0);
+ }
+}
+
+void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
+ if (HasError() || raised_reset_)
+ return;
+
+ if (stanza->Name() == QN_STREAM_ERROR) {
+ // Explicit XMPP stream error
+ SignalStreamError(stanza);
+ } else if (login_task_) {
+ // Handle login handshake
+ login_task_->IncomingStanza(stanza, false);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ } else if (HandleIqResponse(stanza)) {
+ // iq is handled by above call
+ } else {
+ // give every "peek" handler a shot at all stanzas
+ for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
+ (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
+ }
+
+ // give other handlers a shot in precedence order, stopping after handled
+ for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
+ for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
+ if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
+ return;
+ }
+ }
+
+ // If nobody wants to handle a stanza then send back an error.
+ // Only do this for IQ stanzas as messages should probably just be dropped
+ // and presence stanzas should certainly be dropped.
+ std::string type = stanza->Attr(QN_TYPE);
+ if (stanza->Name() == QN_IQ &&
+ !(type == "error" || type == "result")) {
+ SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
+ }
+ }
+}
+
+void XmppEngineImpl::IncomingEnd(bool isError) {
+ if (HasError() || raised_reset_)
+ return;
+
+ SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
+}
+
+void XmppEngineImpl::InternalSendStart(const std::string& to) {
+ std::string hostname = tls_server_hostname_;
+ if (hostname.empty())
+ hostname = to;
+
+ // If not language is specified, the spec says use *
+ std::string lang = lang_;
+ if (lang.length() == 0)
+ lang = "*";
+
+ // send stream-beginning
+ // note, we put a \r\n at tne end fo the first line to cause non-XMPP
+ // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
+ *output_ << "<stream:stream to=\"" << hostname << "\" "
+ << "xml:lang=\"" << lang << "\" "
+ << "version=\"1.0\" "
+ << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ << "xmlns=\"jabber:client\">\r\n";
+}
+
+void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
+ // It should really never be necessary to set a FROM attribute on a stanza.
+ // It is implied by the bind on the stream and if you get it wrong
+ // (by flipping from/to on a message?) the server will close the stream.
+ ASSERT(!element->HasAttr(QN_FROM));
+
+ XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
+}
+
+std::string XmppEngineImpl::ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms, bool encrypted) {
+ return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
+}
+
+SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
+ return sasl_handler_->CreateSaslMechanism(name);
+}
+
+void XmppEngineImpl::SignalBound(const Jid& fullJid) {
+ if (state_ == STATE_OPENING) {
+ bound_jid_ = fullJid;
+ state_ = STATE_OPEN;
+ }
+}
+
+void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
+ if (state_ != STATE_CLOSED) {
+ stream_error_.reset(new XmlElement(*stream_error));
+ SignalError(ERROR_STREAM, 0);
+ }
+}
+
+void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
+ if (state_ != STATE_CLOSED) {
+ error_code_ = error_code;
+ subcode_ = sub_code;
+ state_ = STATE_CLOSED;
+ }
+}
+
+bool XmppEngineImpl::HasError() {
+ return error_code_ != ERROR_NONE;
+}
+
+void XmppEngineImpl::StartTls(const std::string& domain) {
+ if (output_handler_) {
+ // As substitute for the real (login jid's) domain, we permit
+ // verifying a tls_server_domain_ instead, if one was passed.
+ // This allows us to avoid running a proxy that needs to handle
+ // valuable certificates.
+ output_handler_->StartTls(
+ tls_server_domain_.empty() ? domain : tls_server_domain_);
+ encrypted_ = true;
+ }
+}
+
+XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
+ : engine_(engine),
+ state_(engine->state_) {
+ engine->engine_entered_ += 1;
+}
+
+XmppEngineImpl::EnterExit::~EnterExit() {
+ XmppEngineImpl* engine = engine_;
+
+ engine->engine_entered_ -= 1;
+
+ bool closing = (engine->state_ != state_ &&
+ engine->state_ == STATE_CLOSED);
+ bool flushing = closing || (engine->engine_entered_ == 0);
+
+ if (engine->output_handler_ && flushing) {
+ std::string output = engine->output_->str();
+ if (output.length() > 0)
+ engine->output_handler_->WriteOutput(output.c_str(), output.length());
+ engine->output_->str("");
+
+ if (closing) {
+ engine->output_handler_->CloseConnection();
+ engine->output_handler_ = 0;
+ }
+ }
+
+ if (engine->engine_entered_)
+ return;
+
+ if (engine->raised_reset_) {
+ engine->stanza_parser_.Reset();
+ engine->raised_reset_ = false;
+ }
+
+ if (engine->session_handler_) {
+ if (engine->state_ != state_)
+ engine->session_handler_->OnStateChange(engine->state_);
+ // Note: Handling of OnStateChange(CLOSED) should allow for the
+ // deletion of the engine, so no members should be accessed
+ // after this line.
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.h b/webrtc/libjingle/xmpp/xmppengineimpl.h
new file mode 100644
index 0000000000..c322596c46
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppengineimpl.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_
+
+#include <sstream>
+#include <vector>
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmppstanzaparser.h"
+
+namespace buzz {
+
+class XmppLoginTask;
+class XmppEngine;
+class XmppIqEntry;
+class SaslHandler;
+class SaslMechanism;
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput. Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngineImpl : public XmppEngine {
+ public:
+ XmppEngineImpl();
+ virtual ~XmppEngineImpl();
+
+ // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+ //! Registers the handler for socket output
+ virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
+
+ //! Provides socket input to the engine
+ virtual XmppReturnStatus HandleInput(const char* bytes, size_t len);
+
+ //! Advises the engine that the socket has closed
+ virtual XmppReturnStatus ConnectionClosed(int subcode);
+
+ // SESSION SETUP ---------------------------------------------------------
+
+ //! Indicates the (bare) JID for the user to use.
+ virtual XmppReturnStatus SetUser(const Jid& jid);
+
+ //! Get the login (bare) JID.
+ virtual const Jid& GetUser();
+
+ //! Indicates the autentication to use. Takes ownership of the object.
+ virtual XmppReturnStatus SetSaslHandler(SaslHandler* sasl_handler);
+
+ //! Sets whether TLS will be used within the connection (default true).
+ virtual XmppReturnStatus SetTls(TlsOptions use_tls);
+
+ //! Sets an alternate domain from which we allows TLS certificates.
+ //! This is for use in the case where a we want to allow a proxy to
+ //! serve up its own certificate rather than one owned by the underlying
+ //! domain.
+ virtual XmppReturnStatus SetTlsServer(const std::string& proxy_hostname,
+ const std::string& proxy_domain);
+
+ //! Gets whether TLS will be used within the connection.
+ virtual TlsOptions GetTls();
+
+ //! Sets the request resource name, if any (optional).
+ //! Note that the resource name may be overridden by the server; after
+ //! binding, the actual resource name is available as part of FullJid().
+ virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
+
+ //! Gets the request resource name.
+ virtual const std::string& GetRequestedResource();
+
+ //! Sets language
+ virtual void SetLanguage(const std::string& lang) {
+ lang_ = lang;
+ }
+
+ // SESSION MANAGEMENT ---------------------------------------------------
+
+ //! Set callback for state changes.
+ virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler);
+
+ //! Initiates the XMPP connection.
+ //! After supplying connection settings, call this once to initiate,
+ //! (optionally) encrypt, authenticate, and bind the connection.
+ virtual XmppReturnStatus Connect();
+
+ //! The current engine state.
+ virtual State GetState() { return state_; }
+
+ //! Returns true if the connection is encrypted (under TLS)
+ virtual bool IsEncrypted() { return encrypted_; }
+
+ //! The error code.
+ //! Consult this after XmppOutputHandler.OnClose().
+ virtual Error GetError(int *subcode) {
+ if (subcode) {
+ *subcode = subcode_;
+ }
+ return error_code_;
+ }
+
+ //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+ //! Notice the stanza returned is owned by the XmppEngine and
+ //! is deleted when the engine is destroyed.
+ virtual const XmlElement* GetStreamError() { return stream_error_.get(); }
+
+ //! Closes down the connection.
+ //! Sends CloseConnection to output, and disconnects and registered
+ //! session handlers. After Disconnect completes, it is guaranteed
+ //! that no further callbacks will be made.
+ virtual XmppReturnStatus Disconnect();
+
+ // APPLICATION USE -------------------------------------------------------
+
+ //! Adds a listener for session events.
+ //! Stanza delivery is chained to session handlers; the first to
+ //! return 'true' is the last to get each stanza.
+ virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler,
+ XmppEngine::HandlerLevel level);
+
+ //! Removes a listener for session events.
+ virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
+
+ //! Sends a stanza to the server.
+ virtual XmppReturnStatus SendStanza(const XmlElement* stanza);
+
+ //! Sends raw text to the server
+ virtual XmppReturnStatus SendRaw(const std::string& text);
+
+ //! Sends an iq to the server, and registers a callback for the result.
+ //! Returns the cookie passed to the result handler.
+ virtual XmppReturnStatus SendIq(const XmlElement* stanza,
+ XmppIqHandler* iq_handler,
+ XmppIqCookie* cookie);
+
+ //! Unregisters an iq callback handler given its cookie.
+ //! No callback will come to this handler after it's unregistered.
+ virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler** iq_handler);
+
+ //! Forms and sends an error in response to the given stanza.
+ //! Swaps to and from, sets type to "error", and adds error information
+ //! based on the passed code. Text is optional and may be STR_EMPTY.
+ virtual XmppReturnStatus SendStanzaError(const XmlElement* pelOriginal,
+ XmppStanzaError code,
+ const std::string& text);
+
+ //! The fullly bound JID.
+ //! This JID is only valid after binding has succeeded. If the value
+ //! is JID_NULL, the binding has not succeeded.
+ virtual const Jid& FullJid() { return bound_jid_; }
+
+ //! The next unused iq id for this connection.
+ //! Call this when building iq stanzas, to ensure that each iq
+ //! gets its own unique id.
+ virtual std::string NextId();
+
+ private:
+ friend class XmppLoginTask;
+ friend class XmppIqEntry;
+
+ void IncomingStanza(const XmlElement *stanza);
+ void IncomingStart(const XmlElement *stanza);
+ void IncomingEnd(bool isError);
+
+ void InternalSendStart(const std::string& domainName);
+ void InternalSendStanza(const XmlElement* stanza);
+ std::string ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms, bool encrypted);
+ SaslMechanism* GetSaslMechanism(const std::string& name);
+ void SignalBound(const Jid& fullJid);
+ void SignalStreamError(const XmlElement* streamError);
+ void SignalError(Error errorCode, int subCode);
+ bool HasError();
+ void DeleteIqCookies();
+ bool HandleIqResponse(const XmlElement* element);
+ void StartTls(const std::string& domain);
+ void RaiseReset() { raised_reset_ = true; }
+
+ class StanzaParseHandler : public XmppStanzaParseHandler {
+ public:
+ StanzaParseHandler(XmppEngineImpl* outer) : outer_(outer) {}
+ virtual ~StanzaParseHandler() {}
+
+ virtual void StartStream(const XmlElement* stream) {
+ outer_->IncomingStart(stream);
+ }
+ virtual void Stanza(const XmlElement* stanza) {
+ outer_->IncomingStanza(stanza);
+ }
+ virtual void EndStream() {
+ outer_->IncomingEnd(false);
+ }
+ virtual void XmlError() {
+ outer_->IncomingEnd(true);
+ }
+
+ private:
+ XmppEngineImpl* const outer_;
+ };
+
+ class EnterExit {
+ public:
+ EnterExit(XmppEngineImpl* engine);
+ ~EnterExit();
+ private:
+ XmppEngineImpl* engine_;
+ State state_;
+ };
+
+ friend class StanzaParseHandler;
+ friend class EnterExit;
+
+ StanzaParseHandler stanza_parse_handler_;
+ XmppStanzaParser stanza_parser_;
+
+ // state
+ int engine_entered_;
+ Jid user_jid_;
+ std::string password_;
+ std::string requested_resource_;
+ TlsOptions tls_option_;
+ std::string tls_server_hostname_;
+ std::string tls_server_domain_;
+ rtc::scoped_ptr<XmppLoginTask> login_task_;
+ std::string lang_;
+
+ int next_id_;
+ Jid bound_jid_;
+ State state_;
+ bool encrypted_;
+ Error error_code_;
+ int subcode_;
+ rtc::scoped_ptr<XmlElement> stream_error_;
+ bool raised_reset_;
+ XmppOutputHandler* output_handler_;
+ XmppSessionHandler* session_handler_;
+
+ XmlnsStack xmlns_stack_;
+
+ typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
+ rtc::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
+
+ typedef std::vector<XmppIqEntry*> IqEntryVector;
+ rtc::scoped_ptr<IqEntryVector> iq_entries_;
+
+ rtc::scoped_ptr<SaslHandler> sasl_handler_;
+
+ rtc::scoped_ptr<std::stringstream> output_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPENGINEIMPL_H_
diff --git a/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc
new file mode 100644
index 0000000000..23b9fc43ea
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppengineimpl_iq.cc
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <algorithm>
+#include <vector>
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
+#include "webrtc/base/common.h"
+
+namespace buzz {
+
+class XmppIqEntry {
+ XmppIqEntry(const std::string & id, const std::string & to,
+ XmppEngine * pxce, XmppIqHandler * iq_handler) :
+ id_(id),
+ to_(to),
+ engine_(pxce),
+ iq_handler_(iq_handler) {
+ }
+
+private:
+ friend class XmppEngineImpl;
+
+ const std::string id_;
+ const std::string to_;
+ XmppEngine * const engine_;
+ XmppIqHandler * const iq_handler_;
+};
+
+
+XmppReturnStatus
+XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler,
+ XmppIqCookie* cookie) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+ if (NULL == iq_handler)
+ return XMPP_RETURN_BADARGUMENT;
+ if (!element || element->Name() != QN_IQ)
+ return XMPP_RETURN_BADARGUMENT;
+
+ const std::string& type = element->Attr(QN_TYPE);
+ if (type != "get" && type != "set")
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!element->HasAttr(QN_ID))
+ return XMPP_RETURN_BADARGUMENT;
+ const std::string& id = element->Attr(QN_ID);
+
+ XmppIqEntry * iq_entry = new XmppIqEntry(id,
+ element->Attr(QN_TO),
+ this, iq_handler);
+ iq_entries_->push_back(iq_entry);
+ SendStanza(element);
+
+ if (cookie)
+ *cookie = iq_entry;
+
+ return XMPP_RETURN_OK;
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler ** iq_handler) {
+
+ std::vector<XmppIqEntry*, std::allocator<XmppIqEntry*> >::iterator pos;
+
+ pos = std::find(iq_entries_->begin(),
+ iq_entries_->end(),
+ reinterpret_cast<XmppIqEntry*>(cookie));
+
+ if (pos == iq_entries_->end())
+ return XMPP_RETURN_BADARGUMENT;
+
+ XmppIqEntry* entry = *pos;
+ iq_entries_->erase(pos);
+ if (iq_handler)
+ *iq_handler = entry->iq_handler_;
+ delete entry;
+
+ return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::DeleteIqCookies() {
+ for (size_t i = 0; i < iq_entries_->size(); i += 1) {
+ XmppIqEntry * iq_entry_ = (*iq_entries_)[i];
+ (*iq_entries_)[i] = NULL;
+ delete iq_entry_;
+ }
+ iq_entries_->clear();
+}
+
+static void
+AecImpl(XmlElement * error_element, const QName & name,
+ const char * type, const char * code) {
+ error_element->AddElement(new XmlElement(QN_ERROR));
+ error_element->AddAttr(QN_CODE, code, 1);
+ error_element->AddAttr(QN_TYPE, type, 1);
+ error_element->AddElement(new XmlElement(name, true), 1);
+}
+
+
+static void
+AddErrorCode(XmlElement * error_element, XmppStanzaError code) {
+ switch (code) {
+ case XSE_BAD_REQUEST:
+ AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400");
+ break;
+ case XSE_CONFLICT:
+ AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409");
+ break;
+ case XSE_FEATURE_NOT_IMPLEMENTED:
+ AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED,
+ "cancel", "501");
+ break;
+ case XSE_FORBIDDEN:
+ AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403");
+ break;
+ case XSE_GONE:
+ AecImpl(error_element, QN_STANZA_GONE, "modify", "302");
+ break;
+ case XSE_INTERNAL_SERVER_ERROR:
+ AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500");
+ break;
+ case XSE_ITEM_NOT_FOUND:
+ AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404");
+ break;
+ case XSE_JID_MALFORMED:
+ AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400");
+ break;
+ case XSE_NOT_ACCEPTABLE:
+ AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406");
+ break;
+ case XSE_NOT_ALLOWED:
+ AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405");
+ break;
+ case XSE_PAYMENT_REQUIRED:
+ AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402");
+ break;
+ case XSE_RECIPIENT_UNAVAILABLE:
+ AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404");
+ break;
+ case XSE_REDIRECT:
+ AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302");
+ break;
+ case XSE_REGISTRATION_REQUIRED:
+ AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407");
+ break;
+ case XSE_SERVER_NOT_FOUND:
+ AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND,
+ "cancel", "404");
+ break;
+ case XSE_SERVER_TIMEOUT:
+ AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502");
+ break;
+ case XSE_RESOURCE_CONSTRAINT:
+ AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500");
+ break;
+ case XSE_SERVICE_UNAVAILABLE:
+ AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503");
+ break;
+ case XSE_SUBSCRIPTION_REQUIRED:
+ AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407");
+ break;
+ case XSE_UNDEFINED_CONDITION:
+ AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500");
+ break;
+ case XSE_UNEXPECTED_REQUEST:
+ AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400");
+ break;
+ }
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::SendStanzaError(const XmlElement * element_original,
+ XmppStanzaError code,
+ const std::string & text) {
+
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement error_element(element_original->Name());
+ error_element.AddAttr(QN_TYPE, "error");
+
+ // copy attrs, copy 'from' to 'to' and strip 'from'
+ for (const XmlAttr * attribute = element_original->FirstAttr();
+ attribute; attribute = attribute->NextAttr()) {
+ QName name = attribute->Name();
+ if (name == QN_TO)
+ continue; // no need to put a from attr. Server will stamp stanza
+ else if (name == QN_FROM)
+ name = QN_TO;
+ else if (name == QN_TYPE)
+ continue;
+ error_element.AddAttr(name, attribute->Value());
+ }
+
+ // copy children
+ for (const XmlChild * child = element_original->FirstChild();
+ child;
+ child = child->NextChild()) {
+ if (child->IsText()) {
+ error_element.AddText(child->AsText()->Text());
+ } else {
+ error_element.AddElement(new XmlElement(*(child->AsElement())));
+ }
+ }
+
+ // add error information
+ AddErrorCode(&error_element, code);
+ if (text != STR_EMPTY) {
+ XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true);
+ text_element->AddText(text);
+ error_element.AddElement(text_element);
+ }
+
+ SendStanza(&error_element);
+
+ return XMPP_RETURN_OK;
+}
+
+
+bool
+XmppEngineImpl::HandleIqResponse(const XmlElement * element) {
+ if (iq_entries_->empty())
+ return false;
+ if (element->Name() != QN_IQ)
+ return false;
+ std::string type = element->Attr(QN_TYPE);
+ if (type != "result" && type != "error")
+ return false;
+ if (!element->HasAttr(QN_ID))
+ return false;
+ std::string id = element->Attr(QN_ID);
+ std::string from = element->Attr(QN_FROM);
+
+ for (std::vector<XmppIqEntry *>::iterator it = iq_entries_->begin();
+ it != iq_entries_->end(); it += 1) {
+ XmppIqEntry * iq_entry = *it;
+ if (iq_entry->id_ == id && iq_entry->to_ == from) {
+ iq_entries_->erase(it);
+ iq_entry->iq_handler_->IqResponse(iq_entry, element);
+ delete iq_entry;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/xmpplogintask.cc b/webrtc/libjingle/xmpp/xmpplogintask.cc
new file mode 100644
index 0000000000..f5745cd979
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpplogintask.cc
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmpplogintask.h"
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/saslmechanism.h"
+#include "webrtc/libjingle/xmpp/xmppengineimpl.h"
+#include "webrtc/base/base64.h"
+#include "webrtc/base/common.h"
+
+using rtc::ConstantLabel;
+
+namespace buzz {
+
+#ifdef _DEBUG
+const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
+ KLABEL(LOGINSTATE_INIT),
+ KLABEL(LOGINSTATE_STREAMSTART_SENT),
+ KLABEL(LOGINSTATE_STARTED_XMPP),
+ KLABEL(LOGINSTATE_TLS_INIT),
+ KLABEL(LOGINSTATE_AUTH_INIT),
+ KLABEL(LOGINSTATE_BIND_INIT),
+ KLABEL(LOGINSTATE_TLS_REQUESTED),
+ KLABEL(LOGINSTATE_SASL_RUNNING),
+ KLABEL(LOGINSTATE_BIND_REQUESTED),
+ KLABEL(LOGINSTATE_SESSION_REQUESTED),
+ KLABEL(LOGINSTATE_DONE),
+ LASTLABEL
+};
+#endif // _DEBUG
+XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
+ pctx_(pctx),
+ authNeeded_(true),
+ allowNonGoogleLogin_(true),
+ state_(LOGINSTATE_INIT),
+ pelStanza_(NULL),
+ isStart_(false),
+ iqId_(STR_EMPTY),
+ pelFeatures_(),
+ fullJid_(STR_EMPTY),
+ streamId_(STR_EMPTY),
+ pvecQueuedStanzas_(new std::vector<XmlElement *>()),
+ sasl_mech_() {
+}
+
+XmppLoginTask::~XmppLoginTask() {
+ for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
+ delete (*pvecQueuedStanzas_)[i];
+}
+
+void
+XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
+ pelStanza_ = element;
+ isStart_ = isStart;
+ Advance();
+ pelStanza_ = NULL;
+ isStart_ = false;
+}
+
+const XmlElement *
+XmppLoginTask::NextStanza() {
+ const XmlElement * result = pelStanza_;
+ pelStanza_ = NULL;
+ return result;
+}
+
+bool
+XmppLoginTask::Advance() {
+
+ for (;;) {
+
+ const XmlElement * element = NULL;
+
+#if _DEBUG
+ LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
+ << rtc::ErrorName(state_, LOGINTASK_STATES);
+#endif // _DEBUG
+
+ switch (state_) {
+
+ case LOGINSTATE_INIT: {
+ pctx_->RaiseReset();
+ pelFeatures_.reset(NULL);
+
+ // The proper domain to verify against is the real underlying
+ // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
+ // also allows matching against a proxy domain instead, if it is told
+ // to do so - see the implementation of XmppEngineImpl::StartTls and
+ // XmppEngine::SetTlsServerDomain to see how you can use that feature
+ pctx_->InternalSendStart(pctx_->user_jid_.domain());
+ state_ = LOGINSTATE_STREAMSTART_SENT;
+ break;
+ }
+
+ case LOGINSTATE_STREAMSTART_SENT: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (!isStart_ || !HandleStartStream(element))
+ return Failure(XmppEngine::ERROR_VERSION);
+
+ state_ = LOGINSTATE_STARTED_XMPP;
+ return true;
+ }
+
+ case LOGINSTATE_STARTED_XMPP: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (!HandleFeatures(element))
+ return Failure(XmppEngine::ERROR_VERSION);
+
+ bool tls_present = (GetFeature(QN_TLS_STARTTLS) != NULL);
+ // Error if TLS required but not present.
+ if (pctx_->tls_option_ == buzz::TLS_REQUIRED && !tls_present) {
+ return Failure(XmppEngine::ERROR_TLS);
+ }
+ // Use TLS if required or enabled, and also available
+ if ((pctx_->tls_option_ == buzz::TLS_REQUIRED ||
+ pctx_->tls_option_ == buzz::TLS_ENABLED) && tls_present) {
+ state_ = LOGINSTATE_TLS_INIT;
+ continue;
+ }
+
+ if (authNeeded_) {
+ state_ = LOGINSTATE_AUTH_INIT;
+ continue;
+ }
+
+ state_ = LOGINSTATE_BIND_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_TLS_INIT: {
+ const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
+ if (!pelTls)
+ return Failure(XmppEngine::ERROR_TLS);
+
+ XmlElement el(QN_TLS_STARTTLS, true);
+ pctx_->InternalSendStanza(&el);
+ state_ = LOGINSTATE_TLS_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_TLS_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name() != QN_TLS_PROCEED)
+ return Failure(XmppEngine::ERROR_TLS);
+
+ // The proper domain to verify against is the real underlying
+ // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
+ // also allows matching against a proxy domain instead, if it is told
+ // to do so - see the implementation of XmppEngineImpl::StartTls and
+ // XmppEngine::SetTlsServerDomain to see how you can use that feature
+ pctx_->StartTls(pctx_->user_jid_.domain());
+ pctx_->tls_option_ = buzz::TLS_ENABLED;
+ state_ = LOGINSTATE_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_AUTH_INIT: {
+ const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
+ if (!pelSaslAuth) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // Collect together the SASL auth mechanisms presented by the server
+ std::vector<std::string> mechanisms;
+ for (const XmlElement * pelMech =
+ pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
+ pelMech;
+ pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
+
+ mechanisms.push_back(pelMech->BodyText());
+ }
+
+ // Given all the mechanisms, choose the best
+ std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
+ if (choice.empty()) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // No recognized auth mechanism - that's an error
+ sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
+ if (!sasl_mech_) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // OK, let's start it.
+ XmlElement * auth = sasl_mech_->StartSaslAuth();
+ if (auth == NULL) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+ if (allowNonGoogleLogin_) {
+ // Setting the following two attributes is required to support
+ // non-google ids.
+
+ // Allow login with non-google id accounts.
+ auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true");
+
+ // Allow login with either the non-google id or the friendly email.
+ auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true");
+ }
+
+ pctx_->InternalSendStanza(auth);
+ delete auth;
+ state_ = LOGINSTATE_SASL_RUNNING;
+ continue;
+ }
+
+ case LOGINSTATE_SASL_RUNNING: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name().Namespace() != NS_SASL)
+ return Failure(XmppEngine::ERROR_AUTH);
+ if (element->Name() == QN_SASL_CHALLENGE) {
+ XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
+ if (response == NULL) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+ pctx_->InternalSendStanza(response);
+ delete response;
+ state_ = LOGINSTATE_SASL_RUNNING;
+ continue;
+ }
+ if (element->Name() != QN_SASL_SUCCESS) {
+ return Failure(XmppEngine::ERROR_UNAUTHORIZED);
+ }
+
+ // Authenticated!
+ authNeeded_ = false;
+ state_ = LOGINSTATE_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_BIND_INIT: {
+ const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
+ const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
+ if (!pelBindFeature || !pelSessionFeature)
+ return Failure(XmppEngine::ERROR_BIND);
+
+ XmlElement iq(QN_IQ);
+ iq.AddAttr(QN_TYPE, "set");
+
+ iqId_ = pctx_->NextId();
+ iq.AddAttr(QN_ID, iqId_);
+ iq.AddElement(new XmlElement(QN_BIND_BIND, true));
+
+ if (pctx_->requested_resource_ != STR_EMPTY) {
+ iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
+ iq.AddText(pctx_->requested_resource_, 2);
+ }
+ pctx_->InternalSendStanza(&iq);
+ state_ = LOGINSTATE_BIND_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_BIND_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+ element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+ return true;
+
+ if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
+ element->FirstElement()->Name() != QN_BIND_BIND)
+ return Failure(XmppEngine::ERROR_BIND);
+
+ fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
+ if (!fullJid_.IsFull()) {
+ return Failure(XmppEngine::ERROR_BIND);
+ }
+
+ // now request session
+ XmlElement iq(QN_IQ);
+ iq.AddAttr(QN_TYPE, "set");
+
+ iqId_ = pctx_->NextId();
+ iq.AddAttr(QN_ID, iqId_);
+ iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
+ pctx_->InternalSendStanza(&iq);
+
+ state_ = LOGINSTATE_SESSION_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_SESSION_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+ element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+ return false;
+
+ if (element->Attr(QN_TYPE) != "result")
+ return Failure(XmppEngine::ERROR_BIND);
+
+ pctx_->SignalBound(fullJid_);
+ FlushQueuedStanzas();
+ state_ = LOGINSTATE_DONE;
+ return true;
+ }
+
+ case LOGINSTATE_DONE:
+ return false;
+ }
+ }
+}
+
+bool
+XmppLoginTask::HandleStartStream(const XmlElement *element) {
+
+ if (element->Name() != QN_STREAM_STREAM)
+ return false;
+
+ if (element->Attr(QN_XMLNS) != "jabber:client")
+ return false;
+
+ if (element->Attr(QN_VERSION) != "1.0")
+ return false;
+
+ if (!element->HasAttr(QN_ID))
+ return false;
+
+ streamId_ = element->Attr(QN_ID);
+
+ return true;
+}
+
+bool
+XmppLoginTask::HandleFeatures(const XmlElement *element) {
+ if (element->Name() != QN_STREAM_FEATURES)
+ return false;
+
+ pelFeatures_.reset(new XmlElement(*element));
+ return true;
+}
+
+const XmlElement *
+XmppLoginTask::GetFeature(const QName & name) {
+ return pelFeatures_->FirstNamed(name);
+}
+
+bool
+XmppLoginTask::Failure(XmppEngine::Error reason) {
+ state_ = LOGINSTATE_DONE;
+ pctx_->SignalError(reason, 0);
+ return false;
+}
+
+void
+XmppLoginTask::OutgoingStanza(const XmlElement * element) {
+ XmlElement * pelCopy = new XmlElement(*element);
+ pvecQueuedStanzas_->push_back(pelCopy);
+}
+
+void
+XmppLoginTask::FlushQueuedStanzas() {
+ for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
+ pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
+ delete (*pvecQueuedStanzas_)[i];
+ }
+ pvecQueuedStanzas_->clear();
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/xmpplogintask.h b/webrtc/libjingle/xmpp/xmpplogintask.h
new file mode 100644
index 0000000000..58e0a2f3fa
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpplogintask.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_
+
+#include <string>
+#include <vector>
+
+#include "webrtc/libjingle/xmpp/jid.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/scoped_ptr.h"
+
+namespace buzz {
+
+class XmlElement;
+class XmppEngineImpl;
+class SaslMechanism;
+
+
+// TODO: Rename to LoginTask.
+class XmppLoginTask {
+
+public:
+ XmppLoginTask(XmppEngineImpl *pctx);
+ ~XmppLoginTask();
+
+ bool IsDone()
+ { return state_ == LOGINSTATE_DONE; }
+ void IncomingStanza(const XmlElement * element, bool isStart);
+ void OutgoingStanza(const XmlElement *element);
+ void set_allow_non_google_login(bool b)
+ { allowNonGoogleLogin_ = b; }
+
+private:
+ enum LoginTaskState {
+ LOGINSTATE_INIT = 0,
+ LOGINSTATE_STREAMSTART_SENT,
+ LOGINSTATE_STARTED_XMPP,
+ LOGINSTATE_TLS_INIT,
+ LOGINSTATE_AUTH_INIT,
+ LOGINSTATE_BIND_INIT,
+ LOGINSTATE_TLS_REQUESTED,
+ LOGINSTATE_SASL_RUNNING,
+ LOGINSTATE_BIND_REQUESTED,
+ LOGINSTATE_SESSION_REQUESTED,
+ LOGINSTATE_DONE,
+ };
+
+ const XmlElement * NextStanza();
+ bool Advance();
+ bool HandleStartStream(const XmlElement * element);
+ bool HandleFeatures(const XmlElement * element);
+ const XmlElement * GetFeature(const QName & name);
+ bool Failure(XmppEngine::Error reason);
+ void FlushQueuedStanzas();
+
+ XmppEngineImpl * pctx_;
+ bool authNeeded_;
+ bool allowNonGoogleLogin_;
+ LoginTaskState state_;
+ const XmlElement * pelStanza_;
+ bool isStart_;
+ std::string iqId_;
+ rtc::scoped_ptr<XmlElement> pelFeatures_;
+ Jid fullJid_;
+ std::string streamId_;
+ rtc::scoped_ptr<std::vector<XmlElement *> > pvecQueuedStanzas_;
+
+ rtc::scoped_ptr<SaslMechanism> sasl_mech_;
+
+#ifdef _DEBUG
+ static const rtc::ConstantLabel LOGINTASK_STATES[];
+#endif // _DEBUG
+};
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_LOGINTASK_H_
diff --git a/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc
new file mode 100644
index 0000000000..221cbdeaf5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpplogintask_unittest.cc
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/plainsaslhandler.h"
+#include "webrtc/libjingle/xmpp/saslplainmechanism.h"
+#include "webrtc/libjingle/xmpp/util_unittest.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/cryptstring.h"
+#include "webrtc/base/gunit.h"
+
+using buzz::Jid;
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppEngine;
+using buzz::XmppTestHandler;
+
+enum XlttStage {
+ XLTT_STAGE_CONNECT = 0,
+ XLTT_STAGE_STREAMSTART,
+ XLTT_STAGE_TLS_FEATURES,
+ XLTT_STAGE_TLS_PROCEED,
+ XLTT_STAGE_ENCRYPTED_START,
+ XLTT_STAGE_AUTH_FEATURES,
+ XLTT_STAGE_AUTH_SUCCESS,
+ XLTT_STAGE_AUTHENTICATED_START,
+ XLTT_STAGE_BIND_FEATURES,
+ XLTT_STAGE_BIND_SUCCESS,
+ XLTT_STAGE_SESSION_SUCCESS,
+};
+
+class XmppLoginTaskTest : public testing::Test {
+ public:
+ XmppEngine* engine() { return engine_.get(); }
+ XmppTestHandler* handler() { return handler_.get(); }
+ virtual void SetUp() {
+ engine_.reset(XmppEngine::Create());
+ handler_.reset(new XmppTestHandler(engine_.get()));
+
+ Jid jid("david@my-server");
+ rtc::InsecureCryptStringImpl pass;
+ pass.password() = "david";
+ engine_->SetSessionHandler(handler_.get());
+ engine_->SetOutputHandler(handler_.get());
+ engine_->AddStanzaHandler(handler_.get());
+ engine_->SetUser(jid);
+ engine_->SetSaslHandler(
+ new buzz::PlainSaslHandler(jid, rtc::CryptString(pass), true));
+ }
+ virtual void TearDown() {
+ handler_.reset();
+ engine_.reset();
+ }
+ void RunPartialLogin(XlttStage startstage, XlttStage endstage);
+ void SetTlsOptions(buzz::TlsOptions option);
+
+ private:
+ rtc::scoped_ptr<XmppEngine> engine_;
+ rtc::scoped_ptr<XmppTestHandler> handler_;
+};
+
+void XmppLoginTaskTest::SetTlsOptions(buzz::TlsOptions option) {
+ engine_->SetTls(option);
+}
+void XmppLoginTaskTest::RunPartialLogin(XlttStage startstage,
+ XlttStage endstage) {
+ std::string input;
+
+ switch (startstage) {
+ case XLTT_STAGE_CONNECT: {
+ engine_->Connect();
+ XmlElement appStanza(QName("test", "app-stanza"));
+ appStanza.AddText("this-is-a-test");
+ engine_->SendStanza(&appStanza);
+
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("[OPENING]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ if (endstage == XLTT_STAGE_CONNECT)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_STREAMSTART: {
+ input = "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_STREAMSTART)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_TLS_FEATURES: {
+ input = "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_TLS_FEATURES)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_TLS_PROCEED: {
+ input = std::string("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[START-TLS my-server]"
+ "<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_TLS_PROCEED)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_ENCRYPTED_START: {
+ input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_ENCRYPTED_START)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_AUTH_FEATURES: {
+ input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" "
+ "auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_AUTH_FEATURES)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_AUTH_SUCCESS: {
+ input = "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<stream:stream to=\"my-server\" xml:lang=\"*\" "
+ "version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">\r\n", handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_AUTH_SUCCESS)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_AUTHENTICATED_START: {
+ input = std::string("<stream:stream id=\"01234567\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">");
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->OutputActivity());
+ if (endstage == XLTT_STAGE_AUTHENTICATED_START)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_BIND_FEATURES: {
+ input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"0\">"
+ "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/></iq>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_BIND_FEATURES)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_BIND_SUCCESS: {
+ input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+ "<jid>david@my-server/test</jid></bind></iq>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<iq type=\"set\" id=\"1\">"
+ "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/></iq>",
+ handler_->OutputActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ EXPECT_EQ("", handler_->SessionActivity());
+ if (endstage == XLTT_STAGE_BIND_SUCCESS)
+ return;
+ FALLTHROUGH();
+ }
+
+ case XLTT_STAGE_SESSION_SUCCESS: {
+ input = "<iq type='result' id='1'/>";
+ engine_->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("<test:app-stanza xmlns:test=\"test\">this-is-a-test"
+ "</test:app-stanza>", handler_->OutputActivity());
+ EXPECT_EQ("[OPEN]", handler_->SessionActivity());
+ EXPECT_EQ("", handler_->StanzaActivity());
+ if (endstage == XLTT_STAGE_SESSION_SUCCESS)
+ return;
+ FALLTHROUGH();
+ }
+ default:
+ break;
+ }
+}
+
+TEST_F(XmppLoginTaskTest, TestUtf8Good) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+ std::string input = "<?xml version='1.0' encoding='UTF-8'?>"
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNonUtf8Bad) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_CONNECT);
+
+ std::string input = "<?xml version='1.0' encoding='ISO-8859-1'?>"
+ "<stream:stream id=\"a5f2d8c9\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">";
+ engine()->HandleInput(input.c_str(), input.length());
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestNoFeatures) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<iq type='get' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequiredNotPresent) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsRequeiredAndPresent) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>"
+ "<required/>"
+ "</starttls>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>",
+ handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledNotPresent) {
+ SetTlsOptions(buzz::TLS_ENABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsEnabledAndPresent) {
+ SetTlsOptions(buzz::TLS_ENABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledNotPresent) {
+ SetTlsOptions(buzz::TLS_DISABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsDisabledAndPresent) {
+ SetTlsOptions(buzz::TLS_DISABLED);
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_STREAMSTART);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism>"
+ "<mechanism>PLAIN</mechanism>"
+ "<mechanism>X-OAUTH2</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("<auth xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "mechanism=\"PLAIN\" auth:allow-non-google-login=\"true\" "
+ "auth:client-uses-full-bind-result=\"true\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\""
+ ">AGRhdmlkAGRhdmlk</auth>", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsFailure) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_FEATURES);
+
+ std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-TLS]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestTlsBadStream) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_TLS_PROCEED);
+
+ std::string input = "<wrongtag>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSaslPlain) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_ENCRYPTED_START);
+
+ std::string input = "<stream:features>"
+ "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"
+ "<mechanism>DIGEST-MD5</mechanism>"
+ "</mechanisms>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-AUTH]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestWrongPassword) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_FEATURES);
+
+ std::string input = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-UNAUTHORIZED]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestAuthBadStream) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTH_SUCCESS);
+
+ std::string input = "<wrongtag>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-VERSION]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingBindFeature) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+ std::string input = "<stream:features>"
+ "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestMissingSessionFeature) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_AUTHENTICATED_START);
+
+ std::string input = "<stream:features>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
+ "</stream:features>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+/* TODO: Handle this case properly inside XmppLoginTask.
+TEST_F(XmppLoginTaskTest, TestBindFailure1) {
+ // check wrong JID
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>"
+ "<jid>davey@my-server/test</jid></bind></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+*/
+
+TEST_F(XmppLoginTaskTest, TestBindFailure2) {
+ // check missing JID
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='result' id='0'>"
+ "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure3) {
+ // check plain failure
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='error' id='0'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+ EXPECT_EQ("", handler()->StanzaActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestBindFailure4) {
+ // check wrong id to ignore
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_FEATURES);
+
+ std::string input = "<iq type='error' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ // continue after an ignored iq
+ RunPartialLogin(XLTT_STAGE_BIND_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain1) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+ std::string input = "<iq type='error' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-BIND]", handler()->SessionActivity());
+}
+
+TEST_F(XmppLoginTaskTest, TestSessionFailurePlain2) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, XLTT_STAGE_BIND_SUCCESS);
+
+ // check reverse iq to ignore
+ // TODO: consider queueing or passing through?
+ std::string input = "<iq type='get' id='1'/>";
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("", handler()->OutputActivity());
+ EXPECT_EQ("", handler()->SessionActivity());
+
+ // continue after an ignored iq
+ RunPartialLogin(XLTT_STAGE_SESSION_SUCCESS, XLTT_STAGE_SESSION_SUCCESS);
+}
+
+TEST_F(XmppLoginTaskTest, TestBadXml) {
+ int errorKind = 0;
+ for (XlttStage stage = XLTT_STAGE_CONNECT;
+ stage <= XLTT_STAGE_SESSION_SUCCESS;
+ stage = static_cast<XlttStage>(stage + 1)) {
+ RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+ std::string input;
+ switch (errorKind++ % 5) {
+ case 0: input = "&syntax;"; break;
+ case 1: input = "<nons:iq/>"; break;
+ case 2: input = "<iq a='b' a='dupe'/>"; break;
+ case 3: input = "<>"; break;
+ case 4: input = "<iq a='<wrong>'>"; break;
+ }
+
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-XML]", handler()->SessionActivity());
+
+ TearDown();
+ SetUp();
+ }
+}
+
+TEST_F(XmppLoginTaskTest, TestStreamError) {
+ for (XlttStage stage = XLTT_STAGE_CONNECT;
+ stage <= XLTT_STAGE_SESSION_SUCCESS;
+ stage = static_cast<XlttStage>(stage + 1)) {
+ switch (stage) {
+ case XLTT_STAGE_CONNECT:
+ case XLTT_STAGE_TLS_PROCEED:
+ case XLTT_STAGE_AUTH_SUCCESS:
+ continue;
+ default:
+ break;
+ }
+
+ RunPartialLogin(XLTT_STAGE_CONNECT, stage);
+
+ std::string input = "<stream:error>"
+ "<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
+ "<text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'>"
+ "Some special application diagnostic information!"
+ "</text>"
+ "<escape-your-data xmlns='application-ns'/>"
+ "</stream:error>";
+
+ engine()->HandleInput(input.c_str(), input.length());
+
+ EXPECT_EQ("[CLOSED]", handler()->OutputActivity());
+ EXPECT_EQ("[CLOSED][ERROR-STREAM]", handler()->SessionActivity());
+
+ EXPECT_EQ("<str:error xmlns:str=\"http://etherx.jabber.org/streams\">"
+ "<xml-not-well-formed xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/>"
+ "<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\">"
+ "Some special application diagnostic information!"
+ "</text>"
+ "<escape-your-data xmlns=\"application-ns\"/>"
+ "</str:error>", engine()->GetStreamError()->Str());
+
+ TearDown();
+ SetUp();
+ }
+}
+
diff --git a/webrtc/libjingle/xmpp/xmpppump.cc b/webrtc/libjingle/xmpp/xmpppump.cc
new file mode 100644
index 0000000000..a428ffa4dc
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpppump.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmpppump.h"
+
+#include "webrtc/libjingle/xmpp/xmppauth.h"
+
+namespace buzz {
+
+XmppPump::XmppPump(XmppPumpNotify * notify) {
+ state_ = buzz::XmppEngine::STATE_NONE;
+ notify_ = notify;
+ client_ = new buzz::XmppClient(this); // NOTE: deleted by TaskRunner
+}
+
+void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs,
+ buzz::AsyncSocket* socket,
+ buzz::PreXmppAuth* auth) {
+ OnStateChange(buzz::XmppEngine::STATE_START);
+ if (!AllChildrenDone()) {
+ client_->SignalStateChange.connect(this, &XmppPump::OnStateChange);
+ client_->Connect(xcs, "", socket, auth);
+ client_->Start();
+ }
+}
+
+void XmppPump::DoDisconnect() {
+ if (!AllChildrenDone())
+ client_->Disconnect();
+ OnStateChange(buzz::XmppEngine::STATE_CLOSED);
+}
+
+void XmppPump::OnStateChange(buzz::XmppEngine::State state) {
+ if (state_ == state)
+ return;
+ state_ = state;
+ if (notify_ != NULL)
+ notify_->OnStateChange(state);
+}
+
+void XmppPump::WakeTasks() {
+ rtc::Thread::Current()->Post(this);
+}
+
+int64_t XmppPump::CurrentTime() {
+ return (int64_t)rtc::Time();
+}
+
+void XmppPump::OnMessage(rtc::Message *pmsg) {
+ RunTasks();
+}
+
+buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) {
+ if (!AllChildrenDone())
+ return client_->SendStanza(stanza);
+ return buzz::XMPP_RETURN_BADSTATE;
+}
+
+} // namespace buzz
+
diff --git a/webrtc/libjingle/xmpp/xmpppump.h b/webrtc/libjingle/xmpp/xmpppump.h
new file mode 100644
index 0000000000..bd1b5628b5
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpppump.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_
+
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+#include "webrtc/base/messagequeue.h"
+#include "webrtc/base/taskrunner.h"
+#include "webrtc/base/thread.h"
+#include "webrtc/base/timeutils.h"
+
+namespace buzz {
+
+// Simple xmpp pump
+
+class XmppPumpNotify {
+public:
+ virtual ~XmppPumpNotify() {}
+ virtual void OnStateChange(buzz::XmppEngine::State state) = 0;
+};
+
+class XmppPump : public rtc::MessageHandler, public rtc::TaskRunner {
+public:
+ XmppPump(buzz::XmppPumpNotify * notify = NULL);
+
+ buzz::XmppClient *client() { return client_; }
+
+ void DoLogin(const buzz::XmppClientSettings & xcs,
+ buzz::AsyncSocket* socket,
+ buzz::PreXmppAuth* auth);
+ void DoDisconnect();
+
+ void OnStateChange(buzz::XmppEngine::State state);
+
+ void WakeTasks();
+
+ int64_t CurrentTime();
+
+ void OnMessage(rtc::Message *pmsg);
+
+ buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza);
+
+private:
+ buzz::XmppClient *client_;
+ buzz::XmppEngine::State state_;
+ buzz::XmppPumpNotify *notify_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPPUMP_H_
+
diff --git a/webrtc/libjingle/xmpp/xmppsocket.cc b/webrtc/libjingle/xmpp/xmppsocket.cc
new file mode 100644
index 0000000000..d67a71fe43
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppsocket.cc
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "xmppsocket.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include "webrtc/base/basicdefs.h"
+#include "webrtc/base/logging.h"
+#include "webrtc/base/thread.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "webrtc/base/ssladapter.h"
+#endif
+
+#ifdef USE_SSLSTREAM
+#include "webrtc/base/socketstream.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "webrtc/base/sslstreamadapter.h"
+#endif // FEATURE_ENABLE_SSL
+#endif // USE_SSLSTREAM
+
+namespace buzz {
+
+XmppSocket::XmppSocket(buzz::TlsOptions tls) : cricket_socket_(NULL),
+ tls_(tls) {
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+}
+
+void XmppSocket::CreateCricketSocket(int family) {
+ rtc::Thread* pth = rtc::Thread::Current();
+ if (family == AF_UNSPEC) {
+ family = AF_INET;
+ }
+ rtc::AsyncSocket* socket =
+ pth->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
+#ifndef USE_SSLSTREAM
+#ifdef FEATURE_ENABLE_SSL
+ if (tls_ != buzz::TLS_DISABLED) {
+ socket = rtc::SSLAdapter::Create(socket);
+ }
+#endif // FEATURE_ENABLE_SSL
+ cricket_socket_ = socket;
+ cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent);
+ cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent);
+ cricket_socket_->SignalConnectEvent.connect(this,
+ &XmppSocket::OnConnectEvent);
+ cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent);
+#else // USE_SSLSTREAM
+ cricket_socket_ = socket;
+ stream_ = new rtc::SocketStream(cricket_socket_);
+#ifdef FEATURE_ENABLE_SSL
+ if (tls_ != buzz::TLS_DISABLED)
+ stream_ = rtc::SSLStreamAdapter::Create(stream_);
+#endif // FEATURE_ENABLE_SSL
+ stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
+#endif // USE_SSLSTREAM
+}
+
+XmppSocket::~XmppSocket() {
+ Close();
+#ifndef USE_SSLSTREAM
+ delete cricket_socket_;
+#else // USE_SSLSTREAM
+ delete stream_;
+#endif // USE_SSLSTREAM
+}
+
+#ifndef USE_SSLSTREAM
+void XmppSocket::OnReadEvent(rtc::AsyncSocket * socket) {
+ SignalRead();
+}
+
+void XmppSocket::OnWriteEvent(rtc::AsyncSocket * socket) {
+ // Write bytes if there are any
+ while (buffer_.Length() != 0) {
+ int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length());
+ if (written > 0) {
+ buffer_.Consume(written);
+ continue;
+ }
+ if (!cricket_socket_->IsBlocking())
+ LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError();
+ return;
+ }
+}
+
+void XmppSocket::OnConnectEvent(rtc::AsyncSocket * socket) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+ state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+ SignalSSLConnected();
+ OnWriteEvent(cricket_socket_);
+ return;
+ }
+#endif // !defined(FEATURE_ENABLE_SSL)
+ state_ = buzz::AsyncSocket::STATE_OPEN;
+ SignalConnected();
+}
+
+void XmppSocket::OnCloseEvent(rtc::AsyncSocket * socket, int error) {
+ SignalCloseEvent(error);
+}
+
+#else // USE_SSLSTREAM
+
+void XmppSocket::OnEvent(rtc::StreamInterface* stream,
+ int events, int err) {
+ if ((events & rtc::SE_OPEN)) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+ state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+ SignalSSLConnected();
+ events |= rtc::SE_WRITE;
+ } else
+#endif
+ {
+ state_ = buzz::AsyncSocket::STATE_OPEN;
+ SignalConnected();
+ }
+ }
+ if ((events & rtc::SE_READ))
+ SignalRead();
+ if ((events & rtc::SE_WRITE)) {
+ // Write bytes if there are any
+ while (buffer_.Length() != 0) {
+ rtc::StreamResult result;
+ size_t written;
+ int error;
+ result = stream_->Write(buffer_.Data(), buffer_.Length(),
+ &written, &error);
+ if (result == rtc::SR_ERROR) {
+ LOG(LS_ERROR) << "Send error: " << error;
+ return;
+ }
+ if (result == rtc::SR_BLOCK)
+ return;
+ ASSERT(result == rtc::SR_SUCCESS);
+ ASSERT(written > 0);
+ buffer_.Shift(written);
+ }
+ }
+ if ((events & rtc::SE_CLOSE))
+ SignalCloseEvent(err);
+}
+#endif // USE_SSLSTREAM
+
+buzz::AsyncSocket::State XmppSocket::state() {
+ return state_;
+}
+
+buzz::AsyncSocket::Error XmppSocket::error() {
+ return buzz::AsyncSocket::ERROR_NONE;
+}
+
+int XmppSocket::GetError() {
+ return 0;
+}
+
+bool XmppSocket::Connect(const rtc::SocketAddress& addr) {
+ if (cricket_socket_ == NULL) {
+ CreateCricketSocket(addr.family());
+ }
+ if (cricket_socket_->Connect(addr) < 0) {
+ return cricket_socket_->IsBlocking();
+ }
+ return true;
+}
+
+bool XmppSocket::Read(char * data, size_t len, size_t* len_read) {
+#ifndef USE_SSLSTREAM
+ int read = cricket_socket_->Recv(data, len);
+ if (read > 0) {
+ *len_read = (size_t)read;
+ return true;
+ }
+#else // USE_SSLSTREAM
+ rtc::StreamResult result = stream_->Read(data, len, len_read, NULL);
+ if (result == rtc::SR_SUCCESS)
+ return true;
+#endif // USE_SSLSTREAM
+ return false;
+}
+
+bool XmppSocket::Write(const char * data, size_t len) {
+ buffer_.WriteBytes(data, len);
+#ifndef USE_SSLSTREAM
+ OnWriteEvent(cricket_socket_);
+#else // USE_SSLSTREAM
+ OnEvent(stream_, rtc::SE_WRITE, 0);
+#endif // USE_SSLSTREAM
+ return true;
+}
+
+bool XmppSocket::Close() {
+ if (state_ != buzz::AsyncSocket::STATE_OPEN)
+ return false;
+#ifndef USE_SSLSTREAM
+ if (cricket_socket_->Close() == 0) {
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+ SignalClosed();
+ return true;
+ }
+ return false;
+#else // USE_SSLSTREAM
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+ stream_->Close();
+ SignalClosed();
+ return true;
+#endif // USE_SSLSTREAM
+}
+
+bool XmppSocket::StartTls(const std::string & domainname) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (tls_ == buzz::TLS_DISABLED)
+ return false;
+#ifndef USE_SSLSTREAM
+ rtc::SSLAdapter* ssl_adapter =
+ static_cast<rtc::SSLAdapter *>(cricket_socket_);
+ if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0)
+ return false;
+#else // USE_SSLSTREAM
+ rtc::SSLStreamAdapter* ssl_stream =
+ static_cast<rtc::SSLStreamAdapter *>(stream_);
+ if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0)
+ return false;
+#endif // USE_SSLSTREAM
+ state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING;
+ return true;
+#else // !defined(FEATURE_ENABLE_SSL)
+ return false;
+#endif // !defined(FEATURE_ENABLE_SSL)
+}
+
+} // namespace buzz
+
diff --git a/webrtc/libjingle/xmpp/xmppsocket.h b/webrtc/libjingle/xmpp/xmppsocket.h
new file mode 100644
index 0000000000..527b23a5d0
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppsocket.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_
+
+#include "webrtc/libjingle/xmpp/asyncsocket.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/asyncsocket.h"
+#include "webrtc/base/bytebuffer.h"
+#include "webrtc/base/sigslot.h"
+
+// The below define selects the SSLStreamAdapter implementation for
+// SSL, as opposed to the SSLAdapter socket adapter.
+// #define USE_SSLSTREAM
+
+namespace rtc {
+ class StreamInterface;
+ class SocketAddress;
+};
+extern rtc::AsyncSocket* cricket_socket_;
+
+namespace buzz {
+
+class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
+public:
+ XmppSocket(buzz::TlsOptions tls);
+ ~XmppSocket();
+
+ virtual buzz::AsyncSocket::State state();
+ virtual buzz::AsyncSocket::Error error();
+ virtual int GetError();
+
+ virtual bool Connect(const rtc::SocketAddress& addr);
+ virtual bool Read(char * data, size_t len, size_t* len_read);
+ virtual bool Write(const char * data, size_t len);
+ virtual bool Close();
+ virtual bool StartTls(const std::string & domainname);
+
+ sigslot::signal1<int> SignalCloseEvent;
+
+private:
+ void CreateCricketSocket(int family);
+#ifndef USE_SSLSTREAM
+ void OnReadEvent(rtc::AsyncSocket * socket);
+ void OnWriteEvent(rtc::AsyncSocket * socket);
+ void OnConnectEvent(rtc::AsyncSocket * socket);
+ void OnCloseEvent(rtc::AsyncSocket * socket, int error);
+#else // USE_SSLSTREAM
+ void OnEvent(rtc::StreamInterface* stream, int events, int err);
+#endif // USE_SSLSTREAM
+
+ rtc::AsyncSocket * cricket_socket_;
+#ifdef USE_SSLSTREAM
+ rtc::StreamInterface *stream_;
+#endif // USE_SSLSTREAM
+ buzz::AsyncSocket::State state_;
+ rtc::ByteBuffer buffer_;
+ buzz::TlsOptions tls_;
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPSOCKET_H_
+
diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.cc b/webrtc/libjingle/xmpp/xmppstanzaparser.cc
new file mode 100644
index 0000000000..035bb0b6f1
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppstanzaparser.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmppstanzaparser.h"
+
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/base/common.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif
+
+namespace buzz {
+
+XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) :
+ psph_(psph),
+ innerHandler_(this),
+ parser_(&innerHandler_),
+ depth_(0),
+ builder_() {
+}
+
+void
+XmppStanzaParser::Reset() {
+ parser_.Reset();
+ depth_ = 0;
+ builder_.Reset();
+}
+
+void
+XmppStanzaParser::IncomingStartElement(
+ XmlParseContext * pctx, const char * name, const char ** atts) {
+ if (depth_++ == 0) {
+ XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts);
+ if (pelStream == NULL) {
+ pctx->RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+ psph_->StartStream(pelStream);
+ delete pelStream;
+ return;
+ }
+
+ builder_.StartElement(pctx, name, atts);
+}
+
+void
+XmppStanzaParser::IncomingCharacterData(
+ XmlParseContext * pctx, const char * text, int len) {
+ if (depth_ > 1) {
+ builder_.CharacterData(pctx, text, len);
+ }
+}
+
+void
+XmppStanzaParser::IncomingEndElement(
+ XmlParseContext * pctx, const char * name) {
+ if (--depth_ == 0) {
+ psph_->EndStream();
+ return;
+ }
+
+ builder_.EndElement(pctx, name);
+
+ if (depth_ == 1) {
+ XmlElement *element = builder_.CreateElement();
+ psph_->Stanza(element);
+ delete element;
+ }
+}
+
+void
+XmppStanzaParser::IncomingError(
+ XmlParseContext * pctx, XML_Error errCode) {
+ RTC_UNUSED(pctx);
+ RTC_UNUSED(errCode);
+ psph_->XmlError();
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser.h b/webrtc/libjingle/xmpp/xmppstanzaparser.h
new file mode 100644
index 0000000000..3ad052e4ff
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppstanzaparser.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_
+
+#include "webrtc/libjingle/xmllite/xmlbuilder.h"
+#include "webrtc/libjingle/xmllite/xmlparser.h"
+
+
+namespace buzz {
+
+class XmlElement;
+
+class XmppStanzaParseHandler {
+public:
+ virtual ~XmppStanzaParseHandler() {}
+ virtual void StartStream(const XmlElement * pelStream) = 0;
+ virtual void Stanza(const XmlElement * pelStanza) = 0;
+ virtual void EndStream() = 0;
+ virtual void XmlError() = 0;
+};
+
+class XmppStanzaParser {
+public:
+ XmppStanzaParser(XmppStanzaParseHandler *psph);
+ bool Parse(const char * data, size_t len, bool isFinal)
+ { return parser_.Parse(data, len, isFinal); }
+ void Reset();
+
+private:
+ class ParseHandler : public XmlParseHandler {
+ public:
+ ParseHandler(XmppStanzaParser * outer) : outer_(outer) {}
+ virtual void StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts)
+ { outer_->IncomingStartElement(pctx, name, atts); }
+ virtual void EndElement(XmlParseContext * pctx,
+ const char * name)
+ { outer_->IncomingEndElement(pctx, name); }
+ virtual void CharacterData(XmlParseContext * pctx,
+ const char * text, int len)
+ { outer_->IncomingCharacterData(pctx, text, len); }
+ virtual void Error(XmlParseContext * pctx,
+ XML_Error errCode)
+ { outer_->IncomingError(pctx, errCode); }
+ private:
+ XmppStanzaParser * const outer_;
+ };
+
+ friend class ParseHandler;
+
+ void IncomingStartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts);
+ void IncomingEndElement(XmlParseContext * pctx,
+ const char * name);
+ void IncomingCharacterData(XmlParseContext * pctx,
+ const char * text, int len);
+ void IncomingError(XmlParseContext * pctx,
+ XML_Error errCode);
+
+ XmppStanzaParseHandler * psph_;
+ ParseHandler innerHandler_;
+ XmlParser parser_;
+ int depth_;
+ XmlBuilder builder_;
+
+ };
+
+
+}
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPSTANZAPARSER_H_
diff --git a/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc
new file mode 100644
index 0000000000..65fcac9d63
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppstanzaparser_unittest.cc
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "webrtc/libjingle/xmllite/xmlelement.h"
+#include "webrtc/libjingle/xmpp/xmppstanzaparser.h"
+#include "webrtc/base/common.h"
+#include "webrtc/base/gunit.h"
+
+using buzz::QName;
+using buzz::XmlElement;
+using buzz::XmppStanzaParser;
+using buzz::XmppStanzaParseHandler;
+
+class XmppStanzaParserTestHandler : public XmppStanzaParseHandler {
+ public:
+ virtual void StartStream(const XmlElement * element) {
+ ss_ << "START" << element->Str();
+ }
+ virtual void Stanza(const XmlElement * element) {
+ ss_ << "STANZA" << element->Str();
+ }
+ virtual void EndStream() {
+ ss_ << "END";
+ }
+ virtual void XmlError() {
+ ss_ << "ERROR";
+ }
+
+ std::string Str() {
+ return ss_.str();
+ }
+
+ std::string StrClear() {
+ std::string result = ss_.str();
+ ss_.str("");
+ return result;
+ }
+
+ private:
+ std::stringstream ss_;
+};
+
+
+TEST(XmppStanzaParserTest, TestTrivial) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<trivial/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<trivial/>END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestStanzaAtATime) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "<message type='foo'><body>hello</body></message>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>", handler.StrClear());
+
+ fragment = " SOME TEXT TO IGNORE ";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "<iq type='set' id='123'><abc xmlns='def'/></iq>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:iq type=\"set\" id=\"123\" xmlns:c=\"j:c\">"
+ "<abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+ fragment = "</stream:stream>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestFragmentedStanzas) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "lo</body></message> IGNORE ME <iq type='set' id='123'>"
+ "<abc xmlns='def'/></iq></st";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>STANZA<c:iq type=\"set\" id=\"123\" "
+ "xmlns:c=\"j:c\"><abc xmlns=\"def\"/></c:iq>", handler.StrClear());
+
+ fragment = "ream:stream>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("END", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestReset) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ parser.Reset();
+ fragment = "<stream:stream id='abc' xmlns='j:c' xml";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("", handler.StrClear());
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+ parser.Reset();
+
+ fragment = "<stream:stream id='abc' xmlns='j:c' xmlns:stream='str'>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream id=\"abc\" xmlns=\"j:c\" "
+ "xmlns:stream=\"str\"/>", handler.StrClear());
+
+ fragment = "<message type='foo'><body>hello</body></message>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("STANZA<c:message type=\"foo\" xmlns:c=\"j:c\">"
+ "<c:body>hello</c:body></c:message>", handler.StrClear());
+}
+
+TEST(XmppStanzaParserTest, TestError) {
+ XmppStanzaParserTestHandler handler;
+ XmppStanzaParser parser(&handler);
+ std::string fragment;
+
+ fragment = "<-foobar/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+
+ parser.Reset();
+ fragment = "<stream:stream/>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+ parser.Reset();
+
+ fragment = "ns:stream='str'><message type='foo'><body>hel";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("ERROR", handler.StrClear());
+ parser.Reset();
+
+ fragment = "<stream:stream xmlns:stream='st' xmlns='jc'>"
+ "<foo/><bar><st:foobar/></bar>";
+ parser.Parse(fragment.c_str(), fragment.length(), false);
+ EXPECT_EQ("START<stream:stream xmlns:stream=\"st\" xmlns=\"jc\"/>STANZA"
+ "<jc:foo xmlns:jc=\"jc\"/>ERROR", handler.StrClear());
+}
diff --git a/webrtc/libjingle/xmpp/xmpptask.cc b/webrtc/libjingle/xmpp/xmpptask.cc
new file mode 100644
index 0000000000..09067058ae
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpptask.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/constants.h"
+#include "webrtc/libjingle/xmpp/xmppclient.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpptask.h"
+
+namespace buzz {
+
+XmppClientInterface::XmppClientInterface() {
+}
+
+XmppClientInterface::~XmppClientInterface() {
+}
+
+XmppTask::XmppTask(XmppTaskParentInterface* parent,
+ XmppEngine::HandlerLevel level)
+ : XmppTaskBase(parent), stopped_(false) {
+#ifdef _DEBUG
+ debug_force_timeout_ = false;
+#endif
+
+ id_ = GetClient()->NextId();
+ GetClient()->AddXmppTask(this, level);
+ GetClient()->SignalDisconnected.connect(this, &XmppTask::OnDisconnect);
+}
+
+XmppTask::~XmppTask() {
+ StopImpl();
+}
+
+void XmppTask::StopImpl() {
+ while (NextStanza() != NULL) {}
+ if (!stopped_) {
+ GetClient()->RemoveXmppTask(this);
+ GetClient()->SignalDisconnected.disconnect(this);
+ stopped_ = true;
+ }
+}
+
+XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) {
+ if (stopped_)
+ return XMPP_RETURN_BADSTATE;
+ return GetClient()->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original,
+ XmppStanzaError code,
+ const std::string& text) {
+ if (stopped_)
+ return XMPP_RETURN_BADSTATE;
+ return GetClient()->SendStanzaError(element_original, code, text);
+}
+
+void XmppTask::Stop() {
+ StopImpl();
+ Task::Stop();
+}
+
+void XmppTask::OnDisconnect() {
+ Error();
+}
+
+void XmppTask::QueueStanza(const XmlElement* stanza) {
+#ifdef _DEBUG
+ if (debug_force_timeout_)
+ return;
+#endif
+
+ stanza_queue_.push_back(new XmlElement(*stanza));
+ Wake();
+}
+
+const XmlElement* XmppTask::NextStanza() {
+ XmlElement* result = NULL;
+ if (!stanza_queue_.empty()) {
+ result = stanza_queue_.front();
+ stanza_queue_.pop_front();
+ }
+ next_stanza_.reset(result);
+ return result;
+}
+
+XmlElement* XmppTask::MakeIq(const std::string& type,
+ const buzz::Jid& to,
+ const std::string& id) {
+ XmlElement* result = new XmlElement(QN_IQ);
+ if (!type.empty())
+ result->AddAttr(QN_TYPE, type);
+ if (!to.IsEmpty())
+ result->AddAttr(QN_TO, to.Str());
+ if (!id.empty())
+ result->AddAttr(QN_ID, id);
+ return result;
+}
+
+XmlElement* XmppTask::MakeIqResult(const XmlElement * query) {
+ XmlElement* result = new XmlElement(QN_IQ);
+ result->AddAttr(QN_TYPE, STR_RESULT);
+ if (query->HasAttr(QN_FROM)) {
+ result->AddAttr(QN_TO, query->Attr(QN_FROM));
+ }
+ result->AddAttr(QN_ID, query->Attr(QN_ID));
+ return result;
+}
+
+bool XmppTask::MatchResponseIq(const XmlElement* stanza,
+ const Jid& to,
+ const std::string& id) {
+ if (stanza->Name() != QN_IQ)
+ return false;
+
+ if (stanza->Attr(QN_ID) != id)
+ return false;
+
+ return MatchStanzaFrom(stanza, to);
+}
+
+bool XmppTask::MatchStanzaFrom(const XmlElement* stanza,
+ const Jid& to) {
+ Jid from(stanza->Attr(QN_FROM));
+ if (from == to)
+ return true;
+
+ // We address the server as "", check if we are doing so here.
+ if (!to.IsEmpty())
+ return false;
+
+ // It is legal for the server to identify itself with "domain" or
+ // "myself@domain"
+ Jid me = GetClient()->jid();
+ return (from == Jid(me.domain())) || (from == me.BareJid());
+}
+
+bool XmppTask::MatchRequestIq(const XmlElement* stanza,
+ const std::string& type,
+ const QName& qn) {
+ if (stanza->Name() != QN_IQ)
+ return false;
+
+ if (stanza->Attr(QN_TYPE) != type)
+ return false;
+
+ if (stanza->FirstNamed(qn) == NULL)
+ return false;
+
+ return true;
+}
+
+}
diff --git a/webrtc/libjingle/xmpp/xmpptask.h b/webrtc/libjingle/xmpp/xmpptask.h
new file mode 100644
index 0000000000..5b97e89c97
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmpptask.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_
+
+#include <deque>
+#include <string>
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/base/sigslot.h"
+#include "webrtc/base/task.h"
+#include "webrtc/base/taskparent.h"
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPTASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task and XmppClient first.
+//
+// XmppTask is a task that is designed to go underneath XmppClient and be
+// useful there. It has a way of finding its XmppClient parent so you
+// can have it nested arbitrarily deep under an XmppClient and it can
+// still find the XMPP services.
+//
+// Tasks register themselves to listen to particular kinds of stanzas
+// that are sent out by the client. Rather than processing stanzas
+// right away, they should decide if they own the sent stanza,
+// and if so, queue it and Wake() the task, or if a stanza does not belong
+// to you, return false right away so the next XmppTask can take a crack.
+// This technique (synchronous recognize, but asynchronous processing)
+// allows you to have arbitrary logic for recognizing stanzas yet still,
+// for example, disconnect a client while processing a stanza -
+// without reentrancy problems.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppTask;
+
+// XmppClientInterface is an abstract interface for sending and
+// handling stanzas. It can be implemented for unit tests or
+// different network environments. It will usually be implemented by
+// XmppClient.
+class XmppClientInterface {
+ public:
+ XmppClientInterface();
+ virtual ~XmppClientInterface();
+
+ virtual XmppEngine::State GetState() const = 0;
+ virtual const Jid& jid() const = 0;
+ virtual std::string NextId() = 0;
+ virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0;
+ virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza,
+ XmppStanzaError error_code,
+ const std::string& message) = 0;
+ virtual void AddXmppTask(XmppTask* task, XmppEngine::HandlerLevel level) = 0;
+ virtual void RemoveXmppTask(XmppTask* task) = 0;
+ sigslot::signal0<> SignalDisconnected;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XmppClientInterface);
+};
+
+// XmppTaskParentInterface is the interface require for any parent of
+// an XmppTask. It needs, for example, a way to get an
+// XmppClientInterface.
+
+// We really ought to inherit from a TaskParentInterface, but we tried
+// that and it's way too complicated to change
+// Task/TaskParent/TaskRunner. For now, this works.
+class XmppTaskParentInterface : public rtc::Task {
+ public:
+ explicit XmppTaskParentInterface(rtc::TaskParent* parent)
+ : Task(parent) {
+ }
+ virtual ~XmppTaskParentInterface() {}
+
+ virtual XmppClientInterface* GetClient() = 0;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XmppTaskParentInterface);
+};
+
+class XmppTaskBase : public XmppTaskParentInterface {
+ public:
+ explicit XmppTaskBase(XmppTaskParentInterface* parent)
+ : XmppTaskParentInterface(parent),
+ parent_(parent) {
+ }
+ virtual ~XmppTaskBase() {}
+
+ virtual XmppClientInterface* GetClient() {
+ return parent_->GetClient();
+ }
+
+ protected:
+ XmppTaskParentInterface* parent_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XmppTaskBase);
+};
+
+class XmppTask : public XmppTaskBase,
+ public XmppStanzaHandler,
+ public sigslot::has_slots<>
+{
+ public:
+ XmppTask(XmppTaskParentInterface* parent,
+ XmppEngine::HandlerLevel level = XmppEngine::HL_NONE);
+ virtual ~XmppTask();
+
+ std::string task_id() const { return id_; }
+ void set_task_id(std::string id) { id_ = id; }
+
+#ifdef _DEBUG
+ void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; }
+#endif
+
+ virtual bool HandleStanza(const XmlElement* stanza) { return false; }
+
+ protected:
+ XmppReturnStatus SendStanza(const XmlElement* stanza);
+ XmppReturnStatus SetResult(const std::string& code);
+ XmppReturnStatus SendStanzaError(const XmlElement* element_original,
+ XmppStanzaError code,
+ const std::string& text);
+
+ virtual void Stop();
+ virtual void OnDisconnect();
+
+ virtual void QueueStanza(const XmlElement* stanza);
+ const XmlElement* NextStanza();
+
+ bool MatchStanzaFrom(const XmlElement* stanza, const Jid& match_jid);
+
+ bool MatchResponseIq(const XmlElement* stanza, const Jid& to,
+ const std::string& task_id);
+
+ static bool MatchRequestIq(const XmlElement* stanza, const std::string& type,
+ const QName& qn);
+ static XmlElement *MakeIqResult(const XmlElement* query);
+ static XmlElement *MakeIq(const std::string& type,
+ const Jid& to, const std::string& task_id);
+
+ // Returns true if the task is under the specified rate limit and updates the
+ // rate limit accordingly
+ bool VerifyTaskRateLimit(const std::string task_name, int max_count,
+ int per_x_seconds);
+
+private:
+ void StopImpl();
+
+ bool stopped_;
+ std::deque<XmlElement*> stanza_queue_;
+ rtc::scoped_ptr<XmlElement> next_stanza_;
+ std::string id_;
+
+#ifdef _DEBUG
+ bool debug_force_timeout_;
+#endif
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTASK_H_
diff --git a/webrtc/libjingle/xmpp/xmppthread.cc b/webrtc/libjingle/xmpp/xmppthread.cc
new file mode 100644
index 0000000000..f492cdf6c4
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppthread.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/libjingle/xmpp/xmppthread.h"
+
+#include "webrtc/libjingle/xmpp/xmppauth.h"
+#include "webrtc/libjingle/xmpp/xmppclientsettings.h"
+
+namespace buzz {
+namespace {
+
+const uint32_t MSG_LOGIN = 1;
+const uint32_t MSG_DISCONNECT = 2;
+
+struct LoginData: public rtc::MessageData {
+ LoginData(const buzz::XmppClientSettings& s) : xcs(s) {}
+ virtual ~LoginData() {}
+
+ buzz::XmppClientSettings xcs;
+};
+
+} // namespace
+
+XmppThread::XmppThread() {
+ pump_ = new buzz::XmppPump(this);
+}
+
+XmppThread::~XmppThread() {
+ Stop();
+ delete pump_;
+}
+
+void XmppThread::ProcessMessages(int cms) {
+ rtc::Thread::ProcessMessages(cms);
+}
+
+void XmppThread::Login(const buzz::XmppClientSettings& xcs) {
+ Post(this, MSG_LOGIN, new LoginData(xcs));
+}
+
+void XmppThread::Disconnect() {
+ Post(this, MSG_DISCONNECT);
+}
+
+void XmppThread::OnStateChange(buzz::XmppEngine::State state) {
+}
+
+void XmppThread::OnMessage(rtc::Message* pmsg) {
+ if (pmsg->message_id == MSG_LOGIN) {
+ ASSERT(pmsg->pdata != NULL);
+ LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
+ pump_->DoLogin(data->xcs, new XmppSocket(buzz::TLS_DISABLED),
+ new XmppAuth());
+ delete data;
+ } else if (pmsg->message_id == MSG_DISCONNECT) {
+ pump_->DoDisconnect();
+ } else {
+ ASSERT(false);
+ }
+}
+
+} // namespace buzz
diff --git a/webrtc/libjingle/xmpp/xmppthread.h b/webrtc/libjingle/xmpp/xmppthread.h
new file mode 100644
index 0000000000..8017925049
--- /dev/null
+++ b/webrtc/libjingle/xmpp/xmppthread.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
+#define WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
+
+#include "webrtc/libjingle/xmpp/xmppclientsettings.h"
+#include "webrtc/libjingle/xmpp/xmppengine.h"
+#include "webrtc/libjingle/xmpp/xmpppump.h"
+#include "webrtc/libjingle/xmpp/xmppsocket.h"
+#include "webrtc/base/thread.h"
+
+namespace buzz {
+
+class XmppThread:
+ public rtc::Thread, buzz::XmppPumpNotify, rtc::MessageHandler {
+public:
+ XmppThread();
+ ~XmppThread();
+
+ buzz::XmppClient* client() { return pump_->client(); }
+
+ void ProcessMessages(int cms);
+
+ void Login(const buzz::XmppClientSettings & xcs);
+ void Disconnect();
+
+private:
+ buzz::XmppPump* pump_;
+
+ void OnStateChange(buzz::XmppEngine::State state);
+ void OnMessage(rtc::Message* pmsg);
+};
+
+} // namespace buzz
+
+#endif // WEBRTC_LIBJINGLE_XMPP_XMPPTHREAD_H_
+