blob: 0fbbe7eddb49c06dbaba3d882afac73f4ac04b04 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2005, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <sstream>
#include <iostream>
#include "talk/base/common.h"
#include "talk/xmpp/constants.h"
#include "talk/xmpp/moduleimpl.h"
#include "talk/xmpp/chatroommodule.h"
namespace buzz {
// forward declarations
class XmppChatroomImpl;
class XmppChatroomMemberImpl;
//! Module that encapsulates multiple chatrooms.
//! Each chatroom is represented by an XmppChatroomImpl instance
class XmppChatroomModuleImpl : public XmppChatroomModule,
public XmppModuleImpl, public XmppIqHandler {
public:
IMPLEMENT_XMPPMODULE
// Creates a chatroom with specified Jid
XmppChatroomModuleImpl();
~XmppChatroomModuleImpl();
// XmppChatroomModule
virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler);
virtual XmppChatroomHandler* chatroom_handler();
virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid);
virtual const Jid& chatroom_jid() const;
virtual XmppReturnStatus set_nickname(const std::string& nickname);
virtual const std::string& nickname() const;
virtual const Jid member_jid() const;
virtual XmppReturnStatus RequestEnterChatroom(const std::string& password);
virtual XmppReturnStatus RequestExitChatroom();
virtual XmppReturnStatus RequestStatusChange(XmppPresenceShow status,
const std::string& extended_status);
virtual size_t GetChatroomMemberCount();
virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator);
virtual const std::string& subject();
virtual XmppChatroomState state() { return chatroom_state_; }
virtual XmppReturnStatus SendMessage(const XmlElement& message);
// XmppModule
virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {UNUSED2(cookie, pelStanza);}
virtual bool HandleStanza(const XmlElement *);
private:
friend class XmppChatroomMemberEnumeratorImpl;
XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence);
XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state);
XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer);
XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element);
XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence);
XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence);
bool CheckEnterChatroomStateOk();
void FireEnteredStatus(XmppChatroomEnteredStatus status);
void FireExitStatus(XmppChatroomExitedStatus status);
void FireMessageReceived(const XmlElement& message);
void FireMemberEntered(const XmppChatroomMember* entered_member);
void FireMemberExited(const XmppChatroomMember* exited_member);
typedef std::map<Jid, XmppChatroomMemberImpl*> JidMemberMap;
XmppChatroomHandler* chatroom_handler_;
Jid chatroom_jid_;
std::string nickname_;
XmppChatroomState chatroom_state_;
JidMemberMap chatroom_jid_members_;
int chatroom_jid_members_version_;
};
class XmppChatroomMemberImpl : public XmppChatroomMember {
public:
~XmppChatroomMemberImpl() {}
XmppReturnStatus SetPresence(const XmppPresence* presence);
// XmppChatroomMember
const Jid member_jid() const;
const Jid full_jid() const;
const std::string name() const;
const XmppPresence* presence() const;
private:
talk_base::scoped_ptr<XmppPresence> presence_;
};
class XmppChatroomMemberEnumeratorImpl :
public XmppChatroomMemberEnumerator {
public:
XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members,
int* map_version);
// XmppChatroomMemberEnumerator
virtual XmppChatroomMember* current();
virtual bool Next();
virtual bool Prev();
virtual bool IsValid();
virtual bool IsBeforeBeginning();
virtual bool IsAfterEnd();
private:
XmppChatroomModuleImpl::JidMemberMap* map_;
int map_version_created_;
int* map_version_;
XmppChatroomModuleImpl::JidMemberMap::iterator iterator_;
bool before_beginning_;
};
// XmppChatroomModuleImpl ------------------------------------------------
XmppChatroomModule *
XmppChatroomModule::Create() {
return new XmppChatroomModuleImpl();
}
XmppChatroomModuleImpl::XmppChatroomModuleImpl() :
chatroom_handler_(NULL),
chatroom_jid_(STR_EMPTY),
chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM),
chatroom_jid_members_version_(0) {
}
XmppChatroomModuleImpl::~XmppChatroomModuleImpl() {
JidMemberMap::iterator iterator = chatroom_jid_members_.begin();
while (iterator != chatroom_jid_members_.end()) {
delete iterator->second;
iterator++;
}
}
bool
XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) {
ASSERT(engine() != NULL);
// we handle stanzas that are for one of our chatrooms
Jid from_jid = Jid(stanza->Attr(QN_FROM));
// see if it's one of our chatrooms
if (chatroom_jid_ != from_jid.BareJid()) {
return false; // not one of our chatrooms
} else {
// handle presence stanza
if (stanza->Name() == QN_PRESENCE) {
if (from_jid == member_jid()) {
ServerChangeMyPresence(*stanza);
} else {
ServerChangedOtherPresence(*stanza);
}
} else if (stanza->Name() == QN_MESSAGE) {
FireMessageReceived(*stanza);
}
return true;
}
}
XmppReturnStatus
XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) {
// Calling with NULL removes the handler.
chatroom_handler_ = handler;
return XMPP_RETURN_OK;
}
XmppChatroomHandler*
XmppChatroomModuleImpl::chatroom_handler() {
return chatroom_handler_;
}
XmppReturnStatus
XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) {
if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
}
if (chatroom_jid != chatroom_jid.BareJid()) {
// chatroom_jid must be a bare jid
return XMPP_RETURN_BADARGUMENT;
}
chatroom_jid_ = chatroom_jid;
return XMPP_RETURN_OK;
}
const Jid&
XmppChatroomModuleImpl::chatroom_jid() const {
return chatroom_jid_;
}
XmppReturnStatus
XmppChatroomModuleImpl::set_nickname(const std::string& nickname) {
if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
}
nickname_ = nickname;
return XMPP_RETURN_OK;
}
const std::string&
XmppChatroomModuleImpl::nickname() const {
return nickname_;
}
const Jid
XmppChatroomModuleImpl::member_jid() const {
return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_);
}
bool
XmppChatroomModuleImpl::CheckEnterChatroomStateOk() {
if (chatroom_jid_.IsValid() == false) {
ASSERT(0);
return false;
}
if (nickname_ == STR_EMPTY) {
ASSERT(0);
return false;
}
return true;
}
XmppReturnStatus
XmppChatroomModuleImpl::RequestEnterChatroom(const std::string& password) {
UNUSED(password);
if (!engine())
return XMPP_RETURN_BADSTATE;
if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM)
return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
if (CheckEnterChatroomStateOk() == false) {
return XMPP_RETURN_BADSTATE;
}
// entering a chatroom is a presence request to the server
XmlElement element(QN_PRESENCE);
element.AddAttr(QN_TO, member_jid().Str());
element.AddElement(new XmlElement(QN_MUC_X));
XmppReturnStatus status = engine()->SendStanza(&element);
if (status == XMPP_RETURN_OK) {
return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER);
}
return status;
}
XmppReturnStatus
XmppChatroomModuleImpl::RequestExitChatroom() {
if (!engine())
return XMPP_RETURN_BADSTATE;
// currently, can't leave a room unless you've entered
// no way to cancel a pending enter call - is that bad?
if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM)
return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
// exiting a chatroom is a presence request to the server
XmlElement element(QN_PRESENCE);
element.AddAttr(QN_TO, member_jid().Str());
element.AddAttr(QN_TYPE, "unavailable");
XmppReturnStatus status = engine()->SendStanza(&element);
if (status == XMPP_RETURN_OK) {
return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT);
}
return status;
}
XmppReturnStatus
XmppChatroomModuleImpl::RequestStatusChange(XmppPresenceShow status,
const std::string& extended_status) {
UNUSED2(status, extended_status);
return XMPP_RETURN_BADSTATE; //NYI
}
size_t
XmppChatroomModuleImpl::GetChatroomMemberCount() {
return chatroom_jid_members_.size();
}
XmppReturnStatus
XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) {
*enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_);
return XMPP_RETURN_OK;
}
const std::string&
XmppChatroomModuleImpl::subject() {
return STR_EMPTY; //NYI
}
XmppReturnStatus
XmppChatroomModuleImpl::SendMessage(const XmlElement& message) {
XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
// can only send a message if we're in the room
if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call, diff error code?
}
if (message.Name() != QN_MESSAGE) {
IFR(XMPP_RETURN_BADARGUMENT);
}
const std::string& type = message.Attr(QN_TYPE);
if (type != "groupchat") {
IFR(XMPP_RETURN_BADARGUMENT);
}
if (message.HasAttr(QN_FROM)) {
IFR(XMPP_RETURN_BADARGUMENT);
}
if (message.Attr(QN_TO) != chatroom_jid_.Str()) {
IFR(XMPP_RETURN_BADARGUMENT);
}
IFR(engine()->SendStanza(&message));
return xmpp_status;
}
enum TransitionType {
TRANSITION_TYPE_NONE = 0,
TRANSITION_TYPE_ENTER_SUCCESS = 1,
TRANSITION_TYPE_ENTER_FAILURE = 2,
TRANSITION_TYPE_EXIT_VOLUNTARILY = 3,
TRANSITION_TYPE_EXIT_INVOLUNTARILY = 4,
};
struct StateTransitionDescription {
XmppChatroomState old_state;
XmppChatroomState new_state;
bool is_valid_server_transition;
bool is_valid_client_transition;
TransitionType transition_type;
};
StateTransitionDescription Transitions[] = {
{ XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_ENTER_SUCCESS, },
{ XMPP_CHATROOM_STATE_NOT_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_FAILURE, },
{ XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM, true, false, TRANSITION_TYPE_ENTER_SUCCESS, },
{ XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, false, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_INVOLUNTARILY, },
{ XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_IN_ROOM, XMPP_CHATROOM_STATE_REQUESTED_EXIT, false, true, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_NOT_IN_ROOM, true, false, TRANSITION_TYPE_EXIT_VOLUNTARILY, },
{ XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
{ XMPP_CHATROOM_STATE_REQUESTED_EXIT, XMPP_CHATROOM_STATE_IN_ROOM, false, false, TRANSITION_TYPE_NONE, },
};
void
XmppChatroomModuleImpl::FireEnteredStatus(XmppChatroomEnteredStatus status) {
if (chatroom_handler_)
chatroom_handler_->ChatroomEnteredStatus(this, status);
}
void
XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) {
if (chatroom_handler_)
chatroom_handler_->ChatroomExitedStatus(this, status);
}
void
XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) {
if (chatroom_handler_)
chatroom_handler_->MessageReceived(this, message);
}
void
XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) {
// only fire if we're in the room
if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
if (chatroom_handler_)
chatroom_handler_->MemberEntered(this, entered_member);
}
}
void
XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
// only fire if we're in the room
if (chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
if (chatroom_handler_)
chatroom_handler_->MemberExited(this, exited_member);
}
}
XmppReturnStatus
XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement&
presence_element) {
XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
talk_base::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
IFR(presence->set_raw_xml(&presence_element));
JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid());
if (pos == chatroom_jid_members_.end()) {
if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl();
member->SetPresence(presence.get());
chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member));
chatroom_jid_members_version_++;
FireMemberEntered(member);
}
} else {
XmppChatroomMemberImpl* member = pos->second;
if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
member->SetPresence(presence.get());
chatroom_jid_members_version_++;
// $TODO - fire change
}
else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) {
chatroom_jid_members_.erase(pos);
chatroom_jid_members_version_++;
FireMemberExited(member);
delete member;
}
}
return xmpp_status;
}
XmppReturnStatus
XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) {
return ChangePresence(new_state, NULL, false);
}
XmppReturnStatus
XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) {
XmppChatroomState new_state;
if (presence.HasAttr(QN_TYPE) == false) {
new_state = XMPP_CHATROOM_STATE_IN_ROOM;
} else {
new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM;
}
return ChangePresence(new_state, &presence, true);
}
XmppReturnStatus
XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state,
const XmlElement* presence,
bool isServer) {
UNUSED(presence);
XmppChatroomState old_state = chatroom_state_;
// do nothing if state hasn't changed
if (old_state == new_state)
return XMPP_RETURN_OK;
// find the right transition description
StateTransitionDescription* transition_desc = NULL;
for (int i=0; i < ARRAY_SIZE(Transitions); i++) {
if (Transitions[i].old_state == old_state &&
Transitions[i].new_state == new_state) {
transition_desc = &Transitions[i];
break;
}
}
if (transition_desc == NULL) {
ASSERT(0);
return XMPP_RETURN_BADSTATE;
}
// we assert for any invalid transition states, and we'll
if (isServer) {
// $TODO send original stanza back to server and log an error?
ASSERT(transition_desc->is_valid_server_transition);
} else {
if (transition_desc->is_valid_client_transition == false) {
ASSERT(0);
return XMPP_RETURN_BADARGUMENT;
}
}
// set the new state and then fire any notifications to the handler
chatroom_state_ = new_state;
switch (transition_desc->transition_type) {
case TRANSITION_TYPE_ENTER_SUCCESS:
FireEnteredStatus(XMPP_CHATROOM_ENTERED_SUCCESS);
break;
case TRANSITION_TYPE_ENTER_FAILURE:
FireEnteredStatus(GetEnterFailureFromXml(presence));
break;
case TRANSITION_TYPE_EXIT_INVOLUNTARILY:
FireExitStatus(GetExitFailureFromXml(presence));
break;
case TRANSITION_TYPE_EXIT_VOLUNTARILY:
FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED);
break;
case TRANSITION_TYPE_NONE:
break;
}
return XMPP_RETURN_OK;
}
XmppChatroomEnteredStatus
XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) {
XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED;
const XmlElement* error = presence->FirstNamed(QN_ERROR);
if (error != NULL && error->HasAttr(QN_CODE)) {
int code = atoi(error->Attr(QN_CODE).c_str());
switch (code) {
case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break;
case 403: status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED; break;
case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
}
}
return status;
}
XmppChatroomExitedStatus
XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
const XmlElement* error = presence->FirstNamed(QN_ERROR);
if (error != NULL && error->HasAttr(QN_CODE)) {
int code = atoi(error->Attr(QN_CODE).c_str());
switch (code) {
case 307: status = XMPP_CHATROOM_EXITED_KICKED; break;
case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break;
case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break;
}
}
return status;
}
XmppReturnStatus
XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) {
ASSERT(presence != NULL);
// copy presence
presence_.reset(XmppPresence::Create());
presence_->set_raw_xml(presence->raw_xml());
return XMPP_RETURN_OK;
}
const Jid
XmppChatroomMemberImpl::member_jid() const {
return presence_->jid();
}
const Jid
XmppChatroomMemberImpl::full_jid() const {
return Jid("");
}
const std::string
XmppChatroomMemberImpl::name() const {
return member_jid().resource();
}
const XmppPresence*
XmppChatroomMemberImpl::presence() const {
return presence_.get();
}
// XmppChatroomMemberEnumeratorImpl --------------------------------------
XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl(
XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) {
map_ = map;
map_version_ = map_version;
map_version_created_ = *map_version_;
iterator_ = map->begin();
before_beginning_ = true;
}
XmppChatroomMember*
XmppChatroomMemberEnumeratorImpl::current() {
if (IsValid() == false) {
return NULL;
} else if (IsBeforeBeginning() || IsAfterEnd()) {
return NULL;
} else {
return iterator_->second;
}
}
bool
XmppChatroomMemberEnumeratorImpl::Prev() {
if (IsValid() == false) {
return false;
} else if (IsBeforeBeginning()) {
return false;
} else if (iterator_ == map_->begin()) {
before_beginning_ = true;
return false;
} else {
iterator_--;
return current() != NULL;
}
}
bool
XmppChatroomMemberEnumeratorImpl::Next() {
if (IsValid() == false) {
return false;
} else if (IsBeforeBeginning()) {
before_beginning_ = false;
iterator_ = map_->begin();
return current() != NULL;
} else if (IsAfterEnd()) {
return false;
} else {
iterator_++;
return current() != NULL;
}
}
bool
XmppChatroomMemberEnumeratorImpl::IsValid() {
return map_version_created_ == *map_version_;
}
bool
XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() {
return before_beginning_;
}
bool
XmppChatroomMemberEnumeratorImpl::IsAfterEnd() {
return (iterator_ == map_->end());
}
} // namespace buzz