1/*
2 * libjingle
3 * Copyright 2004--2005, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <algorithm>
29#include <iostream>
30#include <map>
31#include <sstream>
32#include <string>
33#include <vector>
34#include "talk/xmpp/chatroommodule.h"
35#include "talk/xmpp/constants.h"
36#include "talk/xmpp/moduleimpl.h"
37#include "webrtc/base/common.h"
38
39namespace buzz {
40
41// forward declarations
42class XmppChatroomImpl;
43class XmppChatroomMemberImpl;
44
45//! Module that encapsulates multiple chatrooms.
46//! Each chatroom is represented by an XmppChatroomImpl instance
47class XmppChatroomModuleImpl : public XmppChatroomModule,
48  public XmppModuleImpl, public XmppIqHandler {
49public:
50  IMPLEMENT_XMPPMODULE
51
52   // Creates a chatroom with specified Jid
53  XmppChatroomModuleImpl();
54  ~XmppChatroomModuleImpl();
55
56  // XmppChatroomModule
57  virtual XmppReturnStatus set_chatroom_handler(XmppChatroomHandler* handler);
58  virtual XmppChatroomHandler* chatroom_handler();
59  virtual XmppReturnStatus set_chatroom_jid(const Jid& chatroom_jid);
60  virtual const Jid& chatroom_jid() const;
61  virtual XmppReturnStatus set_nickname(const std::string& nickname);
62  virtual const std::string& nickname() const;
63  virtual const Jid member_jid() const;
64  virtual XmppReturnStatus RequestEnterChatroom(const std::string& password,
65      const std::string& client_version,
66      const std::string& locale);
67  virtual XmppReturnStatus RequestExitChatroom();
68  virtual XmppReturnStatus RequestConnectionStatusChange(
69      XmppPresenceConnectionStatus connection_status);
70  virtual size_t GetChatroomMemberCount();
71  virtual XmppReturnStatus CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator);
72  virtual const std::string subject();
73  virtual XmppChatroomState state() { return chatroom_state_; }
74  virtual XmppReturnStatus SendMessage(const XmlElement& message);
75
76  // XmppModule
77  virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) {RTC_UNUSED2(cookie, pelStanza);}
78  virtual bool HandleStanza(const XmlElement *);
79
80private:
81  friend class XmppChatroomMemberEnumeratorImpl;
82
83  XmppReturnStatus ServerChangeMyPresence(const XmlElement& presence);
84  XmppReturnStatus ClientChangeMyPresence(XmppChatroomState new_state);
85  XmppReturnStatus ChangePresence(XmppChatroomState new_state, const XmlElement* presence, bool isServer);
86  XmppReturnStatus ServerChangedOtherPresence(const XmlElement& presence_element);
87  XmppChatroomEnteredStatus GetEnterFailureFromXml(const XmlElement* presence);
88  XmppChatroomExitedStatus GetExitFailureFromXml(const XmlElement* presence);
89
90  bool CheckEnterChatroomStateOk();
91
92  void FireEnteredStatus(const XmlElement* presence,
93                         XmppChatroomEnteredStatus status);
94  void FireExitStatus(XmppChatroomExitedStatus status);
95  void FireMessageReceived(const XmlElement& message);
96  void FireMemberEntered(const XmppChatroomMember* entered_member);
97  void FireMemberChanged(const XmppChatroomMember* changed_member);
98  void FireMemberExited(const XmppChatroomMember* exited_member);
99
100
101  typedef std::map<Jid, XmppChatroomMemberImpl*> JidMemberMap;
102
103  XmppChatroomHandler*              chatroom_handler_;
104  Jid                               chatroom_jid_;
105  std::string                       nickname_;
106  XmppChatroomState                 chatroom_state_;
107  JidMemberMap                      chatroom_jid_members_;
108  int                               chatroom_jid_members_version_;
109};
110
111
112class XmppChatroomMemberImpl : public XmppChatroomMember {
113public:
114  ~XmppChatroomMemberImpl() {}
115  XmppReturnStatus SetPresence(const XmppPresence* presence);
116
117  // XmppChatroomMember
118  const Jid member_jid() const;
119  const Jid full_jid() const;
120  const std::string name() const;
121  const XmppPresence* presence() const;
122
123private:
124  rtc::scoped_ptr<XmppPresence>  presence_;
125};
126
127class XmppChatroomMemberEnumeratorImpl :
128        public XmppChatroomMemberEnumerator  {
129public:
130  XmppChatroomMemberEnumeratorImpl(XmppChatroomModuleImpl::JidMemberMap* chatroom_jid_members,
131                                        int* map_version);
132
133  // XmppChatroomMemberEnumerator
134  virtual XmppChatroomMember* current();
135  virtual bool Next();
136  virtual bool Prev();
137  virtual bool IsValid();
138  virtual bool IsBeforeBeginning();
139  virtual bool IsAfterEnd();
140
141private:
142  XmppChatroomModuleImpl::JidMemberMap*           map_;
143  int                                             map_version_created_;
144  int*                                            map_version_;
145  XmppChatroomModuleImpl::JidMemberMap::iterator  iterator_;
146  bool                                            before_beginning_;
147};
148
149
150// XmppChatroomModuleImpl ------------------------------------------------
151XmppChatroomModule *
152XmppChatroomModule::Create() {
153  return new XmppChatroomModuleImpl();
154}
155
156XmppChatroomModuleImpl::XmppChatroomModuleImpl() :
157  chatroom_handler_(NULL),
158  chatroom_jid_(STR_EMPTY),
159  chatroom_state_(XMPP_CHATROOM_STATE_NOT_IN_ROOM),
160  chatroom_jid_members_version_(0) {
161}
162
163XmppChatroomModuleImpl::~XmppChatroomModuleImpl() {
164  JidMemberMap::iterator iterator = chatroom_jid_members_.begin();
165  while (iterator != chatroom_jid_members_.end()) {
166    delete iterator->second;
167    iterator++;
168  }
169}
170
171
172bool
173XmppChatroomModuleImpl::HandleStanza(const XmlElement* stanza) {
174  ASSERT(engine() != NULL);
175
176  // we handle stanzas that are for one of our chatrooms
177  Jid from_jid = Jid(stanza->Attr(QN_FROM));
178  // see if it's one of our chatrooms
179  if (chatroom_jid_ != from_jid.BareJid()) {
180    return false; // not one of our chatrooms
181  } else {
182    // handle presence stanza
183    if (stanza->Name() == QN_PRESENCE) {
184      if (from_jid == member_jid()) {
185        ServerChangeMyPresence(*stanza);
186      } else {
187        ServerChangedOtherPresence(*stanza);
188      }
189    } else if (stanza->Name() == QN_MESSAGE) {
190      FireMessageReceived(*stanza);
191    }
192    return true;
193  }
194}
195
196
197XmppReturnStatus
198XmppChatroomModuleImpl::set_chatroom_handler(XmppChatroomHandler* handler) {
199  // Calling with NULL removes the handler.
200  chatroom_handler_ = handler;
201  return XMPP_RETURN_OK;
202}
203
204
205XmppChatroomHandler*
206XmppChatroomModuleImpl::chatroom_handler() {
207  return chatroom_handler_;
208}
209
210XmppReturnStatus
211XmppChatroomModuleImpl::set_chatroom_jid(const Jid& chatroom_jid) {
212  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
213    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
214  }
215  if (chatroom_jid != chatroom_jid.BareJid()) {
216    // chatroom_jid must be a bare jid
217    return XMPP_RETURN_BADARGUMENT;
218  }
219
220  chatroom_jid_ = chatroom_jid;
221  return XMPP_RETURN_OK;
222}
223
224const Jid&
225XmppChatroomModuleImpl::chatroom_jid() const {
226  return chatroom_jid_;
227}
228
229 XmppReturnStatus
230 XmppChatroomModuleImpl::set_nickname(const std::string& nickname) {
231  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM) {
232    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
233  }
234  nickname_ = nickname;
235  return XMPP_RETURN_OK;
236 }
237
238 const std::string&
239 XmppChatroomModuleImpl::nickname() const {
240  return nickname_;
241 }
242
243const Jid
244XmppChatroomModuleImpl::member_jid() const {
245  return Jid(chatroom_jid_.node(), chatroom_jid_.domain(), nickname_);
246}
247
248
249bool
250XmppChatroomModuleImpl::CheckEnterChatroomStateOk() {
251  if (chatroom_jid_.IsValid() == false) {
252    ASSERT(0);
253    return false;
254  }
255  if (nickname_ == STR_EMPTY) {
256    ASSERT(0);
257    return false;
258  }
259  return true;
260}
261
262std::string GetAttrValueFor(XmppPresenceConnectionStatus connection_status) {
263  switch (connection_status) {
264    default:
265    case XMPP_CONNECTION_STATUS_UNKNOWN:
266      return "";
267    case XMPP_CONNECTION_STATUS_CONNECTING:
268      return STR_PSTN_CONFERENCE_STATUS_CONNECTING;
269    case XMPP_CONNECTION_STATUS_CONNECTED:
270      return STR_PSTN_CONFERENCE_STATUS_CONNECTED;
271  }
272}
273
274XmppReturnStatus
275XmppChatroomModuleImpl::RequestEnterChatroom(
276    const std::string& password,
277    const std::string& client_version,
278    const std::string& locale) {
279  RTC_UNUSED(password);
280  if (!engine())
281    return XMPP_RETURN_BADSTATE;
282
283  if (chatroom_state_ != XMPP_CHATROOM_STATE_NOT_IN_ROOM)
284    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
285
286  if (CheckEnterChatroomStateOk() == false) {
287    return XMPP_RETURN_BADSTATE;
288  }
289
290  // entering a chatroom is a presence request to the server
291  XmlElement element(QN_PRESENCE);
292  element.AddAttr(QN_TO, member_jid().Str());
293
294  XmlElement* muc_x = new XmlElement(QN_MUC_X);
295  element.AddElement(muc_x);
296
297  if (!client_version.empty()) {
298    XmlElement* client_version_element = new XmlElement(QN_CLIENT_VERSION,
299                                                        false);
300    client_version_element->SetBodyText(client_version);
301    muc_x->AddElement(client_version_element);
302  }
303
304  if (!locale.empty()) {
305    XmlElement* locale_element = new XmlElement(QN_LOCALE, false);
306
307    locale_element->SetBodyText(locale);
308    muc_x->AddElement(locale_element);
309  }
310
311  XmppReturnStatus status = engine()->SendStanza(&element);
312  if (status == XMPP_RETURN_OK) {
313    return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_ENTER);
314  }
315  return status;
316}
317
318XmppReturnStatus
319XmppChatroomModuleImpl::RequestExitChatroom() {
320  if (!engine())
321    return XMPP_RETURN_BADSTATE;
322
323  // exiting a chatroom is a presence request to the server
324  XmlElement element(QN_PRESENCE);
325  element.AddAttr(QN_TO, member_jid().Str());
326  element.AddAttr(QN_TYPE, "unavailable");
327  XmppReturnStatus status = engine()->SendStanza(&element);
328  if (status == XMPP_RETURN_OK &&
329      chatroom_state_ == XMPP_CHATROOM_STATE_IN_ROOM) {
330    return ClientChangeMyPresence(XMPP_CHATROOM_STATE_REQUESTED_EXIT);
331  }
332  return status;
333}
334
335XmppReturnStatus
336XmppChatroomModuleImpl::RequestConnectionStatusChange(
337    XmppPresenceConnectionStatus connection_status) {
338  if (!engine())
339    return XMPP_RETURN_BADSTATE;
340
341  if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
342    // $TODO - this isn't a bad state, it's a bad call,  diff error code?
343    return XMPP_RETURN_BADSTATE;
344  }
345
346  if (CheckEnterChatroomStateOk() == false) {
347    return XMPP_RETURN_BADSTATE;
348  }
349
350  // entering a chatroom is a presence request to the server
351  XmlElement element(QN_PRESENCE);
352  element.AddAttr(QN_TO, member_jid().Str());
353  element.AddElement(new XmlElement(QN_MUC_X));
354  if (connection_status != XMPP_CONNECTION_STATUS_UNKNOWN) {
355    XmlElement* con_status_element =
356        new XmlElement(QN_GOOGLE_PSTN_CONFERENCE_STATUS);
357    con_status_element->AddAttr(QN_STATUS, GetAttrValueFor(connection_status));
358    element.AddElement(con_status_element);
359  }
360  XmppReturnStatus status = engine()->SendStanza(&element);
361
362  return status;
363}
364
365size_t
366XmppChatroomModuleImpl::GetChatroomMemberCount() {
367  return chatroom_jid_members_.size();
368}
369
370XmppReturnStatus
371XmppChatroomModuleImpl::CreateMemberEnumerator(XmppChatroomMemberEnumerator** enumerator) {
372  *enumerator = new XmppChatroomMemberEnumeratorImpl(&chatroom_jid_members_, &chatroom_jid_members_version_);
373  return XMPP_RETURN_OK;
374}
375
376const std::string
377XmppChatroomModuleImpl::subject() {
378  return ""; //NYI
379}
380
381XmppReturnStatus
382XmppChatroomModuleImpl::SendMessage(const XmlElement& message) {
383  XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
384
385  // can only send a message if we're in the room
386  if (chatroom_state_ != XMPP_CHATROOM_STATE_IN_ROOM) {
387    return XMPP_RETURN_BADSTATE; // $TODO - this isn't a bad state, it's a bad call,  diff error code?
388  }
389
390  if (message.Name() != QN_MESSAGE) {
391    IFR(XMPP_RETURN_BADARGUMENT);
392  }
393
394  const std::string& type = message.Attr(QN_TYPE);
395  if (type != "groupchat") {
396    IFR(XMPP_RETURN_BADARGUMENT);
397  }
398
399  if (message.HasAttr(QN_FROM)) {
400    IFR(XMPP_RETURN_BADARGUMENT);
401  }
402
403  if (message.Attr(QN_TO) != chatroom_jid_.Str()) {
404    IFR(XMPP_RETURN_BADARGUMENT);
405  }
406
407  IFR(engine()->SendStanza(&message));
408
409  return xmpp_status;
410}
411
412enum TransitionType {
413  TRANSITION_TYPE_NONE                 = 0,
414  TRANSITION_TYPE_ENTER_SUCCESS        = 1,
415  TRANSITION_TYPE_ENTER_FAILURE        = 2,
416  TRANSITION_TYPE_EXIT_VOLUNTARILY     = 3,
417  TRANSITION_TYPE_EXIT_INVOLUNTARILY   = 4,
418};
419
420struct StateTransitionDescription {
421  XmppChatroomState old_state;
422  XmppChatroomState new_state;
423  bool              is_valid_server_transition;
424  bool              is_valid_client_transition;
425  TransitionType    transition_type;
426};
427
428StateTransitionDescription Transitions[] = {
429  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, true,  TRANSITION_TYPE_NONE, },
430  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_IN_ROOM,         false, false, TRANSITION_TYPE_ENTER_SUCCESS, },
431  { XMPP_CHATROOM_STATE_NOT_IN_ROOM,     XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, false, TRANSITION_TYPE_NONE, },
432  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_ENTER_FAILURE, },
433  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_IN_ROOM,         true,  false, TRANSITION_TYPE_ENTER_SUCCESS, },
434  { XMPP_CHATROOM_STATE_REQUESTED_ENTER, XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, false, TRANSITION_TYPE_NONE, },
435  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_EXIT_INVOLUNTARILY,  },
436  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
437  { XMPP_CHATROOM_STATE_IN_ROOM,         XMPP_CHATROOM_STATE_REQUESTED_EXIT,  false, true,  TRANSITION_TYPE_NONE, },
438  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_NOT_IN_ROOM,     true,  false, TRANSITION_TYPE_EXIT_VOLUNTARILY, },
439  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_REQUESTED_ENTER, false, false, TRANSITION_TYPE_NONE, },
440  { XMPP_CHATROOM_STATE_REQUESTED_EXIT,  XMPP_CHATROOM_STATE_IN_ROOM,         false, false, TRANSITION_TYPE_NONE, },
441};
442
443
444
445void
446XmppChatroomModuleImpl::FireEnteredStatus(const XmlElement* presence,
447                                          XmppChatroomEnteredStatus status) {
448  if (chatroom_handler_) {
449    rtc::scoped_ptr<XmppPresence> xmpp_presence(XmppPresence::Create());
450    xmpp_presence->set_raw_xml(presence);
451    chatroom_handler_->ChatroomEnteredStatus(this, xmpp_presence.get(), status);
452  }
453}
454
455void
456XmppChatroomModuleImpl::FireExitStatus(XmppChatroomExitedStatus status) {
457  if (chatroom_handler_)
458    chatroom_handler_->ChatroomExitedStatus(this, status);
459}
460
461void
462XmppChatroomModuleImpl::FireMessageReceived(const XmlElement& message) {
463  if (chatroom_handler_)
464    chatroom_handler_->MessageReceived(this, message);
465}
466
467void
468XmppChatroomModuleImpl::FireMemberEntered(const XmppChatroomMember* entered_member) {
469  if (chatroom_handler_)
470    chatroom_handler_->MemberEntered(this, entered_member);
471}
472
473void
474XmppChatroomModuleImpl::FireMemberChanged(
475    const XmppChatroomMember* changed_member) {
476  if (chatroom_handler_)
477    chatroom_handler_->MemberChanged(this, changed_member);
478}
479
480void
481XmppChatroomModuleImpl::FireMemberExited(const XmppChatroomMember* exited_member) {
482  if (chatroom_handler_)
483    chatroom_handler_->MemberExited(this, exited_member);
484}
485
486
487XmppReturnStatus
488XmppChatroomModuleImpl::ServerChangedOtherPresence(const XmlElement&
489                                                   presence_element) {
490  XmppReturnStatus xmpp_status = XMPP_RETURN_OK;
491  rtc::scoped_ptr<XmppPresence> presence(XmppPresence::Create());
492  IFR(presence->set_raw_xml(&presence_element));
493
494  JidMemberMap::iterator pos = chatroom_jid_members_.find(presence->jid());
495
496  if (pos == chatroom_jid_members_.end()) {
497    if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
498      XmppChatroomMemberImpl* member = new XmppChatroomMemberImpl();
499      member->SetPresence(presence.get());
500      chatroom_jid_members_.insert(std::make_pair(member->member_jid(), member));
501      chatroom_jid_members_version_++;
502      FireMemberEntered(member);
503    }
504  } else {
505    XmppChatroomMemberImpl* member = pos->second;
506    if (presence->available() == XMPP_PRESENCE_AVAILABLE) {
507      member->SetPresence(presence.get());
508      chatroom_jid_members_version_++;
509      FireMemberChanged(member);
510    }
511    else if (presence->available() == XMPP_PRESENCE_UNAVAILABLE) {
512      member->SetPresence(presence.get());
513      chatroom_jid_members_.erase(pos);
514      chatroom_jid_members_version_++;
515      FireMemberExited(member);
516      delete member;
517    }
518  }
519
520  return xmpp_status;
521}
522
523XmppReturnStatus
524XmppChatroomModuleImpl::ClientChangeMyPresence(XmppChatroomState new_state) {
525  return ChangePresence(new_state, NULL, false);
526}
527
528XmppReturnStatus
529XmppChatroomModuleImpl::ServerChangeMyPresence(const XmlElement& presence) {
530   XmppChatroomState new_state;
531
532   if (presence.HasAttr(QN_TYPE) == false) {
533      new_state = XMPP_CHATROOM_STATE_IN_ROOM;
534   } else {
535     new_state = XMPP_CHATROOM_STATE_NOT_IN_ROOM;
536   }
537  return ChangePresence(new_state, &presence, true);
538
539}
540
541XmppReturnStatus
542XmppChatroomModuleImpl::ChangePresence(XmppChatroomState new_state,
543                                       const XmlElement* presence,
544                                       bool isServer) {
545  RTC_UNUSED(presence);
546
547  XmppChatroomState old_state = chatroom_state_;
548
549  // do nothing if state hasn't changed
550  if (old_state == new_state)
551    return XMPP_RETURN_OK;
552
553  // find the right transition description
554  StateTransitionDescription* transition_desc = NULL;
555  for (int i=0; i < ARRAY_SIZE(Transitions); i++) {
556    if (Transitions[i].old_state == old_state &&
557        Transitions[i].new_state == new_state) {
558        transition_desc = &Transitions[i];
559        break;
560    }
561  }
562
563  if (transition_desc == NULL) {
564    ASSERT(0);
565    return XMPP_RETURN_BADSTATE;
566  }
567
568  // we assert for any invalid transition states, and we'll
569  if (isServer) {
570    // $TODO send original stanza back to server and log an error?
571    // Disable the assert because of b/6133072
572    // ASSERT(transition_desc->is_valid_server_transition);
573    if (!transition_desc->is_valid_server_transition) {
574      return XMPP_RETURN_BADSTATE;
575    }
576  } else {
577    if (transition_desc->is_valid_client_transition == false) {
578      ASSERT(0);
579      return XMPP_RETURN_BADARGUMENT;
580    }
581  }
582
583  // set the new state and then fire any notifications to the handler
584  chatroom_state_ = new_state;
585
586  switch (transition_desc->transition_type) {
587    case TRANSITION_TYPE_ENTER_SUCCESS:
588      FireEnteredStatus(presence, XMPP_CHATROOM_ENTERED_SUCCESS);
589      break;
590    case TRANSITION_TYPE_ENTER_FAILURE:
591      FireEnteredStatus(presence, GetEnterFailureFromXml(presence));
592      break;
593    case TRANSITION_TYPE_EXIT_INVOLUNTARILY:
594      FireExitStatus(GetExitFailureFromXml(presence));
595      break;
596    case TRANSITION_TYPE_EXIT_VOLUNTARILY:
597      FireExitStatus(XMPP_CHATROOM_EXITED_REQUESTED);
598      break;
599    case TRANSITION_TYPE_NONE:
600      break;
601  }
602
603  return XMPP_RETURN_OK;
604}
605
606XmppChatroomEnteredStatus
607XmppChatroomModuleImpl::GetEnterFailureFromXml(const XmlElement* presence) {
608  XmppChatroomEnteredStatus status = XMPP_CHATROOM_ENTERED_FAILURE_UNSPECIFIED;
609  const XmlElement* error = presence->FirstNamed(QN_ERROR);
610  if (error != NULL && error->HasAttr(QN_CODE)) {
611    int code = atoi(error->Attr(QN_CODE).c_str());
612    switch (code) {
613      case 401: status = XMPP_CHATROOM_ENTERED_FAILURE_PASSWORD_REQUIRED; break;
614      case 403: {
615        status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BANNED;
616        if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKED)) {
617          status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKED;
618        } else if (error->FirstNamed(QN_GOOGLE_SESSION_BLOCKING)) {
619          status = XMPP_CHATROOM_ENTERED_FAILURE_MEMBER_BLOCKING;
620        }
621        break;
622      }
623      case 405: status = XMPP_CHATROOM_ENTERED_FAILURE_ROOM_LOCKED; break;
624      case 406: status = XMPP_CHATROOM_ENTERED_FAILURE_OUTDATED_CLIENT; break;
625      case 407: status = XMPP_CHATROOM_ENTERED_FAILURE_NOT_A_MEMBER; break;
626      case 409: status = XMPP_CHATROOM_ENTERED_FAILURE_NICKNAME_CONFLICT; break;
627      // http://xmpp.org/extensions/xep-0045.html#enter-maxusers
628      case 503: status = XMPP_CHATROOM_ENTERED_FAILURE_MAX_USERS; break;
629    }
630  }
631  return status;
632}
633
634XmppChatroomExitedStatus
635XmppChatroomModuleImpl::GetExitFailureFromXml(const XmlElement* presence) {
636  XmppChatroomExitedStatus status = XMPP_CHATROOM_EXITED_UNSPECIFIED;
637  const XmlElement* muc_user = presence->FirstNamed(QN_MUC_USER_X);
638  if (muc_user != NULL) {
639    const XmlElement* user_status = muc_user->FirstNamed(QN_MUC_USER_STATUS);
640    if (user_status != NULL && user_status->HasAttr(QN_CODE)) {
641      int code = atoi(user_status->Attr(QN_CODE).c_str());
642      switch (code) {
643        case 307: status = XMPP_CHATROOM_EXITED_KICKED; break;
644        case 322: status = XMPP_CHATROOM_EXITED_NOT_A_MEMBER; break;
645        case 332: status = XMPP_CHATROOM_EXITED_SYSTEM_SHUTDOWN; break;
646      }
647    }
648  }
649  return status;
650}
651
652XmppReturnStatus
653XmppChatroomMemberImpl::SetPresence(const XmppPresence* presence) {
654  ASSERT(presence != NULL);
655
656  // copy presence
657  presence_.reset(XmppPresence::Create());
658  presence_->set_raw_xml(presence->raw_xml());
659  return XMPP_RETURN_OK;
660}
661
662const Jid
663XmppChatroomMemberImpl::member_jid() const {
664  return presence_->jid();
665}
666
667const Jid
668XmppChatroomMemberImpl::full_jid() const {
669  return Jid("");
670}
671
672const std::string
673XmppChatroomMemberImpl::name() const {
674  return member_jid().resource();
675}
676
677const XmppPresence*
678XmppChatroomMemberImpl::presence() const {
679  return presence_.get();
680}
681
682
683// XmppChatroomMemberEnumeratorImpl --------------------------------------
684XmppChatroomMemberEnumeratorImpl::XmppChatroomMemberEnumeratorImpl(
685        XmppChatroomModuleImpl::JidMemberMap* map, int* map_version) {
686  map_ = map;
687  map_version_ = map_version;
688  map_version_created_ = *map_version_;
689  iterator_ = map->begin();
690  before_beginning_ = true;
691}
692
693XmppChatroomMember*
694XmppChatroomMemberEnumeratorImpl::current() {
695  if (IsValid() == false) {
696    return NULL;
697  } else if (IsBeforeBeginning() || IsAfterEnd()) {
698    return NULL;
699  } else {
700    return iterator_->second;
701  }
702}
703
704bool
705XmppChatroomMemberEnumeratorImpl::Prev() {
706  if (IsValid() == false) {
707    return false;
708  } else if (IsBeforeBeginning()) {
709    return false;
710  } else if (iterator_ == map_->begin()) {
711    before_beginning_ = true;
712    return false;
713  } else {
714    iterator_--;
715    return current() != NULL;
716  }
717}
718
719bool
720XmppChatroomMemberEnumeratorImpl::Next() {
721  if (IsValid() == false) {
722    return false;
723  } else if (IsBeforeBeginning()) {
724    before_beginning_ = false;
725    iterator_ = map_->begin();
726    return current() != NULL;
727  } else if (IsAfterEnd()) {
728    return false;
729  } else {
730    iterator_++;
731    return current() != NULL;
732  }
733}
734
735bool
736XmppChatroomMemberEnumeratorImpl::IsValid() {
737  return map_version_created_ == *map_version_;
738}
739
740bool
741XmppChatroomMemberEnumeratorImpl::IsBeforeBeginning() {
742  return before_beginning_;
743}
744
745bool
746XmppChatroomMemberEnumeratorImpl::IsAfterEnd() {
747  return (iterator_ == map_->end());
748}
749
750
751
752} // namespace buzz
753