diff options
Diffstat (limited to 'webrtc/libjingle/xmpp')
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(¤t_time_seconds); + struct tm* current_time = gmtime(¤t_time_seconds); + char output[256]; + strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time); + result->AddAttr(kQnStamp, output, 1); + } + } + + return result; +} + + +} diff --git a/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!<>&</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_ + |