]> granicus.if.org Git - ejabberd/commitdiff
Rewrite mod_mam and mod_muc to use XML generator
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 25 Jul 2016 10:50:30 +0000 (13:50 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 25 Jul 2016 10:50:30 +0000 (13:50 +0300)
17 files changed:
include/mod_muc_room.hrl
include/xmpp_codec.hrl
src/ejabberd_router.erl
src/mod_disco.erl
src/mod_last.erl
src/mod_mam.erl
src/mod_mam_mnesia.erl
src/mod_mam_sql.erl
src/mod_muc.erl
src/mod_muc_admin.erl
src/mod_muc_log.erl
src/mod_muc_room.erl
src/mod_private.erl
src/xmpp.erl
src/xmpp_codec.erl
src/xmpp_util.erl
tools/xmpp_codec.spec

index d985f3f3b3a0c2448e4576b5e3be577ee1af8549..fc20f44c6c56858482e06e5ecdb81625128fd83a 100644 (file)
@@ -71,6 +71,7 @@
 -type config() :: #config{}.
 
 -type role() :: moderator | participant | visitor | none.
+-type affiliation() :: admin | member | outcast | owner | none.
 
 -record(user,
 {
                            host = <<>> :: binary() | '_' | '$2'}).
 
 -type muc_online_users() :: #muc_online_users{}.
-
--type muc_room_state() :: #state{}.
index 7b8275ed575ce62691ea9f49c3db010467f6ac83..85601035d02ce93ef589ba196410282266c23eb5 100644 (file)
 -record(csi, {type :: active | inactive}).
 -type csi() :: #csi{}.
 
--record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
-                      'store' | 'no-permanent-store'}).
+-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
+                      'no-permanent-store' | 'no-permanent-storage'}).
 -type hint() :: #hint{}.
 
 -record(feature_register, {}).
 -type feature_register() :: #feature_register{}.
 
+-record(address, {type :: 'bcc' | 'cc' | 'noreply' | 'ofrom' | 'replyroom' | 'replyto' | 'to',
+                  jid :: any(),
+                  desc :: binary(),
+                  node :: binary(),
+                  delivered :: any()}).
+-type address() :: #address{}.
+
 -record(sasl_success, {text :: any()}).
 -type sasl_success() :: #sasl_success{}.
 
@@ -55,6 +62,9 @@
                  stored :: non_neg_integer()}).
 -type expire() :: #expire{}.
 
+-record(muc_unsubscribe, {}).
+-type muc_unsubscribe() :: #muc_unsubscribe{}.
+
 -record(pubsub_unsubscribe, {node :: binary(),
                              jid :: any(),
                              subid :: binary()}).
@@ -81,7 +91,7 @@
                              type :: 'member' | 'none' | 'outcast' | 'owner' | 'publish-only' | 'publisher'}).
 -type pubsub_affiliation() :: #pubsub_affiliation{}.
 
--record(muc_decline, {reason :: binary(),
+-record(muc_decline, {reason = <<>> :: 'undefined' | binary(),
                       from :: any(),
                       to :: any()}).
 -type muc_decline() :: #muc_decline{}.
                xmlns :: binary()}).
 -type sm_a() :: #sm_a{}.
 
+-record(muc_subscribe, {nick :: binary(),
+                        events = [] :: [binary()]}).
+-type muc_subscribe() :: #muc_subscribe{}.
+
+-record(stanza_id, {by :: any(),
+                    id :: binary()}).
+-type stanza_id() :: #stanza_id{}.
+
 -record(starttls_proceed, {}).
 -type starttls_proceed() :: #starttls_proceed{}.
 
+-record(client_id, {id :: binary()}).
+-type client_id() :: #client_id{}.
+
 -record(sm_resumed, {h :: non_neg_integer(),
                      previd :: binary(),
                      xmlns :: binary()}).
 -record(gone, {uri :: binary()}).
 -type gone() :: #gone{}.
 
+-record(x_conference, {jid :: any(),
+                       password = <<>> :: binary(),
+                       reason = <<>> :: binary(),
+                       continue :: any(),
+                       thread = <<>> :: binary()}).
+-type x_conference() :: #x_conference{}.
+
 -record(private, {xml_els = [] :: [any()]}).
 -type private() :: #private{}.
 
+-record(nick, {name :: binary()}).
+-type nick() :: #nick{}.
+
 -record(p1_ack, {}).
 -type p1_ack() :: #p1_ack{}.
 
                error = [] :: [{integer(),'undefined' | binary()}]}).
 -type stat() :: #stat{}.
 
+-record(addresses, {list = [] :: [#address{}]}).
+-type addresses() :: #addresses{}.
+
 -record('see-other-host', {host :: binary()}).
 -type 'see-other-host'() :: #'see-other-host'{}.
 
 -record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
 -type pubsub_event() :: #pubsub_event{}.
 
+-record(muc_unique, {name = <<>> :: binary()}).
+-type muc_unique() :: #muc_unique{}.
+
 -record(sasl_response, {text :: any()}).
 -type sasl_response() :: #sasl_response{}.
 
 -record(feature_csi, {xmlns :: binary()}).
 -type feature_csi() :: #feature_csi{}.
 
--record(muc_user_destroy, {reason :: binary(),
-                           jid :: any()}).
--type muc_user_destroy() :: #muc_user_destroy{}.
-
 -record(disco_item, {jid :: any(),
                      name :: binary(),
                      node :: binary()}).
 -type disco_item() :: #disco_item{}.
 
--record(disco_items, {node :: binary(),
-                      items = [] :: [#disco_item{}]}).
--type disco_items() :: #disco_items{}.
-
 -record(unblock, {items = [] :: [any()]}).
 -type unblock() :: #unblock{}.
 
 -record(compression, {methods = [] :: [binary()]}).
 -type compression() :: #compression{}.
 
--record(muc_owner_destroy, {jid :: any(),
-                            reason :: binary(),
-                            password :: binary()}).
--type muc_owner_destroy() :: #muc_owner_destroy{}.
+-record(muc_subscriptions, {list = [] :: [any()]}).
+-type muc_subscriptions() :: #muc_subscriptions{}.
 
 -record(pubsub_subscription, {jid :: any(),
                               node :: binary(),
 
 -record(muc_item, {actor :: #muc_actor{},
                    continue :: binary(),
-                   reason :: binary(),
+                   reason = <<>> :: 'undefined' | binary(),
                    affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
                    role :: 'moderator' | 'none' | 'participant' | 'visitor',
                    jid :: any(),
 
 -record(mam_prefs, {xmlns :: binary(),
                     default :: 'always' | 'never' | 'roster',
-                    always = [] :: [any()],
-                    never = [] :: [any()]}).
+                    always :: [any()],
+                    never :: [any()]}).
 -type mam_prefs() :: #mam_prefs{}.
 
 -record(caps, {node :: binary(),
 -record(block_list, {items = [] :: [any()]}).
 -type block_list() :: #block_list{}.
 
+-record(xdata_option, {label :: binary(),
+                       value :: binary()}).
+-type xdata_option() :: #xdata_option{}.
+
 -record(xdata_field, {label :: binary(),
                       type :: 'boolean' | 'fixed' | 'hidden' | 'jid-multi' | 'jid-single' | 'list-multi' | 'list-single' | 'text-multi' | 'text-private' | 'text-single',
                       var :: binary(),
                       required = false :: boolean(),
                       desc :: binary(),
                       values = [] :: [binary()],
-                      options = [] :: [binary()]}).
+                      options = [] :: [#xdata_option{}]}).
 -type xdata_field() :: #xdata_field{}.
 
 -record(version, {name :: binary(),
                   os :: binary()}).
 -type version() :: #version{}.
 
--record(muc_invite, {reason :: binary(),
-                     from :: any(),
-                     to :: any()}).
--type muc_invite() :: #muc_invite{}.
-
 -record(bind, {jid :: any(),
                resource :: any()}).
 -type bind() :: #bind{}.
 -record(rosterver_feature, {}).
 -type rosterver_feature() :: #rosterver_feature{}.
 
--record(muc_user, {decline :: #muc_decline{},
-                   destroy :: #muc_user_destroy{},
-                   invites = [] :: [#muc_invite{}],
-                   items = [] :: [#muc_item{}],
-                   status_codes = [] :: [pos_integer()],
-                   password :: binary()}).
--type muc_user() :: #muc_user{}.
+-record(muc_invite, {reason = <<>> :: 'undefined' | binary(),
+                     from :: any(),
+                     to :: any(),
+                     continue :: binary()}).
+-type muc_invite() :: #muc_invite{}.
 
 -record(carbons_disable, {}).
 -type carbons_disable() :: #carbons_disable{}.
 -type vcard_org() :: #vcard_org{}.
 
 -record(rsm_set, {'after' :: binary(),
-                  before :: 'none' | binary(),
+                  before :: binary(),
                   count :: non_neg_integer(),
                   first :: #rsm_first{},
                   index :: non_neg_integer(),
                   complete :: any()}).
 -type mam_fin() :: #mam_fin{}.
 
+-record(disco_items, {node :: binary(),
+                      items = [] :: [#disco_item{}],
+                      rsm :: #rsm_set{}}).
+-type disco_items() :: #disco_items{}.
+
 -record(vcard_tel, {home = false :: boolean(),
                     work = false :: boolean(),
                     voice = false :: boolean(),
                     number :: binary()}).
 -type vcard_tel() :: #vcard_tel{}.
 
+-record(muc_destroy, {xmlns :: binary(),
+                      jid :: any(),
+                      reason = <<>> :: 'undefined' | binary(),
+                      password :: binary()}).
+-type muc_destroy() :: #muc_destroy{}.
+
+-record(muc_user, {decline :: #muc_decline{},
+                   destroy :: #muc_destroy{},
+                   invites = [] :: [#muc_invite{}],
+                   items = [] :: [#muc_item{}],
+                   status_codes = [] :: [pos_integer()],
+                   password :: binary()}).
+-type muc_user() :: #muc_user{}.
+
 -record(vcard_key, {type :: binary(),
                     cred :: binary()}).
 -type vcard_key() :: #vcard_key{}.
                     start :: any(),
                     'end' :: any(),
                     with :: any(),
+                    withtext :: binary(),
                     rsm :: #rsm_set{},
                     xdata :: #xdata{}}).
 -type mam_query() :: #mam_query{}.
 
--record(muc_owner, {destroy :: #muc_owner_destroy{},
-                    config :: #xdata{}}).
--type muc_owner() :: #muc_owner{}.
-
 -record(pubsub_options, {node :: binary(),
                          jid :: any(),
                          subid :: binary(),
                   fetch = false :: boolean()}).
 -type offline() :: #offline{}.
 
+-record(muc_owner, {destroy :: #muc_destroy{},
+                    config :: #xdata{},
+                    items = [] :: [#muc_item{}]}).
+-type muc_owner() :: #muc_owner{}.
+
 -record(sasl_mechanisms, {list = [] :: [binary()]}).
 -type sasl_mechanisms() :: #sasl_mechanisms{}.
 
 
 -type xmpp_element() :: compression() |
                         pubsub_subscription() |
+                        xdata_option() |
                         version() |
                         pubsub_affiliation() |
                         muc_admin() |
                         rsm_set() |
                         'see-other-host'() |
                         hint() |
+                        stanza_id() |
                         starttls_proceed() |
+                        client_id() |
                         sm_resumed() |
                         forwarded() |
                         xevent() |
                         pubsub_event_item() |
                         muc_item() |
                         vcard_temp() |
+                        address() |
                         sasl_success() |
+                        addresses() |
                         pubsub_event_items() |
+                        muc_subscriptions() |
                         disco_items() |
                         pubsub_options() |
                         compress() |
                         muc_history() |
                         identity() |
                         feature_csi() |
-                        muc_user_destroy() |
                         privacy_query() |
                         delay() |
                         vcard_tel() |
-                        vcard_logo() |
-                        disco_info() |
                         vcard_geo() |
                         vcard_photo() |
-                        feature_register() |
-                        register() |
-                        muc_owner() |
                         pubsub() |
-                        sm_r() |
+                        muc_owner() |
                         muc_actor() |
-                        error() |
-                        stream_error() |
-                        muc_user() |
-                        vcard_adr() |
                         carbons_private() |
                         mix_leave() |
-                        muc_invite() |
+                        muc_subscribe() |
                         rosterver_feature() |
+                        muc_invite() |
                         vcard_xupdate() |
                         carbons_disable() |
                         bookmark_conference() |
                         offline() |
                         time() |
+                        muc_unique() |
                         sasl_response() |
                         pubsub_subscribe() |
                         presence() |
                         starttls_failure() |
                         sasl_challenge() |
                         gone() |
+                        x_conference() |
                         private() |
                         compress_failure() |
                         sasl_failure() |
                         sm_resume() |
                         carbons_enable() |
                         expire() |
+                        muc_unsubscribe() |
                         pubsub_unsubscribe() |
                         muc_decline() |
                         chatstate() |
                         search() |
                         pubsub_publish() |
                         unblock() |
+                        nick() |
                         p1_ack() |
                         block() |
                         mix_join() |
                         starttls() |
                         mam_prefs() |
                         sasl_mechanisms() |
+                        muc_destroy() |
                         vcard_key() |
                         csi() |
                         roster_query() |
-                        muc_owner_destroy() |
                         mam_query() |
                         bookmark_url() |
                         vcard_email() |
-                        vcard_label().
+                        vcard_label() |
+                        vcard_logo() |
+                        disco_info() |
+                        feature_register() |
+                        register() |
+                        sm_r() |
+                        error() |
+                        stream_error() |
+                        muc_user() |
+                        vcard_adr().
index 5924d92c06f714f468eefe7d824c5e5284624410..83ffd932b27fa884039840119e77fa629b64fb9f 100644 (file)
@@ -72,7 +72,7 @@
 start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
 
--spec route(jid(), jid(), xmlel() | xmpp_element()) -> ok.
+-spec route(jid(), jid(), xmlel() | stanza()) -> ok.
 
 route(From, To, Packet) ->
     case catch do_route(From, To, Packet) of
@@ -85,13 +85,21 @@ route(From, To, Packet) ->
 
 %% Route the error packet only if the originating packet is not an error itself.
 %% RFC3920 9.3.1
--spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok.
+-spec route_error(jid(), jid(), xmlel(), xmlel()) -> ok;
+                (jid(), jid(), stanza(), error()) -> ok.
 
-route_error(From, To, ErrPacket, OrigPacket) ->
+route_error(From, To, #xmlel{} = ErrPacket, #xmlel{} = OrigPacket) ->
     #xmlel{attrs = Attrs} = OrigPacket,
     case <<"error">> == fxml:get_attr_s(<<"type">>, Attrs) of
       false -> route(From, To, ErrPacket);
       true -> ok
+    end;
+route_error(From, To, Packet, #error{} = Err) ->
+    Type = xmpp:get_type(Packet),
+    if Type == error; Type == result ->
+           ok;
+       true ->
+           ejabberd_router:route(From, To, xmpp:make_error(Packet, Err))
     end.
 
 -spec register_route(binary()) -> term().
@@ -406,11 +414,16 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
     end.
 
 -spec do_route(jid(), jid(), xmlel() | xmpp_element(), #route{}) -> any().
-do_route(From, To, Packet,
-        #route{local_hint = {apply, Module, Function}, pid = Pid})
-  when is_pid(Pid) andalso node(Pid) == node() ->
-    try
-       Module:Function(From, To, xmpp:decode(Packet, [ignore_els]))
+do_route(From, To, Packet, #route{local_hint = LocalHint,
+                                 pid = Pid}) when is_pid(Pid) ->
+    try xmpp:decode(Packet, [ignore_els]) of
+       Pkt ->
+           case LocalHint of
+               {apply, Module, Function} when node(Pid) == node() ->
+                   Module:Function(From, To, Pkt);
+               _ ->
+                   Pid ! {route, From, To, Pkt}
+           end
     catch error:{xmpp_codec, Why} ->
            ?ERROR_MSG("failed to decode xml element ~p when "
                       "routing from ~s to ~s: ~s",
@@ -418,8 +431,6 @@ do_route(From, To, Packet,
                        xmpp:format_error(Why)]),
            drop
     end;
-do_route(From, To, Packet, #route{pid = Pid}) when is_pid(Pid) ->
-    Pid ! {route, From, To, xmpp:encode(Packet)};
 do_route(_From, _To, _Packet, _Route) ->
     drop.
 
index 03fdab209a0c440e52e03a92530b0448a52cf39d..0d3f242a00949f9f26fab900a8493581dfffcd0a 100644 (file)
@@ -295,7 +295,7 @@ process_sm_iq_items(#iq{type = get, lang = Lang,
            end;
        false ->
            Txt = <<"Not subscribed">>,
-           xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
+           xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
     end.
 
 -spec get_sm_items({error, error()} | {result, [disco_item()]} | empty,
@@ -371,7 +371,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang,
            end;
        false ->
            Txt = <<"Not subscribed">>,
-           xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang))
+           xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
     end.
 
 -spec get_sm_identity([identity()], jid(), jid(),
index b267aa89b191490c984b04fc282ed31027f6b24d..6d0edebf02398e5460964affe205d61f8652ada8 100644 (file)
@@ -140,7 +140,7 @@ process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
            end;
        true ->
            Txt = <<"Not subscribed">>,
-           xmpp:make_error(IQ, xmpp:err_not_subscribed(Txt, Lang))
+           xmpp:make_error(IQ, xmpp:err_subscription_required(Txt, Lang))
     end.
 
 %% @spec (LUser::string(), LServer::string()) ->
index 10eb098da3f3fa32562da425529bb4eba3a604cc..b4ee17720c544c2846cfec28a7c4d109797742a6 100644 (file)
 -export([start/2, stop/1, depends/2]).
 
 -export([user_send_packet/4, user_send_packet_strip_tag/4, user_receive_packet/5,
-        process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5,
-        remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4,
+        process_iq_v0_2/1, process_iq_v0_3/1, disco_sm_features/5,
+        remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
         muc_filter_message/5, message_is_archived/5, delete_old_messages/2,
         get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/4]).
 
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("logger.hrl").
 -include("mod_muc_room.hrl").
 -include("ejabberd_commands.hrl").
 -callback delete_old_messages(binary() | global,
                              erlang:timestamp(),
                              all | chat | groupchat) -> any().
--callback extended_fields() -> [xmlel()].
+-callback extended_fields() -> [xdata_field()].
 -callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat,
                jid(), binary(), recv | send) -> {ok, binary()} | any().
 -callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any().
 -callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error.
--callback select(binary(), jid(), jid(),
-                none | erlang:timestamp(),
-                none | erlang:timestamp(),
-                none | ljid() | {text, binary()},
-                none | #rsm_in{},
-                chat | groupchat) ->
+-callback select(binary(), jid(), jid(), mam_query(), chat | groupchat) ->
     {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}.
 
 %%%===================================================================
@@ -200,14 +195,10 @@ get_room_config(X, RoomState, _From, Lang) ->
              true -> <<"1">>;
              _ -> <<"0">>
          end,
-    XField = #xmlel{name = <<"field">>,
-                   attrs =
-                       [{<<"type">>, <<"boolean">>},
-                        {<<"label">>, translate:translate(Lang, Label)},
-                        {<<"var">>, Var}],
-                   children =
-                       [#xmlel{name = <<"value">>, attrs = [],
-                               children = [{xmlcdata, Val}]}]},
+    XField = #xdata_field{type = boolean,
+                         label = translate:translate(Lang, Label),
+                         var = Var,
+                         values = [Val]},
     X ++ [XField].
 
 set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
@@ -222,7 +213,7 @@ set_room_option(_Acc, <<"muc#roomconfig_mam">> = Opt, Vals, Lang) ->
     catch _:{case_clause, _} ->
            Txt = <<"Value of '~s' should be boolean">>,
            ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-           {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
+           {error, xmpp:err_bad_request(Lang, ErrTxt)}
     end;
 set_room_option(Acc, _Opt, _Vals, _Lang) ->
     Acc.
@@ -236,16 +227,7 @@ user_receive_packet(Pkt, C2SState, JID, Peer, To) ->
            NewPkt = strip_my_archived_tag(Pkt, LServer),
            case store_msg(C2SState, NewPkt, LUser, LServer, Peer, recv) of
                {ok, ID} ->
-                   Archived = #xmlel{name = <<"archived">>,
-                                     attrs = [{<<"by">>, LServer},
-                                              {<<"xmlns">>, ?NS_MAM_TMP},
-                                              {<<"id">>, ID}]},
-                   StanzaID = #xmlel{name = <<"stanza-id">>,
-                                     attrs = [{<<"by">>, LServer},
-                                              {<<"xmlns">>, ?NS_SID_0},
-                                              {<<"id">>, ID}]},
-                    NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
-                   NewPkt#xmlel{children = NewEls};
+                   set_stanza_id(NewPkt, LServer, ID);
                _ ->
                    NewPkt
            end;
@@ -259,19 +241,10 @@ user_send_packet(Pkt, C2SState, JID, Peer) ->
     case should_archive(Pkt, LServer) of
        true ->
            NewPkt = strip_my_archived_tag(Pkt, LServer),
-           case store_msg(C2SState, jlib:replace_from_to(JID, Peer, NewPkt),
+           case store_msg(C2SState, xmpp:set_from_to(NewPkt, JID, Peer),
                      LUser, LServer, Peer, send) of
               {ok, ID} ->
-                   Archived = #xmlel{name = <<"archived">>,
-                                     attrs = [{<<"by">>, LServer},
-                                              {<<"xmlns">>, ?NS_MAM_TMP},
-                                              {<<"id">>, ID}]},
-                   StanzaID = #xmlel{name = <<"stanza-id">>,
-                                     attrs = [{<<"by">>, LServer},
-                                              {<<"xmlns">>, ?NS_SID_0},
-                                              {<<"id">>, ID}]},
-                          NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
-                   NewPkt#xmlel{children = NewEls};
+                   set_stanza_id(NewPkt, LServer, ID);
             _ ->
                 NewPkt
         end;
@@ -291,16 +264,7 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
            StorePkt = strip_x_jid_tags(NewPkt),
            case store_muc(MUCState, StorePkt, RoomJID, From, FromNick) of
                {ok, ID} ->
-                   Archived = #xmlel{name = <<"archived">>,
-                                     attrs = [{<<"by">>, LServer},
-                                              {<<"xmlns">>, ?NS_MAM_TMP},
-                                              {<<"id">>, ID}]},
-                   StanzaID = #xmlel{name = <<"stanza-id">>,
-                                     attrs = [{<<"by">>, LServer},
-                                               {<<"xmlns">>, ?NS_SID_0},
-                                               {<<"id">>, ID}]},
-                    NewEls = [Archived, StanzaID|NewPkt#xmlel.children],
-                    NewPkt#xmlel{children = NewEls};
+                   set_stanza_id(NewPkt, LServer, ID);
                _ ->
                    NewPkt
            end;
@@ -308,71 +272,98 @@ muc_filter_message(Pkt, #state{config = Config} = MUCState,
            Pkt
     end.
 
+set_stanza_id(Pkt, LServer, ID) ->
+    Archived = #mam_archived{by = jid:make(LServer), id = ID},
+    StanzaID = #stanza_id{by = jid:make(LServer), id = ID},
+    NewEls = [Archived, StanzaID|xmpp:get_els(Pkt)],
+    xmpp:set_els(Pkt, NewEls).
+
 % Query archive v0.2
-process_iq_v0_2(#jid{lserver = LServer} = From,
-              #jid{lserver = LServer} = To,
-              #iq{type = get, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
-    Fs = parse_query_v0_2(SubEl),
-    process_iq(LServer, From, To, IQ, SubEl, Fs, chat);
-process_iq_v0_2(From, To, IQ) ->
-    process_iq(From, To, IQ).
+process_iq_v0_2(#iq{from = #jid{lserver = LServer},
+                   to = #jid{lserver = LServer},
+                   type = get, sub_els = [#mam_query{}]} = IQ) ->
+    process_iq(LServer, IQ, chat);
+process_iq_v0_2(IQ) ->
+    process_iq(IQ).
 
 % Query archive v0.3
-process_iq_v0_3(#jid{lserver = LServer} = From,
-               #jid{lserver = LServer} = To,
-               #iq{type = set, sub_el = #xmlel{name = <<"query">>} = SubEl} = IQ) ->
-    process_iq(LServer, From, To, IQ, SubEl, get_xdata_fields(SubEl), chat);
-process_iq_v0_3(#jid{lserver = LServer},
-               #jid{lserver = LServer},
-               #iq{type = get, sub_el = #xmlel{name = <<"query">>}} = IQ) ->
+process_iq_v0_3(#iq{from = #jid{lserver = LServer},
+                   to = #jid{lserver = LServer},
+                   type = set, sub_els = [#mam_query{}]} = IQ) ->
+    process_iq(LServer, IQ, chat);
+process_iq_v0_3(#iq{from = #jid{lserver = LServer},
+                   to = #jid{lserver = LServer},
+                   type = get, sub_els = [#mam_query{}]} = IQ) ->
     process_iq(LServer, IQ);
-process_iq_v0_3(From, To, IQ) ->
-    process_iq(From, To, IQ).
-
-muc_process_iq(#iq{type = set,
-                  sub_el = #xmlel{name = <<"query">>,
-                                  attrs = Attrs} = SubEl} = IQ,
-              MUCState, From, To) ->
-    case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-       NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
-           muc_process_iq(IQ, MUCState, From, To, get_xdata_fields(SubEl));
-       _ ->
-           IQ
-    end;
-muc_process_iq(#iq{type = get,
-                  sub_el = #xmlel{name = <<"query">>,
-                                  attrs = Attrs} = SubEl} = IQ,
-              MUCState, From, To) ->
-    case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-       ?NS_MAM_TMP ->
-           muc_process_iq(IQ, MUCState, From, To, parse_query_v0_2(SubEl));
-       NS when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+process_iq_v0_3(IQ) ->
+    process_iq(IQ).
+
+muc_process_iq(#iq{type = T, lang = Lang,
+                  from = From,
+                  sub_els = [#mam_query{xmlns = NS}]} = IQ,
+              MUCState)
+  when (T == set andalso (NS == ?NS_MAM_0 orelse NS == ?NS_MAM_1)) orelse
+       (T == get andalso NS == ?NS_MAM_TMP) ->
+    case may_enter_room(From, MUCState) of
+       true ->
            LServer = MUCState#state.server_host,
-           process_iq(LServer, IQ);
-       _ ->
-           IQ
+           Role = mod_muc_room:get_role(From, MUCState),
+           process_iq(LServer, IQ, {groupchat, Role, MUCState});
+       false ->
+           Text = <<"Only members may query archives of this room">>,
+           xmpp:make_error(IQ, xmpp:err_forbidden(Text, Lang))
     end;
-muc_process_iq(IQ, _MUCState, _From, _To) ->
+muc_process_iq(#iq{type = get,
+                  sub_els = [#mam_query{xmlns = NS}]} = IQ,
+              MUCState) when NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
+    LServer = MUCState#state.server_host,
+    process_iq(LServer, IQ);
+muc_process_iq(IQ, _MUCState) ->
     IQ.
 
-get_xdata_fields(SubEl) ->
-    case {fxml:get_subtag_with_xmlns(SubEl, <<"x">>, ?NS_XDATA),
-         fxml:get_subtag_with_xmlns(SubEl, <<"set">>, ?NS_RSM)} of
-       {#xmlel{} = XData, false} ->
-           jlib:parse_xdata_submit(XData);
-       {#xmlel{} = XData, #xmlel{}} ->
-           [{<<"set">>, SubEl} | jlib:parse_xdata_submit(XData)];
-       {false, #xmlel{}} ->
-           [{<<"set">>, SubEl}];
-       {false, false} ->
-           []
-    end.
+parse_query(#mam_query{xdata = #xdata{fields = Fs}} = Query, Lang) ->
+    try
+       lists:foldl(
+         fun(#xdata_field{var = <<"start">>, values = [Data|_]}, Q) ->
+                 case jlib:datetime_string_to_timestamp(Data) of
+                     undefined -> throw({error, <<"start">>});
+                     {_, _, _} = TS -> Q#mam_query{start = TS}
+                 end;
+            (#xdata_field{var = <<"end">>, values = [Data|_]}, Q) ->
+                 case jlib:datetime_string_to_timestamp(Data) of
+                     undefined -> throw({error, <<"end">>});
+                     {_, _, _} = TS -> Q#mam_query{'end' = TS}
+                 end;
+            (#xdata_field{var = <<"with">>, values = [Data|_]}, Q) ->
+                 case jid:from_string(Data) of
+                     error -> throw({error, <<"with">>});
+                     J -> Q#mam_query{with = J}
+                 end;
+            (#xdata_field{var = <<"withtext">>, values = [Data|_]}, Q) ->
+                 case Data of
+                     <<"">> -> throw({error, <<"withtext">>});
+                     _ -> Q#mam_query{withtext = Data}
+             end;
+            (#xdata_field{var = <<"FORM_TYPE">>, values = [NS|_]}, Q) ->
+                 case Query#mam_query.xmlns of
+                     NS -> Q;
+                     _ -> throw({error, <<"FORM_TYPE">>})
+                 end;
+            (#xdata_field{}, Acc) ->
+                 Acc
+         end, Query, Fs)
+    catch throw:{error, Var} ->
+           Txt = io_lib:format("Incorrect value of field '~s'", [Var]),
+           {error, xmpp:err_bad_request(iolist_to_binary(Txt), Lang)}
+    end;
+parse_query(Query, _Lang) ->
+    Query.
 
 disco_sm_features(empty, From, To, Node, Lang) ->
     disco_sm_features({result, []}, From, To, Node, Lang);
 disco_sm_features({result, OtherFeatures},
                  #jid{luser = U, lserver = S},
-                 #jid{luser = U, lserver = S}, <<>>, _Lang) ->
+                 #jid{luser = U, lserver = S}, undefined, _Lang) ->
     {result, [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1 | OtherFeatures]};
 disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
     Acc.
@@ -440,210 +431,155 @@ delete_old_messages(_TypeBin, _Days) ->
 %%% Internal functions
 %%%===================================================================
 
-process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) ->
-    NS = case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-            ?NS_MAM_0 ->
-                ?NS_MAM_0;
-            _ ->
-                ?NS_MAM_1
-        end,
-    CommonFields = [#xmlel{name = <<"field">>,
-                          attrs = [{<<"type">>, <<"hidden">>},
-                                   {<<"var">>, <<"FORM_TYPE">>}],
-                          children = [#xmlel{name = <<"value">>,
-                                             children = [{xmlcdata, NS}]}]},
-                   #xmlel{name = <<"field">>,
-                          attrs = [{<<"type">>, <<"jid-single">>},
-                                   {<<"var">>, <<"with">>}]},
-                   #xmlel{name = <<"field">>,
-                          attrs = [{<<"type">>, <<"text-single">>},
-                                   {<<"var">>, <<"start">>}]},
-                   #xmlel{name = <<"field">>,
-                          attrs = [{<<"type">>, <<"text-single">>},
-                                   {<<"var">>, <<"end">>}]}],
+process_iq(LServer, #iq{sub_els = [#mam_query{xmlns = NS}]} = IQ) ->
+    CommonFields = [#xdata_field{type = hidden,
+                                var = <<"FORM_TYPE">>,
+                                values = [NS]},
+                   #xdata_field{type = 'jid-single', var = <<"with">>},
+                   #xdata_field{type = 'text-single', var = <<"start">>},
+                   #xdata_field{type = 'text-single', var = <<"end">>}],
     Mod = gen_mod:db_mod(LServer, ?MODULE),
     ExtendedFields = Mod:extended_fields(),
-    Fields = ExtendedFields ++ CommonFields,
-    Form = #xmlel{name = <<"x">>,
-                 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
-                 children = Fields},
-    IQ#iq{type = result,
-         sub_el = [#xmlel{name = <<"query">>,
-                          attrs = [{<<"xmlns">>, NS}],
-                          children = [Form]}]}.
+    Fields = CommonFields ++ ExtendedFields,
+    Form = #xdata{type = form, fields = Fields},
+    xmpp:make_iq_result(IQ, #mam_query{xmlns = NS, xdata = Form}).
 
 % Preference setting (both v0.2 & v0.3)
-process_iq(#jid{luser = LUser, lserver = LServer},
-          #jid{lserver = LServer},
-          #iq{type = set, lang = Lang, sub_el = #xmlel{name = <<"prefs">>} = SubEl} = IQ) ->
-    try {case fxml:get_tag_attr_s(<<"default">>, SubEl) of
-           <<"always">> -> always;
-           <<"never">> -> never;
-           <<"roster">> -> roster
-           end,
-           lists:foldl(
-               fun(#xmlel{name = <<"always">>, children = Els}, {A, N}) ->
-                       {get_jids(Els) ++ A, N};
-                   (#xmlel{name = <<"never">>, children = Els}, {A, N}) ->
-                       {A, get_jids(Els) ++ N};
-                   (_, {A, N}) ->
-                       {A, N}
-               end, {[], []}, SubEl#xmlel.children)} of
-       {Default, {Always0, Never0}} ->
-           Always = lists:usort(Always0),
-           Never = lists:usort(Never0),
-           case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
-               ok ->
-                   NewPrefs = prefs_el(Default, Always, Never, IQ#iq.xmlns),
-                   IQ#iq{type = result, sub_el = [NewPrefs]};
-               _Err ->
-                   Txt = <<"Database failure">>,
-                   IQ#iq{type = error,
-                       sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
-           end
-    catch _:_ ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+process_iq(#iq{type = set, lang = Lang,
+              sub_els = [#mam_prefs{default = undefined, xmlns = NS}]} = IQ) ->
+    Why = {missing_attr, <<"default">>, <<"prefs">>, NS},
+    ErrTxt = xmpp:format_error(Why),
+    xmpp:make_error(IQ, xmpp:err_bad_request(ErrTxt, Lang));
+process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
+              to = #jid{lserver = LServer},
+              type = set, lang = Lang,
+              sub_els = [#mam_prefs{xmlns = NS,
+                                    default = Default,
+                                    always = Always0,
+                                    never = Never0}]} = IQ) ->
+    Always = lists:usort(get_jids(Always0)),
+    Never = lists:usort(get_jids(Never0)),
+    case write_prefs(LUser, LServer, LServer, Default, Always, Never) of
+       ok ->
+           NewPrefs = prefs_el(Default, Always, Never, NS),
+           xmpp:make_iq_result(IQ, NewPrefs);
+       _Err ->
+           Txt = <<"Database failure">>,
+           xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
     end;
-process_iq(#jid{luser = LUser, lserver = LServer},
-          #jid{lserver = LServer},
-          #iq{type = get, sub_el = #xmlel{name = <<"prefs">>}} = IQ) ->
+process_iq(#iq{from = #jid{luser = LUser, lserver = LServer},
+              to = #jid{lserver = LServer},
+              type = get, sub_els = [#mam_prefs{xmlns = NS}]} = IQ) ->
     Prefs = get_prefs(LUser, LServer),
     PrefsEl = prefs_el(Prefs#archive_prefs.default,
                       Prefs#archive_prefs.always,
                       Prefs#archive_prefs.never,
-                      IQ#iq.xmlns),
-    IQ#iq{type = result, sub_el = [PrefsEl]};
-process_iq(_, _, #iq{sub_el = SubEl} = IQ) ->
-    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
+                      NS),
+    xmpp:make_iq_result(IQ, PrefsEl);
+process_iq(IQ) ->
+    xmpp:make_error(IQ, xmpp:err_not_allowed()).
 
-process_iq(LServer, #jid{luser = LUser} = From, To, IQ, SubEl, Fs, MsgType) ->
+process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
+                       sub_els = [SubEl]} = IQ, MsgType) ->
     case MsgType of
        chat ->
            maybe_activate_mam(LUser, LServer);
        {groupchat, _Role, _MUCState} ->
            ok
     end,
-    case catch lists:foldl(
-                fun({<<"start">>, [Data|_]}, {_, End, With, RSM}) ->
-                        {{_, _, _} = jlib:datetime_string_to_timestamp(Data),
-                         End, With, RSM};
-                   ({<<"end">>, [Data|_]}, {Start, _, With, RSM}) ->
-                        {Start,
-                         {_, _, _} = jlib:datetime_string_to_timestamp(Data),
-                         With, RSM};
-                   ({<<"with">>, [Data|_]}, {Start, End, _, RSM}) ->
-                        {Start, End, jid:tolower(jid:from_string(Data)), RSM};
-                   ({<<"withtext">>, [Data|_]}, {Start, End, _, RSM}) ->
-                        {Start, End, {text, Data}, RSM};
-                   ({<<"set">>, El}, {Start, End, With, _}) ->
-                        {Start, End, With, jlib:rsm_decode(El)};
-                   (_, Acc) ->
-                        Acc
-                end, {none, [], none, none}, Fs) of
-       {'EXIT', _} ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
-       {_Start, _End, _With, #rsm_in{index = Index}} when is_integer(Index) ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
-       {Start, End, With, RSM} ->
-           NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
-           select_and_send(LServer, From, To, Start, End,
-                           With, limit_max(RSM, NS), IQ, MsgType)
+    case parse_query(SubEl, Lang) of
+       #mam_query{rsm = #rsm_set{index = I}} when is_integer(I) ->
+           xmpp:make_error(IQ, xmpp:err_feature_not_implemented());
+       #mam_query{rsm = RSM, xmlns = NS} = Query ->
+           NewRSM = limit_max(RSM, NS),
+           NewQuery = Query#mam_query{rsm = NewRSM},
+           select_and_send(LServer, NewQuery, IQ, MsgType);
+       {error, Err} ->
+           xmpp:make_error(IQ, Err)
     end.
 
-muc_process_iq(#iq{lang = Lang, sub_el = SubEl} = IQ, MUCState, From, To, Fs) ->
-    case may_enter_room(From, MUCState) of
+should_archive(#message{type = T}, _LServer) when T == error; T == result ->
+    false;
+should_archive(#message{body = Body} = Pkt, LServer) ->
+    case is_resent(Pkt, LServer) of
        true ->
-           LServer = MUCState#state.server_host,
-           Role = mod_muc_room:get_role(From, MUCState),
-           process_iq(LServer, From, To, IQ, SubEl, Fs,
-                      {groupchat, Role, MUCState});
-       false ->
-           Text = <<"Only members may query archives of this room">>,
-           Error = ?ERRT_FORBIDDEN(Lang, Text),
-           IQ#iq{type = error, sub_el = [SubEl, Error]}
-    end.
-
-parse_query_v0_2(Query) ->
-    lists:flatmap(
-      fun (#xmlel{name = <<"start">>} = El) ->
-             [{<<"start">>, [fxml:get_tag_cdata(El)]}];
-         (#xmlel{name = <<"end">>} = El) ->
-             [{<<"end">>, [fxml:get_tag_cdata(El)]}];
-         (#xmlel{name = <<"with">>} = El) ->
-             [{<<"with">>, [fxml:get_tag_cdata(El)]}];
-         (#xmlel{name = <<"withtext">>} = El) ->
-             [{<<"withtext">>, [fxml:get_tag_cdata(El)]}];
-         (#xmlel{name = <<"set">>}) ->
-             [{<<"set">>, Query}];
-         (_) ->
-            []
-      end, Query#xmlel.children).
-
-should_archive(#xmlel{name = <<"message">>} = Pkt, LServer) ->
-    case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
-       <<"error">> ->
            false;
-       <<"groupchat">> ->
-           false;
-       _ ->
-           case is_resent(Pkt, LServer) of
-               true ->
+       false ->
+           case check_store_hint(Pkt) of
+               store ->
+                   true;
+               no_store ->
                    false;
-               false ->
-                   case check_store_hint(Pkt) of
-                       store ->
-                           true;
-                       no_store ->
+               none ->
+                   case xmpp:get_text(Body) of
+                       <<>> ->
+                           %% Empty body
                            false;
-                       none ->
-                           case fxml:get_subtag_cdata(Pkt, <<"body">>) of
-                               <<>> ->
-                                   %% Empty body
-                                   false;
-                               _ ->
-                                   true
-                           end
+                       _ ->
+                           true
                    end
            end
     end;
-should_archive(#xmlel{}, _LServer) ->
+should_archive(_, _LServer) ->
     false.
 
 strip_my_archived_tag(Pkt, LServer) ->
+    NewPkt = xmpp:decode_els(
+              Pkt, fun(El) ->
+                           case xmpp:get_name(El) of
+                               <<"archived">> ->
+                                   xmpp:get_ns(El) == ?NS_MAM_TMP;
+                               <<"stanza-id">> ->
+                                   xmpp:get_ns(El) == ?NS_SID_0;
+                               _ ->
+                                   false
+                           end
+                   end),
     NewEls = lists:filter(
-           fun(#xmlel{name = Tag, attrs = Attrs})
-                       when Tag == <<"archived">>; Tag == <<"stanza-id">> ->
-                   case catch jid:nameprep(
-                           fxml:get_attr_s(
-                               <<"by">>, Attrs)) of
-                       LServer ->
-                           false;
-                       _ ->
-                           true
-                   end;
-               (_) ->
-                   true
-           end, Pkt#xmlel.children),
-    Pkt#xmlel{children = NewEls}.
+              fun(#mam_archived{by = #jid{luser = <<>>} = By}) ->
+                      By#jid.lserver /= LServer;
+                 (#stanza_id{by = #jid{luser = <<>>} = By}) ->
+                      By#jid.lserver /= LServer;
+                 (_) ->
+                      true
+              end, xmpp:get_els(NewPkt)),
+    xmpp:set_els(NewPkt, NewEls).
 
 strip_x_jid_tags(Pkt) ->
+    NewPkt = xmpp:decode_els(
+              Pkt, fun(El) ->
+                           case xmpp:get_name(El) of
+                               <<"x">> ->
+                                   case xmpp:get_ns(El) of
+                                       ?NS_MUC_USER -> true;
+                                       ?NS_MUC_ADMIN -> true;
+                                       ?NS_MUC_OWNER -> true;
+                                       _ -> false
+                                   end;
+                               _ ->
+                                   false
+                           end
+                   end),
     NewEls = lists:filter(
-             fun(#xmlel{name = <<"x">>} = XEl) ->
-                     not lists:any(fun(ItemEl) ->
-                                           fxml:get_tag_attr(<<"jid">>, ItemEl)
-                                             /= false
-                                   end, fxml:get_subtags(XEl, <<"item">>));
-                (_) ->
-                     true
-             end, Pkt#xmlel.children),
-    Pkt#xmlel{children = NewEls}.
+              fun(El) ->
+                      Items = case El of
+                                  #muc_user{items = Is} -> Is;
+                                  #muc_admin{items = Is} -> Is;
+                                  #muc_owner{items = Is} -> Is;
+                                  _ -> []
+                              end,
+                      not lists:any(fun(#muc_item{jid = JID}) ->
+                                            JID /= undefined
+                                    end, Items)
+              end, xmpp:get_els(NewPkt)),
+    xmpp:set_els(NewPkt, NewEls).
 
 should_archive_peer(C2SState,
                    #archive_prefs{default = Default,
                                   always = Always,
                                   never = Never},
                    Peer) ->
-    LPeer = jid:tolower(Peer),
+    LPeer = jid:remove_resource(jid:tolower(Peer)),
     case lists:member(LPeer, Always) of
        true ->
            true;
@@ -667,30 +603,30 @@ should_archive_peer(C2SState,
            end
     end.
 
-should_archive_muc(Pkt) ->
-    case fxml:get_attr_s(<<"type">>, Pkt#xmlel.attrs) of
-       <<"groupchat">> ->
-           case check_store_hint(Pkt) of
-               store ->
-                   true;
-               no_store ->
-                   false;
-               none ->
-                   case fxml:get_subtag_cdata(Pkt, <<"body">>) of
-                       <<>> ->
-                           case fxml:get_subtag_cdata(Pkt, <<"subject">>) of
-                               <<>> ->
-                                   false;
-                               _ ->
-                                   true
-                           end;
+should_archive_muc(#message{type = groupchat,
+                           body = Body, subject = Subj} = Pkt) ->
+    case check_store_hint(Pkt) of
+       store ->
+           true;
+       no_store ->
+           false;
+       none ->
+           case xmpp:get_text(Body) of
+               <<"">> ->
+                   case xmpp:get_text(Subj) of
+                       <<"">> ->
+                           false;
                        _ ->
                            true
-                   end
+                   end;
+               _ ->
+                   true
            end;
        _ ->
            false
-    end.
+    end;
+should_archive_muc(_) ->
+    false.
 
 check_store_hint(Pkt) ->
     case has_store_hint(Pkt) of
@@ -705,30 +641,24 @@ check_store_hint(Pkt) ->
            end
     end.
 
+
+-spec has_store_hint(message()) -> boolean().
 has_store_hint(Message) ->
-    fxml:get_subtag_with_xmlns(Message, <<"store">>, ?NS_HINTS)
-      /= false.
+    xmpp:has_subtag(Message, #hint{type = 'store'}).
 
+-spec has_no_store_hint(message()) -> boolean().
 has_no_store_hint(Message) ->
-    fxml:get_subtag_with_xmlns(Message, <<"no-store">>, ?NS_HINTS)
-      /= false orelse
-    fxml:get_subtag_with_xmlns(Message, <<"no-storage">>, ?NS_HINTS)
-      /= false orelse
-    fxml:get_subtag_with_xmlns(Message, <<"no-permanent-store">>, ?NS_HINTS)
-      /= false orelse
-    fxml:get_subtag_with_xmlns(Message, <<"no-permanent-storage">>, ?NS_HINTS)
-      /= false.
+    xmpp:has_subtag(Message, #hint{type = 'no-store'}) orelse
+    xmpp:has_subtag(Message, #hint{type = 'no-storage'}) orelse
+    xmpp:has_subtag(Message, #hint{type = 'no-permanent-store'}) orelse
+    xmpp:has_subtag(Message, #hint{type = 'no-permanent-storage'}).
 
+-spec is_resent(message(), binary()) -> boolean().
 is_resent(Pkt, LServer) ->
-    case fxml:get_subtag_with_xmlns(Pkt, <<"stanza-id">>, ?NS_SID_0) of
-       #xmlel{attrs = Attrs} ->
-           case fxml:get_attr(<<"by">>, Attrs) of
-               {value, LServer} ->
-                   true;
-               _ ->
-                   false
-           end;
-       false ->
+    case xmpp:get_subtag(Pkt, #stanza_id{}) of
+       #stanza_id{by = #jid{luser = <<>>, lserver = LServer}} ->
+           true;
+       _ ->
            false
     end.
 
@@ -744,7 +674,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
        true ->
            US = {LUser, LServer},
            Mod = gen_mod:db_mod(LServer, ?MODULE),
-           Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir);
+           El = xmpp:encode(Pkt),
+           Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir);
        false ->
            pass
     end.
@@ -755,7 +686,8 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
            LServer = MUCState#state.server_host,
            {U, S, _} = jid:tolower(RoomJID),
            Mod = gen_mod:db_mod(LServer, ?MODULE),
-           Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv);
+           El = xmpp:encode(Pkt),
+           Mod:store(El, LServer, {U, S}, groupchat, Peer, Nick, recv);
        false ->
            pass
     end.
@@ -796,20 +728,10 @@ get_prefs(LUser, LServer) ->
     end.
 
 prefs_el(Default, Always, Never, NS) ->
-    Default1 = jlib:atom_to_binary(Default),
-    JFun = fun(L) ->
-                  [#xmlel{name = <<"jid">>,
-                          children = [{xmlcdata, jid:to_string(J)}]}
-                   || J <- L]
-          end,
-    Always1 = #xmlel{name = <<"always">>,
-                    children = JFun(Always)},
-    Never1 = #xmlel{name = <<"never">>,
-                   children = JFun(Never)},
-    #xmlel{name = <<"prefs">>,
-          attrs = [{<<"xmlns">>, NS},
-                   {<<"default">>, Default1}],
-          children = [Always1, Never1]}.
+    #mam_prefs{default = Default,
+              always = [jid:make(LJ) || LJ <- Always],
+              never = [jid:make(LJ) || LJ <- Never],
+              xmlns = NS}.
 
 maybe_activate_mam(LUser, LServer) ->
     ActivateOpt = gen_mod:get_module_opt(LServer, ?MODULE,
@@ -838,21 +760,19 @@ maybe_activate_mam(LUser, LServer) ->
            ok
     end.
 
-select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) ->
-    {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End,
-                                                With, RSM, MsgType),
+select_and_send(LServer, Query, #iq{from = From, to = To} = IQ, MsgType) ->
+    {Msgs, IsComplete, Count} =
+       case MsgType of
+           chat ->
+               select(LServer, From, From, Query, MsgType);
+           {groupchat, _Role, _MUCState} ->
+               select(LServer, From, To, Query, MsgType)
+       end,
     SortedMsgs = lists:keysort(2, Msgs),
-    send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ).
+    send(SortedMsgs, Count, IsComplete, IQ).
 
-select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) ->
-    case MsgType of
-       chat ->
-           select(LServer, From, From, Start, End, With, RSM, MsgType);
-       {groupchat, _Role, _MUCState} ->
-           select(LServer, From, To, Start, End, With, RSM, MsgType)
-    end.
-
-select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
+select(_LServer, JidRequestor, JidArchive,
+       #mam_query{start = Start, 'end' = End, rsm = RSM},
        {groupchat, _Role, #state{config = #config{mam = false},
                                 history = History}} = MsgType) ->
     #lqueue{len = L, queue = Q} = History,
@@ -864,7 +784,7 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
                  case match_interval(Now, Start, End) and
                      match_rsm(Now, RSM) of
                      true ->
-                         {[{jlib:integer_to_binary(TS), TS,
+                         {[{integer_to_binary(TS), TS,
                             msg_to_el(#archive_msg{
                                          type = groupchat,
                                          timestamp = Now,
@@ -879,31 +799,27 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM,
          end, 0, queue:to_list(Q)),
     Msgs = lists:flatten(Msgs0),
     case RSM of
-       #rsm_in{max = Max, direction = before} ->
+       #rsm_set{max = Max, before = Before} when is_binary(Before) ->
            {NewMsgs, IsComplete} = filter_by_max(lists:reverse(Msgs), Max),
            {NewMsgs, IsComplete, L};
-       #rsm_in{max = Max} ->
+       #rsm_set{max = Max} ->
            {NewMsgs, IsComplete} = filter_by_max(Msgs, Max),
            {NewMsgs, IsComplete, L};
        _ ->
            {Msgs, true, L}
     end;
-select(LServer, JidRequestor, JidArchive, Start, End, With, RSM, MsgType) ->
+select(LServer, JidRequestor, JidArchive, Query, MsgType) ->
     Mod = gen_mod:db_mod(LServer, ?MODULE),
-    Mod:select(LServer, JidRequestor, JidArchive, Start, End, With, RSM,
-              MsgType).
+    Mod:select(LServer, JidRequestor, JidArchive, Query, MsgType).
 
 msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer},
          MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
     Pkt2 = maybe_update_from_to(Pkt1, JidRequestor, JidArchive, Peer, MsgType,
                                Nick),
-    Pkt3 = #xmlel{name = <<"forwarded">>,
-                 attrs = [{<<"xmlns">>, ?NS_FORWARD}],
-                 children = [fxml:replace_tag_attr(
-                               <<"xmlns">>, <<"jabber:client">>, Pkt2)]},
-    jlib:add_delay_info(Pkt3, LServer, TS).
+    #forwarded{sub_els = [Pkt2],
+              delay = #delay{stamp = TS, from = jid:make(LServer)}}.
 
-maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
+maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
                     Peer, {groupchat, Role,
                            #state{config = #config{anonymous = Anon}}},
                     Nick) ->
@@ -919,18 +835,13 @@ maybe_update_from_to(#xmlel{children = Els} = Pkt, JidRequestor, JidArchive,
                end,
     Items = case ExposeJID of
                true ->
-                   [#xmlel{name = <<"x">>,
-                           attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
-                           children =
-                               [#xmlel{name = <<"item">>,
-                                       attrs = [{<<"jid">>,
-                                                 jid:to_string(Peer)}]}]}];
+                   [#muc_user{items = [#muc_item{jid = Peer}]}];
                false ->
                    []
            end,
-    Pkt1 = Pkt#xmlel{children = Items ++ Els},
-    Pkt2 = jlib:replace_from(jid:replace_resource(JidArchive, Nick), Pkt1),
-    jlib:remove_attr(<<"to">>, Pkt2);
+    Pkt#message{from = jid:replace_resource(JidArchive, Nick),
+               to = undefined,
+               sub_els = Items ++ Els};
 maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
     Pkt.
 
@@ -966,62 +877,46 @@ is_bare_copy(#jid{luser = U, lserver = S, lresource = R}, To) ->
            false
     end.
 
-send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) ->
-    QID = fxml:get_tag_attr_s(<<"queryid">>, SubEl),
-    NS = fxml:get_tag_attr_s(<<"xmlns">>, SubEl),
-    QIDAttr = if QID /= <<>> ->
-                     [{<<"queryid">>, QID}];
-                true ->
-                   []
-             end,
-    CompleteAttr = if NS == ?NS_MAM_TMP ->
-                          [];
-                     NS == ?NS_MAM_0; NS == ?NS_MAM_1 ->
-                          [{<<"complete">>, jlib:atom_to_binary(IsComplete)}]
-                  end,
+-spec send([{binary(), integer(), xmlel()}],
+          non_neg_integer(), boolean(), iq()) -> iq() | ignore.
+send(Msgs, Count, IsComplete,
+     #iq{from = From, to = To,
+        sub_els = [#mam_query{id = QID, xmlns = NS}]} = IQ) ->
     Els = lists:map(
            fun({ID, _IDInt, El}) ->
-                   #xmlel{name = <<"message">>,
-                          children = [#xmlel{name = <<"result">>,
-                                             attrs = [{<<"xmlns">>, NS},
-                                                      {<<"id">>, ID}|QIDAttr],
-                                             children = [El]}]}
+                   #message{sub_els = [#mam_result{xmlns = NS,
+                                                   id = ID,
+                                                   queryid = QID,
+                                                   sub_els = [El]}]}
            end, Msgs),
-    RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS),
+    RSMOut = make_rsm_out(Msgs, Count),
+    Result = if NS == ?NS_MAM_TMP ->
+                    #mam_query{xmlns = NS, id = QID, rsm = RSMOut};
+               true ->
+                    #mam_fin{id = QID, rsm = RSMOut, complete = IsComplete}
+            end,
     if NS == ?NS_MAM_TMP; NS == ?NS_MAM_1 ->
            lists:foreach(
              fun(El) ->
                      ejabberd_router:route(To, From, El)
              end, Els),
-           IQ#iq{type = result, sub_el = RSMOut};
+           xmpp:make_iq_result(IQ, Result);
        NS == ?NS_MAM_0 ->
-           ejabberd_router:route(
-             To, From, jlib:iq_to_xml(IQ#iq{type = result, sub_el = []})),
+           ejabberd_router:route(To, From, xmpp:make_iq_result(IQ)),
            lists:foreach(
              fun(El) ->
                      ejabberd_router:route(To, From, El)
              end, Els),
-           ejabberd_router:route(
-             To, From, #xmlel{name = <<"message">>,
-                              children = RSMOut}),
+           ejabberd_router:route(To, From, #message{sub_els = [Result]}),
            ignore
     end.
 
-make_rsm_out([], _, Count, Attrs, NS) ->
-    Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
-            true -> <<"fin">>
-         end,
-    [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
-           children = jlib:rsm_encode(#rsm_out{count = Count})}];
-make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) ->
+-spec make_rsm_out([{binary(), integer(), xmlel()}], non_neg_integer()) -> rsm_set().
+make_rsm_out([], Count) ->
+    #rsm_set{count = Count};
+make_rsm_out([{FirstID, _, _}|_] = Msgs, Count) ->
     {LastID, _, _} = lists:last(Msgs),
-    Tag = if NS == ?NS_MAM_TMP -> <<"query">>;
-            true -> <<"fin">>
-         end,
-    [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs],
-           children = jlib:rsm_encode(
-                        #rsm_out{first = FirstID, count = Count,
-                                 last = LastID})}].
+    #rsm_set{first = #rsm_first{data = FirstID}, last = LastID, count = Count}.
 
 filter_by_max(Msgs, undefined) ->
     {Msgs, true};
@@ -1030,23 +925,24 @@ filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 ->
 filter_by_max(_Msgs, _Junk) ->
     {[], true}.
 
+-spec limit_max(rsm_set(), binary()) -> rsm_set().
 limit_max(RSM, ?NS_MAM_TMP) ->
     RSM; % XEP-0313 v0.2 doesn't require clients to support RSM.
-limit_max(#rsm_in{max = Max} = RSM, _NS) when not is_integer(Max) ->
-    RSM#rsm_in{max = ?DEF_PAGE_SIZE};
-limit_max(#rsm_in{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
-    RSM#rsm_in{max = ?MAX_PAGE_SIZE};
+limit_max(#rsm_set{max = Max} = RSM, _NS) when not is_integer(Max) ->
+    RSM#rsm_set{max = ?DEF_PAGE_SIZE};
+limit_max(#rsm_set{max = Max} = RSM, _NS) when Max > ?MAX_PAGE_SIZE ->
+    RSM#rsm_set{max = ?MAX_PAGE_SIZE};
 limit_max(RSM, _NS) ->
     RSM.
 
 match_interval(Now, Start, End) ->
     (Now >= Start) and (Now =< End).
 
-match_rsm(Now, #rsm_in{id = ID, direction = aft}) when ID /= <<"">> ->
-    Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
+match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
+    Now1 = (catch usec_to_now(binary_to_integer(ID))),
     Now > Now1;
-match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> ->
-    Now1 = (catch usec_to_now(jlib:binary_to_integer(ID))),
+match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
+    Now1 = (catch usec_to_now(binary_to_integer(ID))),
     Now < Now1;
 match_rsm(_Now, _) ->
     true.
@@ -1066,15 +962,10 @@ datetime_to_now(DateTime, USecs) ->
        calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
     {Seconds div 1000000, Seconds rem 1000000, USecs}.
 
-get_jids(Els) ->
-    lists:flatmap(
-      fun(#xmlel{name = <<"jid">>} = El) ->
-             J = jid:from_string(fxml:get_tag_cdata(El)),
-             [jid:tolower(jid:remove_resource(J)),
-              jid:tolower(J)];
-        (_) ->
-             []
-      end, Els).
+get_jids(undefined) ->
+    [];
+get_jids(Js) ->
+    [jid:tolower(jid:remove_resource(J)) || J <- Js].
 
 get_commands_spec() ->
     [#ejabberd_commands{name = delete_old_mam_messages, tags = [purge],
index be14d0fff5065e7f53ea6bc32bcd27ef7e6ea55f..cbe7c336c6d8bfcb2342e2abfe474b96f26b9eb3 100644 (file)
 
 %% API
 -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
-        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
 
 -include_lib("stdlib/include/ms_transform.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("logger.hrl").
 -include("mod_mam.hrl").
 
@@ -132,7 +132,8 @@ get_prefs(LUser, LServer) ->
 
 select(_LServer, JidRequestor,
        #jid{luser = LUser, lserver = LServer} = JidArchive,
-       Start, End, With, RSM, MsgType) ->
+       #mam_query{start = Start, 'end' = End,
+                 with = With, rsm = RSM}, MsgType) ->
     MS = make_matchspec(LUser, LServer, Start, End, With),
     Msgs = mnesia:dirty_select(archive_msg, MS),
     SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs),
@@ -174,7 +175,7 @@ make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) ->
                 Peer == With ->
              Msg
       end);
-make_matchspec(LUser, LServer, Start, End, none) ->
+make_matchspec(LUser, LServer, Start, End, undefined) ->
     ets:fun2ms(
       fun(#archive_msg{timestamp = TS,
                       us = US,
@@ -184,28 +185,27 @@ make_matchspec(LUser, LServer, Start, End, none) ->
              Msg
       end).
 
-filter_by_rsm(Msgs, none) ->
+filter_by_rsm(Msgs, undefined) ->
     {Msgs, true};
-filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 ->
+filter_by_rsm(_Msgs, #rsm_set{max = Max}) when Max < 0 ->
     {[], true};
-filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) ->
-    NewMsgs = case Direction of
-                 aft when ID /= <<"">> ->
+filter_by_rsm(Msgs, #rsm_set{max = Max, before = Before, 'after' = After}) ->
+    NewMsgs = if is_binary(After), After /= <<"">> ->
                      lists:filter(
                        fun(#archive_msg{id = I}) ->
-                               ?BIN_GREATER_THAN(I, ID)
+                               ?BIN_GREATER_THAN(I, After)
                        end, Msgs);
-                 before when ID /= <<"">> ->
+                is_binary(Before), Before /= <<"">> ->
                      lists:foldl(
                        fun(#archive_msg{id = I} = Msg, Acc)
-                               when ?BIN_LESS_THAN(I, ID) ->
+                               when ?BIN_LESS_THAN(I, Before) ->
                                [Msg|Acc];
                           (_, Acc) ->
                                Acc
                        end, [], Msgs);
-                 before when ID == <<"">> ->
+                is_binary(Before), Before == <<"">> ->
                      lists:reverse(Msgs);
-                 _ ->
+                true ->
                      Msgs
              end,
     filter_by_max(NewMsgs, Max).
index 20ed8d4f155311ada2344ceac4fca8026642fa47..6e523198983d80c253761e43be9542fcf1cd304d 100644 (file)
 
 %% API
 -export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
-        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]).
+        extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/5]).
 
 -include_lib("stdlib/include/ms_transform.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("mod_mam.hrl").
 -include("logger.hrl").
 -include("ejabberd_sql_pt.hrl").
@@ -51,9 +51,7 @@ delete_old_messages(ServerHost, TimeStamp, Type) ->
     ok.
 
 extended_fields() ->
-    [#xmlel{name = <<"field">>,
-           attrs = [{<<"type">>, <<"text-single">>},
-                    {<<"var">>, <<"withtext">>}]}].
+    [#xdata_field{type = 'text-single', var = <<"withtext">>}].
 
 store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) ->
     TSinteger = p1_time_compat:system_time(micro_seconds),
@@ -126,13 +124,12 @@ get_prefs(LUser, LServer) ->
     end.
 
 select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
-       Start, End, With, RSM, MsgType) ->
+       MAMQuery, MsgType) ->
     User = case MsgType of
               chat -> LUser;
               {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive)
           end,
-    {Query, CountQuery} = make_sql_query(User, LServer,
-                                        Start, End, With, RSM),
+    {Query, CountQuery} = make_sql_query(User, LServer, MAMQuery),
     % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
     % reasonable limit on how many stanzas may be pushed to a client in one
     % request. If a query returns a number of stanzas greater than this limit
@@ -142,10 +139,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
     case {ejabberd_sql:sql_query(LServer, Query),
          ejabberd_sql:sql_query(LServer, CountQuery)} of
        {{selected, _, Res}, {selected, _, [[Count]]}} ->
-           {Max, Direction} = case RSM of
-                                  #rsm_in{max = M, direction = D} -> {M, D};
-                                  _ -> {undefined, undefined}
-                              end,
+           {Max, Direction, _} = get_max_direction_id(MAMQuery#mam_query.rsm),
            {Res1, IsComplete} =
                if Max >= 0 andalso Max /= undefined andalso length(Res) > Max ->
                        if Direction == before ->
@@ -200,15 +194,10 @@ usec_to_now(Int) ->
     Sec = Secs rem 1000000,
     {MSec, Sec, USec}.
 
-make_sql_query(User, LServer, Start, End, With, RSM) ->
-    {Max, Direction, ID} = case RSM of
-       #rsm_in{} ->
-           {RSM#rsm_in.max,
-               RSM#rsm_in.direction,
-               RSM#rsm_in.id};
-       none ->
-           {none, none, <<>>}
-    end,
+make_sql_query(User, LServer,
+              #mam_query{start = Start, 'end' = End, with = With,
+                         withtext = WithText, rsm = RSM}) ->
+    {Max, Direction, ID} = get_max_direction_id(RSM),
     ODBCType = ejabberd_config:get_option(
                 {sql_type, LServer},
                 ejabberd_sql:opt_type(sql_type)),
@@ -228,12 +217,16 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
                     true ->
                          []
                  end,
-    WithClause = case With of
-                    {text, <<>>} ->
-                        [];
-                    {text, Txt} ->
-                        [<<" and match (txt) against ('">>,
-                         Escape(Txt), <<"')">>];
+    WithTextClause = case WithText of
+                        {text, <<>>} ->
+                            [];
+                        {text, Txt} ->
+                            [<<" and match (txt) against ('">>,
+                             Escape(Txt), <<"')">>];
+                        undefined ->
+                            []
+                    end,
+    WithClause = case catch jid:tolower(With) of
                     {_, _, <<>>} ->
                         [<<" and bare_peer='">>,
                          Escape(jid:to_string(With)),
@@ -242,7 +235,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
                         [<<" and peer='">>,
                          Escape(jid:to_string(With)),
                          <<"'">>];
-                    none ->
+                    _ ->
                         []
                 end,
     PageClause = case catch jlib:binary_to_integer(ID) of
@@ -250,7 +243,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
                         case Direction of
                             before ->
                                 [<<" AND timestamp < ">>, ID];
-                            aft ->
+                            'after' ->
                                 [<<" AND timestamp > ">>, ID];
                             _ ->
                                 []
@@ -276,7 +269,7 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
 
     Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick"
              " FROM archive WHERE username='">>,
-            SUser, <<"'">>, WithClause, StartClause, EndClause,
+            SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause,
             PageClause],
 
     QueryPage =
@@ -294,4 +287,20 @@ make_sql_query(User, LServer, Start, End, With, RSM) ->
        end,
     {QueryPage,
      [<<"SELECT COUNT(*) FROM archive WHERE username='">>,
-      SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}.
+      SUser, <<"'">>, WithClause, WithTextClause, StartClause, EndClause, <<";">>]}.
+
+-spec get_max_direction_id(rsm_set() | undefined) ->
+                                 {integer() | undefined,
+                                  before | 'after' | undefined,
+                                  binary()}.
+get_max_direction_id(RSM) ->
+    case RSM of
+       #rsm_set{max = Max, before = Before} when is_binary(Before) ->
+           {Max, before, Before};
+       #rsm_set{max = Max, 'after' = After} when is_binary(After) ->
+           {Max, 'after', After};
+       #rsm_set{max = Max} ->
+           {Max, undefined, <<>>};
+       _ ->
+           {undefined, undefined, <<>>}
+    end.
index a86f580d30c1a949a324018b1304382e08f83ce6..294456ee21baf6030180773adb305e772b02157f 100644 (file)
         forget_room/3,
         create_room/5,
         shutdown_rooms/1,
-        process_iq_disco_items/4,
+        process_disco_info/1,
+        process_disco_items/1,
+        process_vcard/1,
+        process_register/1,
+        process_muc_unique/1,
+        process_mucsub/1,
         broadcast_service_message/2,
         export/1,
         import/1,
@@ -58,7 +63,7 @@
 -include("ejabberd.hrl").
 -include("logger.hrl").
 
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("mod_muc.hrl").
 
 -record(state,
@@ -154,17 +159,6 @@ forget_room(ServerHost, Host, Name) ->
     Mod = gen_mod:db_mod(LServer, ?MODULE),
     Mod:forget_room(LServer, Host, Name).
 
-process_iq_disco_items(Host, From, To,
-                      #iq{lang = Lang} = IQ) ->
-    Rsm = jlib:rsm_decode(IQ),
-    DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
-    Res = IQ#iq{type = result,
-               sub_el =
-                   [#xmlel{name = <<"query">>,
-                           attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
-                           children = iq_disco_items(Host, From, Lang, DiscoNode, Rsm)}]},
-    ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
-
 can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
 can_use_nick(ServerHost, Host, JID, Nick) ->
     LServer = jid:nameprep(ServerHost),
@@ -176,6 +170,8 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
 %%====================================================================
 
 init([Host, Opts]) ->
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
+                             one_queue),
     MyHost = gen_mod:get_opt_host(Host, Opts,
                                  <<"conference.@HOST@">>),
     Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
@@ -255,6 +251,18 @@ init([Host, Opts]) ->
     RoomShaper = gen_mod:get_opt(room_shaper, Opts,
                                  fun(A) when is_atom(A) -> A end,
                                  none),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER,
+                                 ?MODULE, process_register, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
+                                 ?MODULE, process_vcard, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB,
+                                 ?MODULE, process_mucsub, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE,
+                                 ?MODULE, process_muc_unique, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
+                                 ?MODULE, process_disco_info, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
+                                 ?MODULE, process_disco_items, IQDisc),
     ejabberd_router:register_route(MyHost, Host),
     load_permanent_rooms(MyHost, Host,
                         {Access, AccessCreate, AccessAdmin, AccessPersistent},
@@ -314,8 +322,14 @@ handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
     {noreply, State};
 handle_info(_Info, State) -> {noreply, State}.
 
-terminate(_Reason, State) ->
-    ejabberd_router:unregister_route(State#state.host),
+terminate(_Reason, #state{host = MyHost}) ->
+    ejabberd_router:unregister_route(MyHost),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_REGISTER),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUCSUB),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_MUC_UNIQUE),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
     ok.
 
 code_change(_OldVsn, State, _Extra) -> {ok, State}.
@@ -331,197 +345,162 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
        allow ->
            do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
                From, To, Packet, DefRoomOpts);
-       _ ->
-           #xmlel{attrs = Attrs} = Packet,
-           Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+       deny ->
+           Lang = xmpp:get_lang(Packet),
            ErrText = <<"Access denied by service policy">>,
-           Err = jlib:make_error_reply(Packet,
-                   ?ERRT_FORBIDDEN(Lang, ErrText)),
-           ejabberd_router:route_error(To, From, Err, Packet)
+           Err = xmpp:err_forbidden(ErrText, Lang),
+           ejabberd_router:route_error(To, From, Packet, Err)
     end.
 
-
+do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
+         From, #jid{luser = <<"">>, lresource = <<"">>} = To,
+         #iq{} = IQ, _DefRoomOpts) ->
+    ejabberd_local:process_iq(From, To, IQ);
+do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
+         From, #jid{luser = <<"">>, lresource = <<"">>} = To,
+         #message{lang = Lang, body = Body, type = Type} = Packet, _) ->
+    {_AccessRoute, _AccessCreate, AccessAdmin, _AccessPersistent} = Access,
+    if Type == error ->
+           ok;
+       true ->
+           case acl:match_rule(ServerHost, AccessAdmin, From) of
+               allow ->
+                   Msg = xmpp:get_text(Body),
+                   broadcast_service_message(Host, Msg);
+               deny ->
+                   ErrText = <<"Only service administrators are allowed "
+                               "to send service messages">>,
+                   Err = xmpp:make_error(
+                           Packet, xmpp:err_forbidden(ErrText, Lang)),
+                   ejabberd_router:route(To, From, Err)
+           end
+    end;
+do_route1(_Host, _ServerHost, _Access, _HistorySize, _RoomShaper,
+         From, #jid{luser = <<"">>} = To, Packet, _DefRoomOpts) ->
+    Err = xmpp:err_item_not_found(),
+    ejabberd_router:route_error(To, From, Packet, Err);
 do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
          From, To, Packet, DefRoomOpts) ->
-    {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
+    {_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
     {Room, _, Nick} = jid:tolower(To),
-    #xmlel{name = Name, attrs = Attrs} = Packet,
-    case Room of
-      <<"">> ->
-         case Nick of
-           <<"">> ->
-               case Name of
-                 <<"iq">> ->
-                     case jlib:iq_query_info(Packet) of
-                       #iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
-                           sub_el = _SubEl, lang = Lang} =
-                           IQ ->
-                           Info = ejabberd_hooks:run_fold(disco_info,
-                                                          ServerHost, [],
-                                                          [ServerHost, ?MODULE,
-                                                           <<"">>, <<"">>]),
-                           Res = IQ#iq{type = result,
-                                       sub_el =
-                                           [#xmlel{name = <<"query">>,
-                                                   attrs =
-                                                       [{<<"xmlns">>, XMLNS}],
-                                                   children =
-                                                       iq_disco_info(
-                                                         ServerHost, Lang) ++
-                                                         Info}]},
-                           ejabberd_router:route(To, From,
-                                                 jlib:iq_to_xml(Res));
-                       #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
-                           spawn(?MODULE, process_iq_disco_items,
-                                 [Host, From, To, IQ]);
-                       #iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
-                           lang = Lang, sub_el = _SubEl} =
-                           IQ ->
-                           Res = IQ#iq{type = result,
-                                       sub_el =
-                                           [#xmlel{name = <<"query">>,
-                                                   attrs =
-                                                       [{<<"xmlns">>, XMLNS}],
-                                                   children =
-                                                       iq_get_register_info(ServerHost,
-                                                                            Host,
-                                                                            From,
-                                                                            Lang)}]},
-                           ejabberd_router:route(To, From,
-                                                 jlib:iq_to_xml(Res));
-                       #iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
-                           lang = Lang, sub_el = SubEl} =
-                           IQ ->
-                           case process_iq_register_set(ServerHost, Host, From,
-                                                        SubEl, Lang)
-                               of
-                             {result, IQRes} ->
-                                 Res = IQ#iq{type = result,
-                                             sub_el =
-                                                 [#xmlel{name = <<"query">>,
-                                                         attrs =
-                                                             [{<<"xmlns">>,
-                                                               XMLNS}],
-                                                         children = IQRes}]},
-                                 ejabberd_router:route(To, From,
-                                                       jlib:iq_to_xml(Res));
-                             {error, Error} ->
-                                 Err = jlib:make_error_reply(Packet, Error),
-                                 ejabberd_router:route(To, From, Err)
-                           end;
-                       #iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
-                           lang = Lang, sub_el = _SubEl} =
-                           IQ ->
-                           Res = IQ#iq{type = result,
-                                       sub_el =
-                                           [#xmlel{name = <<"vCard">>,
-                                                   attrs =
-                                                       [{<<"xmlns">>, XMLNS}],
-                                                   children =
-                                                       iq_get_vcard(Lang)}]},
-                           ejabberd_router:route(To, From,
-                                                 jlib:iq_to_xml(Res));
-                       #iq{type = get, xmlns = ?NS_MUCSUB,
-                           sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
-                             RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
-                             Subs = lists:map(
-                                      fun(J) ->
-                                              #xmlel{name = <<"subscription">>,
-                                                     attrs = [{<<"jid">>,
-                                                               jid:to_string(J)}]}
-                                      end, RoomJIDs),
-                             Res = IQ#iq{type = result,
-                                         sub_el = [SubEl#xmlel{children = Subs}]},
-                             ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
-                       #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
-                           Res = IQ#iq{type = result,
-                                       sub_el =
-                                           [#xmlel{name = <<"unique">>,
-                                                   attrs =
-                                                       [{<<"xmlns">>,
-                                                         ?NS_MUC_UNIQUE}],
-                                                   children =
-                                                       [iq_get_unique(From)]}]},
-                           ejabberd_router:route(To, From,
-                                                 jlib:iq_to_xml(Res));
-                       #iq{} ->
-                           Err = jlib:make_error_reply(Packet,
-                                                       ?ERR_FEATURE_NOT_IMPLEMENTED),
-                           ejabberd_router:route(To, From, Err);
-                       _ -> ok
-                     end;
-                 <<"message">> ->
-                     case fxml:get_attr_s(<<"type">>, Attrs) of
-                       <<"error">> -> ok;
-                       _ ->
-                           case acl:match_rule(ServerHost, AccessAdmin, From)
-                               of
-                             allow ->
-                                 Msg = fxml:get_path_s(Packet,
-                                                      [{elem, <<"body">>},
-                                                       cdata]),
-                                 broadcast_service_message(Host, Msg);
-                             _ ->
-                                 Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-                                 ErrText =
-                                     <<"Only service administrators are allowed "
-                                       "to send service messages">>,
-                                 Err = jlib:make_error_reply(Packet,
-                                                             ?ERRT_FORBIDDEN(Lang,
-                                                                             ErrText)),
-                                 ejabberd_router:route(To, From, Err)
-                           end
-                     end;
-                 <<"presence">> -> ok
-               end;
-           _ ->
-               case fxml:get_attr_s(<<"type">>, Attrs) of
-                 <<"error">> -> ok;
-                 <<"result">> -> ok;
-                 _ ->
-                     Err = jlib:make_error_reply(Packet,
-                                                 ?ERR_ITEM_NOT_FOUND),
-                     ejabberd_router:route(To, From, Err)
-               end
-         end;
-      _ ->
-           case mnesia:dirty_read(muc_online_room, {Room, Host}) of
-               [] ->
-                   Type = fxml:get_attr_s(<<"type">>, Attrs),
-                   case {Name, Type} of
-                       {<<"presence">>, <<"">>} ->
-                           case check_user_can_create_room(ServerHost,
-                                   AccessCreate, From, Room) and
-                               check_create_roomid(ServerHost, Room) of
-                               true ->
-                                   {ok, Pid} = start_new_room(Host, ServerHost, Access,
-                                           Room, HistorySize,
-                                           RoomShaper, From, Nick, DefRoomOpts),
-                                   register_room(Host, Room, Pid),
-                                   mod_muc_room:route(Pid, From, Nick, Packet),
-                                   ok;
-                               false ->
-                                   Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-                                   ErrText = <<"Room creation is denied by service policy">>,
-                                   Err = jlib:make_error_reply(
-                                           Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
-                                   ejabberd_router:route(To, From, Err)
-                           end;
-                       _ ->
-                           Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-                           ErrText = <<"Conference room does not exist">>,
-                           Err = jlib:make_error_reply(Packet,
-                                   ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
+    case mnesia:dirty_read(muc_online_room, {Room, Host}) of
+       [] ->
+           case Packet of
+               #presence{type = available, lang = Lang} ->
+                   case check_user_can_create_room(
+                          ServerHost, AccessCreate, From, Room) and
+                       check_create_roomid(ServerHost, Room) of
+                       true ->
+                           {ok, Pid} = start_new_room(
+                                         Host, ServerHost, Access,
+                                         Room, HistorySize,
+                                         RoomShaper, From, Nick, DefRoomOpts),
+                           register_room(Host, Room, Pid),
+                           mod_muc_room:route(Pid, From, Nick, Packet),
+                           ok;
+                       false ->
+                           ErrText = <<"Room creation is denied by service policy">>,
+                           Err = xmpp:make_error(
+                                   Packet, xmpp:err_forbidden(ErrText, Lang)),
                            ejabberd_router:route(To, From, Err)
                    end;
-               [R] ->
-                   Pid = R#muc_online_room.pid,
-                   ?DEBUG("MUC: send to process ~p~n", [Pid]),
-                   mod_muc_room:route(Pid, From, Nick, Packet),
-                   ok
-           end
+               _ ->
+                   Lang = xmpp:get_lang(Packet),
+                   ErrText = <<"Conference room does not exist">>,
+                   Err = xmpp:err_item_not_found(ErrText, Lang),
+                   ejabberd_router:route_error(To, From, Packet, Err)
+           end;
+       [R] ->
+           Pid = R#muc_online_room.pid,
+           ?DEBUG("MUC: send to process ~p~n", [Pid]),
+           mod_muc_room:route(Pid, From, Nick, Packet),
+           ok
+    end.
+
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = get, lang = Lang} = IQ) ->
+    Desc = translate:translate(Lang, <<"ejabberd MUC module">>),
+    Copyright = <<"Copyright (c) 2003-2016 ProcessOne">>,
+    xmpp:make_iq_result(
+      IQ, #vcard_temp{fn = <<"ejabberd/mod_muc">>,
+                     url = ?EJABBERD_URI,
+                     desc = <<Desc/binary, $\n, Copyright/binary>>});
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)).
+
+-spec process_register(iq()) -> iq().
+process_register(#iq{type = get, from = From, to = To, lang = Lang} = IQ) ->
+    Host = To#jid.lserver,
+    ServerHost = ejabberd_router:host_of_route(Host),
+    xmpp:make_iq_result(IQ, iq_get_register_info(ServerHost, Host, From, Lang));
+process_register(#iq{type = set, from = From, to = To,
+                    lang = Lang, sub_els = [El]} = IQ) ->
+    Host = To#jid.lserver,
+    ServerHost = ejabberd_router:host_of_route(Host),
+    case process_iq_register_set(ServerHost, Host, From, El, Lang) of
+       {result, Result} ->
+           xmpp:make_iq_result(IQ, Result);
+       {error, Err} ->
+           xmpp:make_error(IQ, Err)
     end.
 
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, to = To, lang = Lang,
+                      sub_els = [#disco_info{node = undefined}]} = IQ) ->
+    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+    X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
+                               [ServerHost, ?MODULE, undefined, Lang]),
+    MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
+                     true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
+                     false -> []
+                 end,
+    Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+               ?NS_REGISTER, ?NS_MUC, ?NS_RSM,
+               ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures],
+    Identity = #identity{category = <<"conference">>,
+                        type = <<"text">>,
+                        name = translate:translate(Lang, <<"Chatrooms">>)},
+    xmpp:make_iq_result(
+      IQ, #disco_info{features = Features,
+                     identities = [Identity],
+                     xdata = X});
+process_disco_info(#iq{type = get, lang = Lang} = IQ) ->
+    xmpp:make_error(IQ, xmpp:err_item_not_found(<<"No info available">>, Lang)).
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
+                       sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
+    Host = To#jid.lserver,
+    xmpp:make_iq_result(
+      IQ, #disco_items{node = Node,
+                      items = iq_disco_items(Host, From, Lang, Node, RSM)}).
+
+-spec process_muc_unique(iq()) -> iq().
+process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
+    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_muc_unique(#iq{from = From, type = get} = IQ) ->
+    Name = p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
+                                     randoms:get_string()])),
+    xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
+
+-spec process_mucsub(iq()) -> iq().
+process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
+    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
+    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_mucsub(#iq{type = get, from = From, to = To} = IQ) ->
+    Host = To#jid.lserver,
+    ServerHost = ejabberd_router:host_of_route(Host),
+    RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
+    xmpp:make_iq_result(IQ, #muc_subscriptions{list = RoomJIDs}).
+
 check_user_can_create_room(ServerHost, AccessCreate,
                           From, _RoomID) ->
     case acl:match_rule(ServerHost, AccessCreate, From) of
@@ -583,61 +562,21 @@ register_room(Host, Room, Pid) ->
     end,
     mnesia:transaction(F).
 
-
-iq_disco_info(ServerHost, Lang) ->
-    [#xmlel{name = <<"identity">>,
-           attrs =
-               [{<<"category">>, <<"conference">>},
-                {<<"type">>, <<"text">>},
-                {<<"name">>,
-                 translate:translate(Lang, <<"Chatrooms">>)}],
-           children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_MUC}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_RSM}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_MUCSUB}], children = []},
-     #xmlel{name = <<"feature">>,
-           attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
-       case gen_mod:is_loaded(ServerHost, mod_mam) of
-           true ->
-               [#xmlel{name = <<"feature">>,
-                       attrs = [{<<"var">>, ?NS_MAM_TMP}]},
-                #xmlel{name = <<"feature">>,
-                       attrs = [{<<"var">>, ?NS_MAM_0}]},
-                #xmlel{name = <<"feature">>,
-                       attrs = [{<<"var">>, ?NS_MAM_1}]}];
-           false ->
-               []
-       end.
-
-iq_disco_items(Host, From, Lang, <<>>, none) ->
+iq_disco_items(Host, From, Lang, undefined, undefined) ->
     Rooms = get_vh_rooms(Host),
     case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
        true ->
            iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
        false ->
-           iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
+           iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined)
     end;
-iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
-    XmlEmpty = #xmlel{name = <<"item">>,
-                                  attrs =
-                                      [{<<"jid">>, <<"conference.localhost">>},
-                                       {<<"node">>, <<"emptyrooms">>},
-                                       {<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
-                                  children = []},
+iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, undefined) ->
+    Empty = #disco_item{jid = jid:make(<<"conference.localhost">>),
+                       node = <<"emptyrooms">>,
+                       name = translate:translate(Lang, <<"Empty Rooms">>)},
     Query = {get_disco_item, only_non_empty, From, Lang},
-    [XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
-iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) ->
+    [Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
+iq_disco_items(Host, From, Lang, <<"emptyrooms">>, undefined) ->
     iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
 iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
     {Rooms, RsmO} = get_vh_rooms(Host, Rsm),
@@ -645,62 +584,55 @@ iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
     iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
 
 iq_disco_items_list(Host, Rooms, Query) ->
-    lists:zf(fun (#muc_online_room{name_host =
-                                      {Name, _Host},
-                                  pid = Pid}) ->
-                    case catch gen_fsm:sync_send_all_state_event(Pid,
-                                                                 Query,
-                                                                 100)
-                        of
-                      {item, Desc} ->
-                          flush(),
-                          {true,
-                           #xmlel{name = <<"item">>,
-                                  attrs =
-                                      [{<<"jid">>,
-                                        jid:to_string({Name, Host,
-                                                            <<"">>})},
-                                       {<<"name">>, Desc}],
-                                  children = []}};
-                      _ -> false
-                    end
-            end, Rooms).
-
-get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
-    AllRooms = lists:sort(get_vh_rooms(Host)),
-    Count = erlang:length(AllRooms),
-    Guard = case Direction of
-               _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
-               aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
-               before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
-               _ -> [{'==', {element, 2, '$1'}, Host}]
-           end,
-    L = lists:sort(
-         mnesia:dirty_select(muc_online_room,
-                             [{#muc_online_room{name_host = '$1', _ = '_'},
-                               Guard,
-                               ['$_']}])),
-    L2 = if
-            Index == undefined andalso Direction == before ->
-                lists:reverse(lists:sublist(lists:reverse(L), 1, M));
-            Index == undefined ->
-                lists:sublist(L, 1, M);
-            Index > Count  orelse Index < 0 ->
-                [];
-            true ->
-                lists:sublist(L, Index+1, M)
-        end,
-    if L2 == [] -> {L2, #rsm_out{count = Count}};
-       true ->
-          H = hd(L2),
-          NewIndex = get_room_pos(H, AllRooms),
-          T = lists:last(L2),
-          {F, _} = H#muc_online_room.name_host,
-          {Last, _} = T#muc_online_room.name_host,
-          {L2,
-           #rsm_out{first = F, last = Last, count = Count,
-                    index = NewIndex}}
-    end.
+    lists:zf(
+      fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
+             case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
+                 {item, Desc} ->
+                     flush(),
+                     {true, #disco_item{jid = jid:make(Name, Host),
+                                        name = Desc}};
+                 _ ->
+                     false
+             end
+      end, Rooms).
+
+get_vh_rooms(_, _) ->
+    todo.
+%% get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
+%%     AllRooms = lists:sort(get_vh_rooms(Host)),
+%%     Count = erlang:length(AllRooms),
+%%     Guard = case Direction of
+%%             _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
+%%             aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
+%%             before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
+%%             _ -> [{'==', {element, 2, '$1'}, Host}]
+%%         end,
+%%     L = lists:sort(
+%%       mnesia:dirty_select(muc_online_room,
+%%                           [{#muc_online_room{name_host = '$1', _ = '_'},
+%%                             Guard,
+%%                             ['$_']}])),
+%%     L2 = if
+%%          Index == undefined andalso Direction == before ->
+%%              lists:reverse(lists:sublist(lists:reverse(L), 1, M));
+%%          Index == undefined ->
+%%              lists:sublist(L, 1, M);
+%%          Index > Count  orelse Index < 0 ->
+%%              [];
+%%          true ->
+%%              lists:sublist(L, Index+1, M)
+%%      end,
+%%     if L2 == [] -> {L2, #rsm_out{count = Count}};
+%%        true ->
+%%        H = hd(L2),
+%%        NewIndex = get_room_pos(H, AllRooms),
+%%        T = lists:last(L2),
+%%        {F, _} = H#muc_online_room.name_host,
+%%        {Last, _} = T#muc_online_room.name_host,
+%%        {L2,
+%%         #rsm_out{first = F, last = Last, count = Count,
+%%                  index = NewIndex}}
+%%     end.
 
 get_subscribed_rooms(ServerHost, Host, From) ->
     Rooms = get_rooms(ServerHost, Host),
@@ -730,60 +662,32 @@ get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
 
 flush() -> receive _ -> flush() after 0 -> ok end.
 
--define(XFIELD(Type, Label, Var, Val),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"type">>, Type},
-                   {<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, Val}]}]}).
-
-iq_get_unique(From) ->
-    {xmlcdata,
-     p1_sha:sha(term_to_binary([From, p1_time_compat:timestamp(),
-                            randoms:get_string()]))}.
-
 get_nick(ServerHost, Host, From) ->
     LServer = jid:nameprep(ServerHost),
     Mod = gen_mod:db_mod(LServer, ?MODULE),
     Mod:get_nick(LServer, Host, From).
 
 iq_get_register_info(ServerHost, Host, From, Lang) ->
-    {Nick, Registered} = case get_nick(ServerHost, Host,
-                                      From)
-                            of
-                          error -> {<<"">>, []};
-                          N ->
-                              {N,
-                               [#xmlel{name = <<"registered">>, attrs = [],
-                                       children = []}]}
+    {Nick, Registered} = case get_nick(ServerHost, Host, From) of
+                            error -> {<<"">>, false};
+                            N -> {N, true}
                         end,
-    Registered ++
-      [#xmlel{name = <<"instructions">>, attrs = [],
-             children =
-                 [{xmlcdata,
-                   translate:translate(Lang,
-                                       <<"You need a client that supports x:data "
-                                         "to register the nickname">>)}]},
-       #xmlel{name = <<"x">>,
-             attrs = [{<<"xmlns">>, ?NS_XDATA},
-                      {<<"type">>, <<"form">>}],
-             children =
-                 [#xmlel{name = <<"title">>, attrs = [],
-                         children =
-                             [{xmlcdata,
-                               <<(translate:translate(Lang,
-                                                      <<"Nickname Registration at ">>))/binary,
-                                 Host/binary>>}]},
-                  #xmlel{name = <<"instructions">>, attrs = [],
-                         children =
-                             [{xmlcdata,
-                               translate:translate(Lang,
-                                                   <<"Enter nickname you want to register">>)}]},
-                  ?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
-                          Nick)]}].
+    Title = <<(translate:translate(
+                Lang, <<"Nickname Registration at ">>))/binary, Host/binary>>,
+    Inst = translate:translate(Lang, <<"Enter nickname you want to register">>),
+    Field = #xdata_field{type = 'text-single',
+                        label = translate:translate(Lang, <<"Nickname">>),
+                        var = <<"nick">>,
+                        values = [Nick]},
+    X = #xdata{type = form, title = Title,
+              instructions = [Inst], fields = [Field]},
+    #register{nick = Nick,
+             registered = Registered,
+             instructions = 
+                 translate:translate(
+                   Lang, <<"You need a client that supports x:data "
+                           "to register the nickname">>),
+             xdata = X}.
 
 set_nick(ServerHost, Host, From, Nick) ->
     LServer = jid:nameprep(ServerHost),
@@ -793,66 +697,43 @@ set_nick(ServerHost, Host, From, Nick) ->
 iq_set_register_info(ServerHost, Host, From, Nick,
                     Lang) ->
     case set_nick(ServerHost, Host, From, Nick) of
-      {atomic, ok} -> {result, []};
+      {atomic, ok} -> {result, undefined};
       {atomic, false} ->
          ErrText = <<"That nickname is registered by another "
                      "person">>,
-         {error, ?ERRT_CONFLICT(Lang, ErrText)};
+         {error, xmpp:err_conflict(ErrText, Lang)};
       _ ->
          Txt = <<"Database failure">>,
-         {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
+         {error, xmpp:err_internal_server_error(Txt, Lang)}
     end.
 
-process_iq_register_set(ServerHost, Host, From, SubEl,
-                       Lang) ->
-    #xmlel{children = Els} = SubEl,
-    case fxml:get_subtag(SubEl, <<"remove">>) of
-      false ->
-         case fxml:remove_cdata(Els) of
-           [#xmlel{name = <<"x">>} = XEl] ->
-               case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
-                     fxml:get_tag_attr_s(<<"type">>, XEl)}
-                   of
-                 {?NS_XDATA, <<"cancel">>} -> {result, []};
-                 {?NS_XDATA, <<"submit">>} ->
-                     XData = jlib:parse_xdata_submit(XEl),
-                     case XData of
-                       invalid ->
-                             Txt = <<"Incorrect data form">>,
-                             {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-                       _ ->
-                           case lists:keysearch(<<"nick">>, 1, XData) of
-                             {value, {_, [Nick]}} when Nick /= <<"">> ->
-                                 iq_set_register_info(ServerHost, Host, From,
-                                                      Nick, Lang);
-                             _ ->
-                                 ErrText =
-                                     <<"You must fill in field \"Nickname\" "
-                                       "in the form">>,
-                                 {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
-                           end
-                     end;
-                 _ -> {error, ?ERR_BAD_REQUEST}
-               end;
-           _ -> {error, ?ERR_BAD_REQUEST}
-         end;
-      _ ->
-         iq_set_register_info(ServerHost, Host, From, <<"">>,
-                              Lang)
+process_iq_register_set(ServerHost, Host, From,
+                       #register{remove = true}, Lang) ->
+    iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
+process_iq_register_set(_ServerHost, _Host, _From,
+                       #register{xdata = #xdata{type = cancel}}, _Lang) ->
+    {result, undefined};
+process_iq_register_set(ServerHost, Host, From,
+                       #register{nick = Nick, xdata = XData}, Lang) ->
+    case XData of
+       #xdata{type = submit, fields = Fs} ->
+           case lists:keyfind(<<"nick">>, #xdata_field.var, Fs) of
+               #xdata_field{values = [N]} ->
+                   iq_set_register_info(ServerHost, Host, From, N, Lang);
+               _ ->
+                   ErrText = <<"You must fill in field \"Nickname\" in the form">>,
+                   {error, xmpp:err_not_acceptable(ErrText, Lang)}
+           end;
+       #xdata{} ->
+           Txt = <<"Incorrect data form">>,
+           {error, xmpp:err_bad_request(Txt, Lang)};
+       _ when is_binary(Nick), Nick /= <<"">> ->
+           iq_set_register_info(ServerHost, Host, From, Nick, Lang);
+       _ ->
+           ErrText = <<"You must fill in field \"Nickname\" in the form">>,
+           {error, xmpp:err_not_acceptable(ErrText, Lang)}
     end.
 
-iq_get_vcard(Lang) ->
-    [#xmlel{name = <<"FN">>, attrs = [],
-           children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
-     #xmlel{name = <<"URL">>, attrs = [],
-           children = [{xmlcdata, ?EJABBERD_URI}]},
-     #xmlel{name = <<"DESC">>, attrs = [],
-           children =
-               [{xmlcdata,
-                 <<(translate:translate(Lang,
-                                        <<"ejabberd MUC module">>))/binary,
-                   "\nCopyright (c) 2003-2016 ProcessOne">>}]}].
-
 broadcast_service_message(Host, Msg) ->
     lists:foreach(
        fun(#muc_online_room{pid = Pid}) ->
index 0b5e79f603ce57af9dfc3c4dc1b2db32bee0056a..a7ba1613800eb3f1bd8dfcc600115d8ca74ecc76 100644 (file)
@@ -24,7 +24,7 @@
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("mod_muc_room.hrl").
 -include("mod_muc.hrl").
 -include("ejabberd_http.hrl").
@@ -241,7 +241,7 @@ web_menu_host(Acc, _Host, Lang) ->
 
 -define(TDTD(L, N),
        ?XE(<<"tr">>, [?XCT(<<"td">>, L),
-                      ?XC(<<"td">>, jlib:integer_to_binary(N))
+                      ?XC(<<"td">>, integer_to_binary(N))
                      ])).
 
 web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
@@ -283,7 +283,7 @@ get_sort_query(Q) ->
 
 get_sort_query2(Q) ->
     {value, {_, String}} = lists:keysearch(<<"sort">>, 1, Q),
-    Integer = jlib:binary_to_integer(String),
+    Integer = binary_to_integer(String),
     case Integer >= 0 of
        true -> {ok, {normal, Integer}};
        false -> {ok, {reverse, abs(Integer)}}
@@ -309,7 +309,7 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
     {Titles_TR, _} =
        lists:mapfoldl(
          fun(Title, Num_column) ->
-                 NCS = jlib:integer_to_binary(Num_column),
+                 NCS = integer_to_binary(Num_column),
                  TD = ?XE(<<"td">>, [?CT(Title),
                                      ?C(<<" ">>),
                                      ?AC(<<"?sort=", NCS/binary>>, <<"<">>),
@@ -383,7 +383,7 @@ prepare_room_info(Room_info) ->
      Just_created,
      Title} = Room_info,
     [NameHost,
-     jlib:integer_to_binary(Num_participants),
+     integer_to_binary(Num_participants),
      Ts_last_message,
      jlib:atom_to_binary(Public),
      jlib:atom_to_binary(Persistent),
@@ -830,7 +830,7 @@ get_options(Config) ->
     Fields = [jlib:atom_to_binary(Field) || Field <- record_info(fields, config)],
     [config | ValuesRaw] = tuple_to_list(Config),
     Values = lists:map(fun(V) when is_atom(V) -> jlib:atom_to_binary(V);
-                          (V) when is_integer(V) -> jlib:integer_to_binary(V);
+                          (V) when is_integer(V) -> integer_to_binary(V);
                           (V) when is_tuple(V); is_list(V) -> list_to_binary(hd(io_lib:format("~w", [V])));
                           (V) -> V end, ValuesRaw),
     lists:zip(Fields, Values).
index ec4711b4325a8cca5681ccd2f50c895ec33ad56e..4b129ce815f88c9af7a4ed9d7420b6e553404643 100644 (file)
@@ -46,7 +46,7 @@
 -include("ejabberd.hrl").
 -include("logger.hrl").
 
--include("jlib.hrl").
+-include("xmpp.hrl").
 -include("mod_muc.hrl").
 -include("mod_muc_room.hrl").
 
@@ -196,15 +196,13 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
 add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
     case has_no_permanent_store_hint(Packet) of
        false ->
-           case {fxml:get_subtag(Packet, <<"subject">>),
-                   fxml:get_subtag(Packet, <<"body">>)}
-           of
-               {false, false} -> ok;
-               {false, SubEl} ->
-                   Message = {body, fxml:get_tag_cdata(SubEl)},
+           case {Packet#message.subject, Packet#message.body} of
+               {[], []} -> ok;
+               {[], Body} ->
+                   Message = {body, xmpp:get_text(Body)},
                    add_message_to_log(Nick, Message, Room, Opts, State);
-               {SubEl, _} ->
-                   Message = {subject, fxml:get_tag_cdata(SubEl)},
+               {Subj, _} ->
+                   Message = {subject, xmpp:get_text(Subj)},
                    add_message_to_log(Nick, Message, Room, Opts, State)
            end;
        true -> ok
@@ -1035,7 +1033,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
                                           max_users ->
                                               <<"<div class=\"rcot\">",
                                                 OptText/binary, ": \"",
-                                                (htmlize(jlib:integer_to_binary(T),
+                                                (htmlize(integer_to_binary(T),
                                                          FileFormat))/binary,
                                                 "\"</div>">>;
                                           title ->
@@ -1053,7 +1051,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
                                           allow_private_messages_from_visitors ->
                                               <<"<div class=\"rcot\">",
                                                 OptText/binary, ": \"",
-                                                (htmlize(?T((jlib:atom_to_binary(T))),
+                                                (htmlize(?T(jlib:atom_to_binary(T)),
                                                          FileFormat))/binary,
                                                 "\"</div>">>;
                                           _ -> <<"\"", T/binary, "\"">>
@@ -1168,7 +1166,7 @@ get_room_occupants(RoomJIDString) ->
     [{U#user.jid, U#user.nick, U#user.role}
      || {_, U} <- (?DICT):to_list(StateData#state.users)].
 
--spec get_room_state(binary(), binary()) -> muc_room_state().
+-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
 
 get_room_state(RoomName, MucService) ->
     case mnesia:dirty_read(muc_online_room,
@@ -1180,7 +1178,7 @@ get_room_state(RoomName, MucService) ->
       [] -> #state{}
     end.
 
--spec get_room_state(pid()) -> muc_room_state().
+-spec get_room_state(pid()) -> mod_muc_room:state().
 
 get_room_state(RoomPid) ->
     {ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
@@ -1204,14 +1202,10 @@ fjoin(FileList) ->
     list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
 
 has_no_permanent_store_hint(Packet) ->
-    fxml:get_subtag_with_xmlns(Packet, <<"no-store">>, ?NS_HINTS)
-      =/= false orelse
-    fxml:get_subtag_with_xmlns(Packet, <<"no-storage">>, ?NS_HINTS)
-      =/= false orelse
-    fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-store">>, ?NS_HINTS)
-      =/= false orelse
-    fxml:get_subtag_with_xmlns(Packet, <<"no-permanent-storage">>, ?NS_HINTS)
-      =/= false.
+    xmpp:has_subtag(Packet, #hint{type = 'no-store'}) orelse
+    xmpp:has_subtag(Packet, #hint{type = 'no-storage'}) orelse
+    xmpp:has_subtag(Packet, #hint{type = 'no-permanent-store'}) orelse
+    xmpp:has_subtag(Packet, #hint{type = 'no-permanent-storage'}).
 
 mod_opt_type(access_log) ->
     fun (A) when is_atom(A) -> A end;
index 773953c4a13b5e5601f0a0174df836eb9ff1281c..29b7942cf0c022338c08ff5a09a495a43b9bc8d8 100644 (file)
@@ -51,7 +51,7 @@
 -include("ejabberd.hrl").
 -include("logger.hrl").
 
--include("jlib.hrl").
+-include("xmpp.hrl").
 
 -include("mod_muc_room.hrl").
 
 
 -endif.
 
+-type state() :: #state{}.
+-type fsm_stop() :: {stop, normal, state()}.
+-type fsm_next() :: {next_state, normal_state, state()}.
+-type fsm_transition() :: fsm_stop() | fsm_next().
+-type history_element() :: {binary(), %% nick
+                           message(), %% message itself
+                           boolean(), %% have subject
+                           erlang:timestamp(),
+                           non_neg_integer()}.
+
+-export_type([state/0]).
+
 %%%----------------------------------------------------------------------
 %%% API
 %%%----------------------------------------------------------------------
@@ -133,349 +145,187 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
     {ok, normal_state, State}.
 
 normal_state({route, From, <<"">>,
-             #xmlel{name = <<"message">>, attrs = Attrs,
-                    children = Els} =
-                 Packet},
-            StateData) ->
-    Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+             #message{type = Type, lang = Lang} = Packet}, StateData) ->
     case is_user_online(From, StateData) orelse
-          is_user_allowed_message_nonparticipant(From, StateData)
-       of
-      true ->
-         case fxml:get_attr_s(<<"type">>, Attrs) of
-           <<"groupchat">> ->
-               Activity = get_user_activity(From, StateData),
-               Now = p1_time_compat:system_time(micro_seconds),
-               MinMessageInterval =
-                   trunc(gen_mod:get_module_opt(StateData#state.server_host,
-                                                mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0)
-                          * 1000000),
-               Size = element_size(Packet),
-               {MessageShaper, MessageShaperInterval} =
-                   shaper:update(Activity#activity.message_shaper, Size),
-               if Activity#activity.message /= undefined ->
-                      ErrText = <<"Traffic rate limit is exceeded">>,
-                      Err = jlib:make_error_reply(Packet,
-                                                  ?ERRT_RESOURCE_CONSTRAINT(Lang,
-                                                                            ErrText)),
-                      ejabberd_router:route(StateData#state.jid, From, Err),
-                      {next_state, normal_state, StateData};
-                  Now >=
-                    Activity#activity.message_time + MinMessageInterval,
-                  MessageShaperInterval == 0 ->
-                      {RoomShaper, RoomShaperInterval} =
-                          shaper:update(StateData#state.room_shaper, Size),
-                      RoomQueueEmpty =
-                          queue:is_empty(StateData#state.room_queue),
-                      if RoomShaperInterval == 0, RoomQueueEmpty ->
-                             NewActivity = Activity#activity{message_time =
-                                                                 Now,
-                                                             message_shaper =
-                                                                 MessageShaper},
-                             StateData1 = store_user_activity(From,
-                                                              NewActivity,
-                                                              StateData),
-                             StateData2 = StateData1#state{room_shaper =
-                                                               RoomShaper},
-                             process_groupchat_message(From, Packet,
-                                                       StateData2);
-                         true ->
-                             StateData1 = if RoomQueueEmpty ->
-                                                 erlang:send_after(RoomShaperInterval,
-                                                                   self(),
-                                                                   process_room_queue),
-                                                 StateData#state{room_shaper =
-                                                                     RoomShaper};
-                                             true -> StateData
-                                          end,
-                             NewActivity = Activity#activity{message_time =
-                                                                 Now,
-                                                             message_shaper =
-                                                                 MessageShaper,
-                                                             message = Packet},
-                             RoomQueue = queue:in({message, From},
-                                                  StateData#state.room_queue),
-                             StateData2 = store_user_activity(From,
-                                                              NewActivity,
-                                                              StateData1),
-                             StateData3 = StateData2#state{room_queue =
-                                                               RoomQueue},
-                             {next_state, normal_state, StateData3}
-                      end;
-                  true ->
-                      MessageInterval = (Activity#activity.message_time +
-                                           MinMessageInterval
-                                           - Now)
-                                          div 1000,
-                      Interval = lists:max([MessageInterval,
-                                            MessageShaperInterval]),
-                      erlang:send_after(Interval, self(),
-                                        {process_user_message, From}),
-                      NewActivity = Activity#activity{message = Packet,
-                                                      message_shaper =
-                                                          MessageShaper},
-                      StateData1 = store_user_activity(From, NewActivity,
-                                                       StateData),
-                      {next_state, normal_state, StateData1}
-               end;
-           <<"error">> ->
-               case is_user_online(From, StateData) of
-                 true ->
-                     ErrorText = <<"It is not allowed to send error messages to the"
-                                   " room. The participant (~s) has sent an error "
-                                   "message (~s) and got kicked from the room">>,
-                     NewState = expulse_participant(Packet, From, StateData,
-                                                    translate:translate(Lang,
-                                                                        ErrorText)),
-                     close_room_if_temporary_and_empty(NewState);
-                 _ -> {next_state, normal_state, StateData}
-               end;
-           <<"chat">> ->
-               ErrText =
-                   <<"It is not allowed to send private messages "
-                     "to the conference">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_NOT_ACCEPTABLE(Lang,
-                                                                ErrText)),
-               ejabberd_router:route(StateData#state.jid, From, Err),
-               {next_state, normal_state, StateData};
-           Type when (Type == <<"">>) or (Type == <<"normal">>) ->
-               IsInvitation = is_invitation(Els),
-               IsVoiceRequest = is_voice_request(Els) and
-                                  is_visitor(From, StateData),
-               IsVoiceApprovement = is_voice_approvement(Els) and
-                                      not is_visitor(From, StateData),
-               if IsInvitation ->
-                      case catch check_invitation(From, Packet, Lang, StateData)
-                          of
-                        {error, Error} ->
-                            Err = jlib:make_error_reply(Packet, Error),
-                            ejabberd_router:route(StateData#state.jid, From, Err),
-                            {next_state, normal_state, StateData};
-                        IJID ->
-                            Config = StateData#state.config,
-                            case Config#config.members_only of
-                              true ->
-                                  case get_affiliation(IJID, StateData) of
-                                    none ->
-                                        NSD = set_affiliation(IJID, member,
-                                                              StateData),
-                                        send_affiliation(IJID, member,
-                                                         StateData),
-                                        store_room(NSD),
-                                        {next_state, normal_state, NSD};
-                                    _ -> {next_state, normal_state, StateData}
-                                  end;
-                              false -> {next_state, normal_state, StateData}
-                            end
-                      end;
-                  IsVoiceRequest ->
-                      NewStateData = case
-                                       (StateData#state.config)#config.allow_voice_requests
-                                         of
-                                       true ->
-                                           MinInterval =
-                                               (StateData#state.config)#config.voice_request_min_interval,
-                                           BareFrom =
-                                               jid:remove_resource(jid:tolower(From)),
-                                           NowPriority = -p1_time_compat:system_time(micro_seconds),
-                                           CleanPriority = NowPriority +
-                                                             MinInterval *
-                                                               1000000,
-                                           Times =
-                                               clean_treap(StateData#state.last_voice_request_time,
-                                                           CleanPriority),
-                                           case treap:lookup(BareFrom, Times)
-                                               of
-                                             error ->
-                                                 Times1 =
-                                                     treap:insert(BareFrom,
-                                                                  NowPriority,
-                                                                  true, Times),
-                                                 NSD =
-                                                     StateData#state{last_voice_request_time
-                                                                         =
-                                                                         Times1},
-                                                 send_voice_request(From, NSD),
-                                                 NSD;
-                                             {ok, _, _} ->
-                                                 ErrText =
-                                                     <<"Please, wait for a while before sending "
-                                                       "new voice request">>,
-                                                 Err =
-                                                     jlib:make_error_reply(Packet,
-                                                                           ?ERRT_NOT_ACCEPTABLE(Lang,
-                                                                                                ErrText)),
-                                                 ejabberd_router:route(StateData#state.jid,
-                                                              From, Err),
-                                                 StateData#state{last_voice_request_time
-                                                                     = Times}
-                                           end;
-                                       false ->
-                                           ErrText =
-                                               <<"Voice requests are disabled in this "
-                                                 "conference">>,
-                                           Err = jlib:make_error_reply(Packet,
-                                                                       ?ERRT_FORBIDDEN(Lang,
-                                                                                       ErrText)),
-                                           ejabberd_router:route(StateData#state.jid,
-                                                        From, Err),
-                                           StateData
-                                     end,
-                      {next_state, normal_state, NewStateData};
-                  IsVoiceApprovement ->
-                      NewStateData = case is_moderator(From, StateData) of
-                                       true ->
-                                           case
-                                             extract_jid_from_voice_approvement(Els)
-                                               of
-                                             error ->
-                                                 ErrText =
-                                                     <<"Failed to extract JID from your voice "
-                                                       "request approval">>,
-                                                 Err =
-                                                     jlib:make_error_reply(Packet,
-                                                                           ?ERRT_BAD_REQUEST(Lang,
-                                                                                             ErrText)),
-                                                 ejabberd_router:route(StateData#state.jid,
-                                                              From, Err),
-                                                 StateData;
-                                             {ok, TargetJid} ->
-                                                 case is_visitor(TargetJid,
-                                                                 StateData)
-                                                     of
-                                                   true ->
-                                                       Reason = <<>>,
-                                                       NSD =
-                                                           set_role(TargetJid,
-                                                                    participant,
-                                                                    StateData),
-                                                       catch
-                                                         send_new_presence(TargetJid,
-                                                                           Reason,
-                                                                           NSD,
-                                                                            StateData),
-                                                       NSD;
-                                                   _ -> StateData
-                                                 end
-                                           end;
-                                       _ ->
-                                           ErrText =
-                                               <<"Only moderators can approve voice requests">>,
-                                           Err = jlib:make_error_reply(Packet,
-                                                                       ?ERRT_NOT_ALLOWED(Lang,
-                                                                                         ErrText)),
-                                           ejabberd_router:route(StateData#state.jid,
-                                                        From, Err),
-                                           StateData
-                                     end,
-                      {next_state, normal_state, NewStateData};
-                  true -> {next_state, normal_state, StateData}
-               end;
-           _ ->
-               ErrText = <<"Improper message type">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_NOT_ACCEPTABLE(Lang,
-                                                                ErrText)),
-               ejabberd_router:route(StateData#state.jid, From, Err),
-               {next_state, normal_state, StateData}
-         end;
-      _ ->
-         case fxml:get_attr_s(<<"type">>, Attrs) of
-           <<"error">> -> ok;
-           _ ->
-               handle_roommessage_from_nonparticipant(Packet, Lang,
-                                                      StateData, From)
-         end,
-         {next_state, normal_state, StateData}
-    end;
-normal_state({route, From, <<"">>,
-             #xmlel{name = <<"iq">>} = Packet},
-            StateData) ->
-    case jlib:iq_query_info(Packet) of
-       reply ->
-           {next_state, normal_state, StateData};
-       IQ0 ->
-           case ejabberd_hooks:run_fold(
-                  muc_process_iq,
-                  StateData#state.server_host,
-                  IQ0, [StateData, From, StateData#state.jid]) of
-               ignore ->
-                   {next_state, normal_state, StateData};
-               #iq{type = T} = IQRes when T == error; T == result ->
-                   ejabberd_router:route(StateData#state.jid, From, jlib:iq_to_xml(IQRes)),
+       is_user_allowed_message_nonparticipant(From, StateData) of
+       true when Type == groupchat ->
+           Activity = get_user_activity(From, StateData),
+           Now = p1_time_compat:system_time(micro_seconds),
+           MinMessageInterval = trunc(gen_mod:get_module_opt(
+                                        StateData#state.server_host,
+                                        mod_muc, min_message_interval,
+                                        fun(MMI) when is_number(MMI) -> MMI end, 0)
+                                      * 1000000),
+           Size = element_size(Packet),
+           {MessageShaper, MessageShaperInterval} =
+               shaper:update(Activity#activity.message_shaper, Size),
+           if Activity#activity.message /= undefined ->
+                   ErrText = <<"Traffic rate limit is exceeded">>,
+                   Err = xmpp:make_error(
+                           Packet,
+                           xmpp:err_resource_constraint(ErrText, Lang)),
+                   ejabberd_router:route(StateData#state.jid, From, Err),
                    {next_state, normal_state, StateData};
-               #iq{type = Type, xmlns = XMLNS, lang = Lang,
-                   sub_el = #xmlel{name = SubElName, attrs = Attrs} = SubEl} = IQ
-                 when (XMLNS == (?NS_MUC_ADMIN)) or
-                      (XMLNS == (?NS_MUC_OWNER))
-                      or (XMLNS == (?NS_DISCO_INFO))
-                      or (XMLNS == (?NS_DISCO_ITEMS))
-                      or (XMLNS == (?NS_VCARD))
-                      or (XMLNS == (?NS_MUCSUB))
-                      or (XMLNS == (?NS_CAPTCHA)) ->
-                   Res1 = case XMLNS of
-                              ?NS_MUC_ADMIN ->
-                                  process_iq_admin(From, Type, Lang, SubEl, StateData);
-                              ?NS_MUC_OWNER ->
-                                  process_iq_owner(From, Type, Lang, SubEl, StateData);
-                              ?NS_DISCO_INFO ->
-                                  case fxml:get_attr(<<"node">>, Attrs) of
-                                      false -> process_iq_disco_info(From, Type, Lang, StateData);
-                                      {value, _} ->
-                                          Txt = <<"Disco info is not available for this node">>,
-                                          {error, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)}
-                                  end;
-                              ?NS_DISCO_ITEMS ->
-                                  process_iq_disco_items(From, Type, Lang, StateData);
-                              ?NS_VCARD ->
-                                  process_iq_vcard(From, Type, Lang, SubEl, StateData);
-                              ?NS_MUCSUB ->
-                                  process_iq_mucsub(From, Packet, IQ, StateData);
-                              ?NS_CAPTCHA ->
-                                  process_iq_captcha(From, Type, Lang, SubEl, StateData)
-                          end,
-                   {IQRes, NewStateData} =
-                       case Res1 of
-                           {result, Res, SD} ->
-                               {IQ#iq{type = result,
-                                      sub_el =
-                                          [#xmlel{name = SubElName,
-                                                  attrs =
-                                                      [{<<"xmlns">>,
-                                                        XMLNS}],
-                                                  children = Res}]},
-                                SD};
-                           {ignore, SD} -> {ignore, SD};
-                           {error, Error, ResStateData} ->
-                               {IQ#iq{type = error,
-                                      sub_el = [SubEl, Error]},
-                                ResStateData};
-                           {error, Error} ->
-                               {IQ#iq{type = error,
-                                      sub_el = [SubEl, Error]},
-                                StateData}
-                       end,
-                   if IQRes /= ignore ->
-                           ejabberd_router:route(
-                             StateData#state.jid, From, jlib:iq_to_xml(IQRes));
+              Now >= Activity#activity.message_time + MinMessageInterval,
+              MessageShaperInterval == 0 ->
+                   {RoomShaper, RoomShaperInterval} =
+                       shaper:update(StateData#state.room_shaper, Size),
+                   RoomQueueEmpty = queue:is_empty(StateData#state.room_queue),
+                   if RoomShaperInterval == 0, RoomQueueEmpty ->
+                           NewActivity = Activity#activity{
+                                           message_time = Now,
+                                           message_shaper = MessageShaper},
+                           StateData1 = store_user_activity(From,
+                                                            NewActivity,
+                                                            StateData),
+                           StateData2 = StateData1#state{room_shaper =
+                                                             RoomShaper},
+                           process_groupchat_message(From, Packet,
+                                                     StateData2);
                       true ->
-                           ok
-                   end,
-                   case NewStateData of
-                       stop -> {stop, normal, StateData};
-                       _ -> {next_state, normal_state, NewStateData}
+                           StateData1 = if RoomQueueEmpty ->
+                                                erlang:send_after(RoomShaperInterval,
+                                                                  self(),
+                                                                  process_room_queue),
+                                                StateData#state{room_shaper =
+                                                                    RoomShaper};
+                                           true -> StateData
+                                        end,
+                           NewActivity = Activity#activity{
+                                           message_time = Now,
+                                           message_shaper = MessageShaper,
+                                           message = Packet},
+                           RoomQueue = queue:in({message, From},
+                                                StateData#state.room_queue),
+                           StateData2 = store_user_activity(From,
+                                                            NewActivity,
+                                                            StateData1),
+                           StateData3 = StateData2#state{room_queue = RoomQueue},
+                           {next_state, normal_state, StateData3}
                    end;
+              true ->
+                   MessageInterval = (Activity#activity.message_time +
+                                          MinMessageInterval - Now) div 1000,
+                   Interval = lists:max([MessageInterval,
+                                         MessageShaperInterval]),
+                   erlang:send_after(Interval, self(),
+                                     {process_user_message, From}),
+                   NewActivity = Activity#activity{
+                                   message = Packet,
+                                   message_shaper = MessageShaper},
+                   StateData1 = store_user_activity(From, NewActivity, StateData),
+                   {next_state, normal_state, StateData1}
+           end;
+       true when Type == error ->
+           case is_user_online(From, StateData) of
+               true ->
+                   ErrorText = <<"It is not allowed to send error messages to the"
+                                 " room. The participant (~s) has sent an error "
+                                 "message (~s) and got kicked from the room">>,
+                   NewState = expulse_participant(Packet, From, StateData,
+                                                  translate:translate(Lang,
+                                                                      ErrorText)),
+                   close_room_if_temporary_and_empty(NewState);
                _ ->
-                   Err = jlib:make_error_reply(Packet,
-                                               ?ERR_FEATURE_NOT_IMPLEMENTED),
-                   ejabberd_router:route(StateData#state.jid, From, Err),
                    {next_state, normal_state, StateData}
-           end
+           end;
+       true when Type == chat ->
+           ErrText = <<"It is not allowed to send private messages "
+                       "to the conference">>,
+           Err = xmpp:err_not_acceptable(ErrText, Lang),
+           ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
+           {next_state, normal_state, StateData};
+       true when Type == normal ->
+           {next_state, normal_state,
+            try xmpp:decode_els(Packet) of
+                Pkt -> process_normal_message(From, Pkt, StateData)
+            catch _:{xmpp_codec, Why} ->
+                    Txt = xmpp:format_error(Why),
+                    Err = xmpp:err_bad_request(Txt, Lang),
+                    ejabberd_router:route_error(
+                      StateData#state.jid, From, Packet, Err),
+                    StateData
+            end};
+       true ->
+           ErrText = <<"Improper message type">>,
+           Err = xmpp:err_not_acceptable(ErrText, Lang),
+           ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
+           {next_state, normal_state, StateData};
+       false when Type /= error ->
+           handle_roommessage_from_nonparticipant(Packet, StateData, From);
+       false ->
+           {next_state, normal_state, StateData}
     end;
-normal_state({route, From, Nick,
-             #xmlel{name = <<"presence">>} = Packet},
-            StateData) ->
+normal_state({route, From, <<"">>,
+             #iq{type = Type, lang = Lang, sub_els = [_]} = IQ0},
+            StateData) when Type == get; Type == set ->
+    try
+       case ejabberd_hooks:run_fold(
+              muc_process_iq,
+              StateData#state.server_host,
+              xmpp:set_from_to(xmpp:decode_els(IQ0),
+                               From, StateData#state.jid),
+              [StateData]) of
+           ignore ->
+               {next_state, normal_state, StateData};
+           #iq{type = T} = IQRes when T == error; T == result ->
+               ejabberd_router:route(StateData#state.jid, From, IQRes),
+               {next_state, normal_state, StateData};
+           #iq{sub_els = [SubEl]} = IQ ->
+               Res1 = case xmpp:get_ns(SubEl) of
+                          ?NS_MUC_ADMIN ->
+                              process_iq_admin(From, IQ, StateData);
+                          ?NS_MUC_OWNER ->
+                              process_iq_owner(From, IQ, StateData);
+                          ?NS_DISCO_INFO when SubEl#disco_info.node == undefined ->
+                              process_iq_disco_info(From, IQ, StateData);
+                          ?NS_DISCO_INFO ->
+                              Txt = <<"Disco info is not available for this node">>,
+                              {error, xmpp:err_service_unavailable(Txt, Lang)};
+                          ?NS_DISCO_ITEMS ->
+                              process_iq_disco_items(From, IQ, StateData);
+                          ?NS_VCARD ->
+                              process_iq_vcard(From, IQ, StateData);
+                          ?NS_MUCSUB ->
+                              process_iq_mucsub(From, IQ, StateData);
+                          ?NS_CAPTCHA ->
+                              process_iq_captcha(From, IQ, StateData);
+                          _ ->
+                              {error, xmpp:err_feature_not_implemented()}
+                      end,
+               {IQRes, NewStateData} =
+                   case Res1 of
+                       {result, Res, SD} ->
+                           {xmpp:make_iq_result(IQ, Res), SD};
+                       {result, Res} ->
+                           {xmpp:make_iq_result(IQ, Res), StateData};
+                       {ignore, SD} ->
+                           {ignore, SD};
+                       {error, Error, ResStateData} ->
+                           {xmpp:make_error(IQ0, Error), ResStateData};
+                       {error, Error} ->
+                           {xmpp:make_error(IQ0, Error), StateData}
+                   end,
+               if IQRes /= ignore ->
+                       ejabberd_router:route(StateData#state.jid, From, IQRes);
+                  true ->
+                       ok
+               end,
+               case NewStateData of
+                   stop -> {stop, normal, StateData};
+                   _ -> {next_state, normal_state, NewStateData}
+               end
+       end
+    catch _:{xmpp_codec, Why} ->
+           ErrTxt = xmpp:format_error(Why),
+           Err = xmpp:make_error(IQ0, xmpp:err_bad_request(ErrTxt, Lang)),
+           ejabberd_router:route(StateData#state.jid, From, Err)
+    end;
+normal_state({route, From, <<"">>, #iq{} = IQ}, StateData) ->
+    Err = xmpp:err_bad_request(),
+    ejabberd_router:route_error(StateData#state.jid, From, IQ, Err),
+    {next_state, normal_state, StateData};
+normal_state({route, From, Nick, #presence{} = Packet}, StateData) ->
     Activity = get_user_activity(From, StateData),
     Now = p1_time_compat:system_time(micro_seconds),
     MinPresenceInterval =
@@ -485,185 +335,135 @@ normal_state({route, From, Nick,
                                              I
                                      end, 0)
               * 1000000),
-    if (Now >=
-         Activity#activity.presence_time + MinPresenceInterval)
-        and (Activity#activity.presence == undefined) ->
-          NewActivity = Activity#activity{presence_time = Now},
-          StateData1 = store_user_activity(From, NewActivity,
-                                           StateData),
-          process_presence(From, Nick, Packet, StateData1);
+    if (Now >= Activity#activity.presence_time + MinPresenceInterval)
+       and (Activity#activity.presence == undefined) ->
+           NewActivity = Activity#activity{presence_time = Now},
+           StateData1 = store_user_activity(From, NewActivity,
+                                            StateData),
+           process_presence(From, Nick, Packet, StateData1);
        true ->
-          if Activity#activity.presence == undefined ->
-                 Interval = (Activity#activity.presence_time +
-                               MinPresenceInterval
-                               - Now)
-                              div 1000,
-                 erlang:send_after(Interval, self(),
-                                   {process_user_presence, From});
-             true -> ok
-          end,
-          NewActivity = Activity#activity{presence =
-                                              {Nick, Packet}},
-          StateData1 = store_user_activity(From, NewActivity,
-                                           StateData),
-          {next_state, normal_state, StateData1}
+           if Activity#activity.presence == undefined ->
+                   Interval = (Activity#activity.presence_time +
+                                   MinPresenceInterval - Now) div 1000,
+                   erlang:send_after(Interval, self(),
+                                     {process_user_presence, From});
+              true -> ok
+           end,
+           NewActivity = Activity#activity{presence = {Nick, Packet}},
+           StateData1 = store_user_activity(From, NewActivity,
+                                            StateData),
+           {next_state, normal_state, StateData1}
     end;
 normal_state({route, From, ToNick,
-             #xmlel{name = <<"message">>, attrs = Attrs} = Packet},
+             #message{type = Type, lang = Lang} = Packet},
             StateData) ->
-    Type = fxml:get_attr_s(<<"type">>, Attrs),
-    Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-    case decide_fate_message(Type, Packet, From, StateData)
-       of
-      {expulse_sender, Reason} ->
-         ?DEBUG(Reason, []),
-         ErrorText = <<"It is not allowed to send error messages to the"
-                   " room. The participant (~s) has sent an error "
-                   "message (~s) and got kicked from the room">>,
-         NewState = expulse_participant(Packet, From, StateData,
-                                        translate:translate(Lang, ErrorText)),
-         {next_state, normal_state, NewState};
-      forget_message -> {next_state, normal_state, StateData};
-      continue_delivery ->
-         case
-           {(StateData#state.config)#config.allow_private_messages,
-            is_user_online(From, StateData)}
-             of
-           {true, true} ->
-               case Type of
-                 <<"groupchat">> ->
-                     ErrText =
-                         <<"It is not allowed to send private messages "
-                           "of type \"groupchat\"">>,
-                     Err = jlib:make_error_reply(Packet,
-                                                 ?ERRT_BAD_REQUEST(Lang,
-                                                                   ErrText)),
-                     ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                            ToNick),
-                                  From, Err);
-                 _ ->
-                     case find_jids_by_nick(ToNick, StateData) of
-                       false ->
-                           ErrText =
-                               <<"Recipient is not in the conference room">>,
-                           Err = jlib:make_error_reply(Packet,
-                                                       ?ERRT_ITEM_NOT_FOUND(Lang,
-                                                                            ErrText)),
-                           ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                                  ToNick),
-                                        From, Err);
+    case decide_fate_message(Packet, From, StateData) of
+       {expulse_sender, Reason} ->
+           ?DEBUG(Reason, []),
+           ErrorText = <<"It is not allowed to send error messages to the"
+                         " room. The participant (~s) has sent an error "
+                         "message (~s) and got kicked from the room">>,
+           NewState = expulse_participant(Packet, From, StateData,
+                                          translate:translate(Lang, ErrorText)),
+           {next_state, normal_state, NewState};
+       forget_message ->
+           {next_state, normal_state, StateData};
+       continue_delivery ->
+           case {(StateData#state.config)#config.allow_private_messages,
+                 is_user_online(From, StateData)} of
+               {true, true} when Type == groupchat ->
+                   ErrText = <<"It is not allowed to send private messages "
+                               "of type \"groupchat\"">>,
+                   Err = xmpp:err_bad_request(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     jid:replace_resource(StateData#state.jid, ToNick),
+                     From, Packet, Err);
+               {true, true} ->
+                   case find_jids_by_nick(ToNick, StateData) of
+                       [] ->
+                           ErrText = <<"Recipient is not in the conference room">>,
+                           Err = xmpp:err_item_not_found(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             jid:replace_resource(StateData#state.jid, ToNick),
+                             From, Packet, Err);
                        ToJIDs ->
                            SrcIsVisitor = is_visitor(From, StateData),
-                           DstIsModerator = is_moderator(hd(ToJIDs),
-                                                         StateData),
+                           DstIsModerator = is_moderator(hd(ToJIDs), StateData),
                            PmFromVisitors =
                                (StateData#state.config)#config.allow_private_messages_from_visitors,
                            if SrcIsVisitor == false;
                               PmFromVisitors == anyone;
                               (PmFromVisitors == moderators) and
-                                DstIsModerator ->
-                                  {ok, #user{nick = FromNick}} =
-                                      (?DICT):find(jid:tolower(From),
-                                                   StateData#state.users),
-                                  FromNickJID =
-                                      jid:replace_resource(StateData#state.jid,
-                                                                FromNick),
-                                  X = #xmlel{name = <<"x">>,
-                                             attrs = [{<<"xmlns">>, ?NS_MUC_USER}]},
-                                  PrivMsg = fxml:append_subtags(Packet, [X]),
-                                  [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
-                                   || ToJID <- ToJIDs];
+                              DstIsModerator ->
+                                   {ok, #user{nick = FromNick}} =
+                                       (?DICT):find(jid:tolower(From),
+                                                    StateData#state.users),
+                                   FromNickJID =
+                                       jid:replace_resource(StateData#state.jid,
+                                                            FromNick),
+                                   X = #muc_user{},
+                                   PrivMsg = xmpp:set_subtag(Packet, X),
+                                   [ejabberd_router:route(FromNickJID, ToJID, PrivMsg)
+                                    || ToJID <- ToJIDs];
                               true ->
-                                  ErrText =
-                                      <<"It is not allowed to send private messages">>,
-                                  Err = jlib:make_error_reply(Packet,
-                                                              ?ERRT_FORBIDDEN(Lang,
-                                                                              ErrText)),
-                                  ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                                         ToNick),
-                                               From, Err)
+                                   ErrText = <<"It is not allowed to send private messages">>,
+                                   Err = xmpp:err_forbidden(ErrText, Lang),
+                                   ejabberd_router:route_error(
+                                     jid:replace_resource(StateData#state.jid, ToNick),
+                                     From, Packet, Err)
                            end
-                     end
-               end;
-           {true, false} ->
-               ErrText =
-                   <<"Only occupants are allowed to send messages "
-                     "to the conference">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_NOT_ACCEPTABLE(Lang,
-                                                                ErrText)),
-               ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                      ToNick),
-                            From, Err);
-           {false, _} ->
-               ErrText =
-                   <<"It is not allowed to send private messages">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_FORBIDDEN(Lang, ErrText)),
-               ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                      ToNick),
-                            From, Err)
-         end,
+                   end;
+               {true, false} ->
+                   ErrText = <<"Only occupants are allowed to send messages "
+                               "to the conference">>,
+                   Err = xmpp:err_not_acceptable(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     jid:replace_resource(StateData#state.jid, ToNick),
+                     From, Packet, Err);
+               {false, _} ->
+                   ErrText = <<"It is not allowed to send private messages">>,
+                   Err = xmpp:err_forbidden(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     jid:replace_resource(StateData#state.jid, ToNick),
+                     From, Packet, Err)
+           end,
          {next_state, normal_state, StateData}
     end;
 normal_state({route, From, ToNick,
-             #xmlel{name = <<"iq">>, attrs = Attrs} = Packet},
+             #iq{id = StanzaId, lang = Lang} = Packet},
             StateData) ->
-    Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-    StanzaId = fxml:get_attr_s(<<"id">>, Attrs),
     case {(StateData#state.config)#config.allow_query_users,
-         is_user_online_iq(StanzaId, From, StateData)}
-       of
-      {true, {true, NewId, FromFull}} ->
-         case find_jid_by_nick(ToNick, StateData) of
-           false ->
-               case jlib:iq_query_info(Packet) of
-                 reply -> ok;
-                 _ ->
-                     ErrText = <<"Recipient is not in the conference room">>,
-                     Err = jlib:make_error_reply(Packet,
-                                                 ?ERRT_ITEM_NOT_FOUND(Lang,
-                                                                      ErrText)),
-                     ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                            ToNick),
-                                  From, Err)
-               end;
-           ToJID ->
-               {ok, #user{nick = FromNick}} =
-                   (?DICT):find(jid:tolower(FromFull),
-                                StateData#state.users),
-               {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
-                                                   StanzaId, NewId, Packet),
-               ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                      FromNick),
-                            ToJID2, Packet2)
-         end;
-      {_, {false, _, _}} ->
-         case jlib:iq_query_info(Packet) of
-           reply -> ok;
-           _ ->
-               ErrText =
-                   <<"Only occupants are allowed to send queries "
-                     "to the conference">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_NOT_ACCEPTABLE(Lang,
-                                                                ErrText)),
-               ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                      ToNick),
-                            From, Err)
-         end;
-      _ ->
-         case jlib:iq_query_info(Packet) of
-           reply -> ok;
-           _ ->
-               ErrText = <<"Queries to the conference members are "
-                           "not allowed in this room">>,
-               Err = jlib:make_error_reply(Packet,
-                                           ?ERRT_NOT_ALLOWED(Lang, ErrText)),
-               ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                      ToNick),
-                            From, Err)
-         end
+         is_user_online_iq(StanzaId, From, StateData)} of
+       {true, {true, NewId, FromFull}} ->
+           case find_jid_by_nick(ToNick, StateData) of
+               false ->
+                   ErrText = <<"Recipient is not in the conference room">>,
+                   Err = xmpp:err_item_not_found(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     jid:replace_resource(StateData#state.jid, ToNick),
+                     From, Packet, Err);
+               ToJID ->
+                   {ok, #user{nick = FromNick}} =
+                       (?DICT):find(jid:tolower(FromFull), StateData#state.users),
+                   {ToJID2, Packet2} = handle_iq_vcard(ToJID, NewId, Packet),
+                   ejabberd_router:route(
+                     jid:replace_resource(StateData#state.jid, FromNick),
+                     ToJID2, Packet2)
+           end;
+       {_, {false, _, _}} ->
+           ErrText = <<"Only occupants are allowed to send queries "
+                       "to the conference">>,
+           Err = xmpp:err_not_acceptable(ErrText, Lang),
+           ejabberd_router:route_error(
+             jid:replace_resource(StateData#state.jid, ToNick),
+             From, Packet, Err);
+       _ ->
+           ErrText = <<"Queries to the conference members are "
+                       "not allowed in this room">>,
+           Err = xmpp:err_not_allowed(ErrText, Lang),
+           ejabberd_router:route_error(
+             jid:replace_resource(StateData#state.jid, ToNick),
+             From, Packet, Err)
     end,
     {next_state, normal_state, StateData};
 normal_state(_Event, StateData) ->
@@ -671,11 +471,7 @@ normal_state(_Event, StateData) ->
 
 handle_event({service_message, Msg}, _StateName,
             StateData) ->
-    MessagePkt = #xmlel{name = <<"message">>,
-                       attrs = [{<<"type">>, <<"groupchat">>}],
-                       children =
-                           [#xmlel{name = <<"body">>, attrs = [],
-                                   children = [{xmlcdata, Msg}]}]},
+    MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
     send_wrapped_multiple(
       StateData#state.jid,
       StateData#state.users,
@@ -687,22 +483,9 @@ handle_event({service_message, Msg}, _StateName,
     {next_state, normal_state, NSD};
 handle_event({destroy, Reason}, _StateName,
             StateData) ->
-    {result, [], stop} = destroy_room(#xmlel{name =
-                                                <<"destroy">>,
-                                            attrs =
-                                                [{<<"xmlns">>, ?NS_MUC_OWNER}],
-                                            children =
-                                                case Reason of
-                                                  none -> [];
-                                                  _Else ->
-                                                      [#xmlel{name =
-                                                                  <<"reason">>,
-                                                              attrs = [],
-                                                              children =
-                                                                  [{xmlcdata,
-                                                                    Reason}]}]
-                                                end},
-                                     StateData),
+    {result, undefined, stop} =
+       destroy_room(#muc_destroy{xmlns = ?NS_MUC_OWNER, reason = Reason},
+                    StateData),
     ?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
              [jid:to_string(StateData#state.jid), Reason]),
     add_to_log(room_existence, destroyed, StateData),
@@ -710,7 +493,7 @@ handle_event({destroy, Reason}, _StateName,
 handle_event(destroy, StateName, StateData) ->
     ?INFO_MSG("Destroyed MUC room ~s",
              [jid:to_string(StateData#state.jid)]),
-    handle_event({destroy, none}, StateName, StateData);
+    handle_event({destroy, undefined}, StateName, StateData);
 handle_event({set_affiliations, Affiliations},
             StateName, StateData) ->
     {next_state, StateName,
@@ -741,7 +524,7 @@ handle_sync_event(get_state, _From, StateName,
     {reply, {ok, StateData}, StateName, StateData};
 handle_sync_event({change_config, Config}, _From,
                  StateName, StateData) ->
-    {result, [], NSD} = change_config(Config, StateData),
+    {result, undefined, NSD} = change_config(Config, StateData),
     {reply, {ok, NSD#state.config}, StateName, NSD};
 handle_sync_event({change_state, NewStateData}, _From,
                  StateName, _StateData) ->
@@ -821,12 +604,11 @@ handle_info({captcha_failed, From}, normal_state,
                 {ok, {Nick, Packet}} ->
                     Robots = (?DICT):erase(From, StateData#state.robots),
                     Txt = <<"The CAPTCHA verification has failed">>,
-                    Err = jlib:make_error_reply(
-                            Packet, ?ERRT_NOT_AUTHORIZED(?MYLANG, Txt)),
-                    ejabberd_router:route % TODO: s/Nick/""/
-                                (jid:replace_resource(StateData#state.jid,
-                                                           Nick),
-                                 From, Err),
+                    Lang = xmpp:get_lang(Packet),
+                    Err = xmpp:err_not_authorized(Txt, Lang),
+                    ejabberd_router:route_error(
+                      jid:replace_resource(StateData#state.jid, Nick),
+                      From, Packet, Err),
                     StateData#state{robots = Robots};
                 _ -> StateData
               end,
@@ -845,22 +627,12 @@ terminate(Reason, _StateName, StateData) ->
                      "because of a system shutdown">>;
                _ -> <<"Room terminates">>
              end,
-    ItemAttrs = [{<<"affiliation">>, <<"none">>},
-                {<<"role">>, <<"none">>}],
-    ReasonEl = #xmlel{name = <<"reason">>, attrs = [],
-                     children = [{xmlcdata, ReasonT}]},
-    Packet = #xmlel{name = <<"presence">>,
-                   attrs = [{<<"type">>, <<"unavailable">>}],
-                   children =
-                       [#xmlel{name = <<"x">>,
-                               attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
-                               children =
-                                   [#xmlel{name = <<"item">>,
-                                           attrs = ItemAttrs,
-                                           children = [ReasonEl]},
-                                    #xmlel{name = <<"status">>,
-                                           attrs = [{<<"code">>, <<"332">>}],
-                                           children = []}]}]},
+    Packet = #presence{
+               type = unavailable,
+               sub_els = [#muc_user{items = [#muc_item{affiliation = none,
+                                                       reason = ReasonT,
+                                                       role = none}],
+                                    status_codes = [332]}]},
     (?DICT):fold(fun (LJID, Info, _) ->
                         Nick = Info#user.nick,
                         case Reason of
@@ -883,14 +655,12 @@ terminate(Reason, _StateName, StateData) ->
 %%%----------------------------------------------------------------------
 %%% Internal functions
 %%%----------------------------------------------------------------------
-
+-spec route(pid(), jid(), binary(), stanza()) -> ok.
 route(Pid, From, ToNick, Packet) ->
     gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
 
-process_groupchat_message(From,
-                         #xmlel{name = <<"message">>, attrs = Attrs} = Packet,
-                         StateData) ->
-    Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec process_groupchat_message(jid(), message(), state()) -> fsm_next().
+process_groupchat_message(From, #message{lang = Lang} = Packet, StateData) ->
     case is_user_online(From, StateData) orelse
           is_user_allowed_message_nonparticipant(From, StateData)
        of
@@ -932,7 +702,7 @@ process_groupchat_message(From,
                         drop ->
                             {next_state, normal_state, StateData};
                         NewPacket1 ->
-                            NewPacket = fxml:remove_subtags(NewPacket1, <<"nick">>, {<<"xmlns">>, ?NS_NICK}),
+                            NewPacket = xmpp:remove_subtag(NewPacket1, #nick{}),
                             Node = if Subject == false -> ?NS_MUCSUB_NODES_MESSAGES;
                                       true -> ?NS_MUCSUB_NODES_SUBJECT
                                    end,
@@ -951,41 +721,136 @@ process_groupchat_message(From,
                             {next_state, normal_state, NewStateData2}
                       end;
                   _ ->
-                      Err = case
-                              (StateData#state.config)#config.allow_change_subj
-                                of
+                      Err = case (StateData#state.config)#config.allow_change_subj of
                               true ->
-                                  ?ERRT_FORBIDDEN(Lang,
-                                                  <<"Only moderators and participants are "
-                                                    "allowed to change the subject in this "
-                                                    "room">>);
+                                  xmpp:err_forbidden(
+                                    <<"Only moderators and participants are "
+                                      "allowed to change the subject in this "
+                                      "room">>, Lang);
                               _ ->
-                                  ?ERRT_FORBIDDEN(Lang,
-                                                  <<"Only moderators are allowed to change "
-                                                    "the subject in this room">>)
+                                  xmpp:err_forbidden(
+                                    <<"Only moderators are allowed to change "
+                                      "the subject in this room">>, Lang)
                             end,
-                      ejabberd_router:route(StateData#state.jid, From,
-                                   jlib:make_error_reply(Packet, Err)),
+                      ejabberd_router:route_error(
+                        StateData#state.jid, From, Packet, Err),
                       {next_state, normal_state, StateData}
                 end;
             true ->
                 ErrText = <<"Visitors are not allowed to send messages "
                             "to all occupants">>,
-                Err = jlib:make_error_reply(Packet,
-                                            ?ERRT_FORBIDDEN(Lang, ErrText)),
-                ejabberd_router:route(StateData#state.jid, From, Err),
+                Err = xmpp:err_forbidden(ErrText, Lang),
+                ejabberd_router:route_error(
+                  StateData#state.jid, From, Packet, Err),
                 {next_state, normal_state, StateData}
          end;
       false ->
-         ErrText =
-             <<"Only occupants are allowed to send messages "
-               "to the conference">>,
-         Err = jlib:make_error_reply(Packet,
-                                     ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
-         ejabberd_router:route(StateData#state.jid, From, Err),
+         ErrText = <<"Only occupants are allowed to send messages "
+                     "to the conference">>,
+         Err = xmpp:err_not_acceptable(ErrText, Lang),
+         ejabberd_router:route_error(StateData#state.jid, From, Packet, Err),
          {next_state, normal_state, StateData}
     end.
 
+-spec process_normal_message(jid(), message(), state()) -> state().
+process_normal_message(From, #message{lang = Lang} = Pkt, StateData) ->
+    IsInvitation = is_invitation(Pkt),
+    IsVoiceRequest = is_voice_request(Pkt) and
+       is_visitor(From, StateData),
+    IsVoiceApprovement = is_voice_approvement(Pkt) and
+       not is_visitor(From, StateData),
+    if IsInvitation ->
+           case check_invitation(From, Pkt, StateData) of
+               {error, Error} ->
+                   ejabberd_router:route_error(StateData#state.jid, From, Pkt, Error),
+                   StateData;
+               IJID ->
+                   Config = StateData#state.config,
+                   case Config#config.members_only of
+                       true ->
+                           case get_affiliation(IJID, StateData) of
+                               none ->
+                                   NSD = set_affiliation(IJID, member, StateData),
+                                   send_affiliation(IJID, member, StateData),
+                                   store_room(NSD),
+                                   NSD;
+                               _ ->
+                                   StateData
+                           end;
+                       false ->
+                           StateData
+                   end
+           end;
+       IsVoiceRequest ->
+           case (StateData#state.config)#config.allow_voice_requests of
+               true ->
+                   MinInterval = (StateData#state.config)#config.voice_request_min_interval,
+                   BareFrom = jid:remove_resource(jid:tolower(From)),
+                   NowPriority = -p1_time_compat:system_time(micro_seconds),
+                   CleanPriority = NowPriority + MinInterval * 1000000,
+                   Times = clean_treap(StateData#state.last_voice_request_time,
+                                       CleanPriority),
+                   case treap:lookup(BareFrom, Times) of
+                       error ->
+                           Times1 = treap:insert(BareFrom,
+                                                 NowPriority,
+                                                 true, Times),
+                           NSD = StateData#state{last_voice_request_time = Times1},
+                           send_voice_request(From, Lang, NSD),
+                           NSD;
+                       {ok, _, _} ->
+                           ErrText = <<"Please, wait for a while before sending "
+                                       "new voice request">>,
+                           Err = xmpp:err_not_acceptable(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             StateData#state.jid, From, Pkt, Err),
+                           StateData#state{last_voice_request_time = Times}
+                   end;
+               false ->
+                   ErrText = <<"Voice requests are disabled in this conference">>,
+                   Err = xmpp:err_forbidden(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     StateData#state.jid, From, Pkt, Err),
+                   StateData
+           end;
+       IsVoiceApprovement ->
+           case is_moderator(From, StateData) of
+               true ->
+                   case extract_jid_from_voice_approvement(Pkt) of
+                       error ->
+                           ErrText = <<"Failed to extract JID from your voice "
+                                       "request approval">>,
+                           Err = xmpp:err_bad_request(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             StateData#state.jid, From, Pkt, Err),
+                           StateData;
+                       TargetJid ->
+                           case is_visitor(TargetJid, StateData) of
+                               true ->
+                                   Reason = <<>>,
+                                   NSD = set_role(TargetJid,
+                                                  participant,
+                                                  StateData),
+                                   catch send_new_presence(TargetJid,
+                                                           Reason,
+                                                           NSD,
+                                                           StateData),
+                                   NSD;
+                               _ ->
+                                   StateData
+                           end
+                   end;
+               _ ->
+                   ErrText = <<"Only moderators can approve voice requests">>,
+                   Err = xmpp:err_not_allowed(ErrText, Lang),
+                   ejabberd_router:route_error(
+                     StateData#state.jid, From, Pkt, Err),
+                   StateData
+           end;
+       true ->
+           StateData
+    end.
+
 %% @doc Check if this non participant can send message to room.
 %%
 %% XEP-0045 v1.23:
@@ -993,6 +858,7 @@ process_groupchat_message(From,
 %% an implementation MAY allow users with certain privileges
 %% (e.g., a room owner, room admin, or service-level admin)
 %% to send messages to the room even if those users are not occupants.
+-spec is_user_allowed_message_nonparticipant(jid(), state()) -> boolean().
 is_user_allowed_message_nonparticipant(JID,
                                       StateData) ->
     case get_service_affiliation(JID, StateData) of
@@ -1002,6 +868,7 @@ is_user_allowed_message_nonparticipant(JID,
 
 %% @doc Get information of this participant, or default values.
 %% If the JID is not a participant, return values for a service message.
+-spec get_participant_data(jid(), state()) -> {binary(), role(), boolean()}.
 get_participant_data(From, StateData) ->
     case (?DICT):find(jid:tolower(From),
                      StateData#state.users)
@@ -1011,14 +878,11 @@ get_participant_data(From, StateData) ->
       error -> {<<"">>, moderator, false}
     end.
 
-process_presence(From, Nick,
-                #xmlel{name = <<"presence">>, attrs = Attrs0} = Packet0,
-                StateData) ->
-    Type0 = fxml:get_attr_s(<<"type">>, Attrs0),
+-spec process_presence(jid(), binary(), presence(), state()) -> fsm_transition().
+process_presence(From, Nick, #presence{type = Type0} = Packet0, StateData) ->
     IsOnline = is_user_online(From, StateData),
-    IsSubscriber = is_subscriber(From, StateData),
-    if Type0 == <<"">>;
-       IsOnline and ((Type0 == <<"unavailable">>) or (Type0 == <<"error">>)) ->
+    if Type0 == available;
+       IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
           case ejabberd_hooks:run_fold(muc_filter_presence,
                                        StateData#state.server_host,
                                        Packet0,
@@ -1027,119 +891,104 @@ process_presence(From, Nick,
                                         From, Nick]) of
             drop ->
                 {next_state, normal_state, StateData};
-            #xmlel{attrs = Attrs} = Packet ->
-                Type = fxml:get_attr_s(<<"type">>, Attrs),
-                Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
-                StateData1 = case Type of
-                               <<"unavailable">> ->
-                                   NewPacket = case
-                                                 {(StateData#state.config)#config.allow_visitor_status,
-                                                  is_visitor(From, StateData)}
-                                                   of
-                                                 {false, true} ->
-                                                     strip_status(Packet);
-                                                 _ -> Packet
-                                               end,
-                                   NewState = add_user_presence_un(From, NewPacket,
-                                                                   StateData),
-                                   case (?DICT):find(Nick, StateData#state.nicks) of
-                                     {ok, [_, _ | _]} -> ok;
-                                     _ -> send_new_presence(From, NewState, StateData)
-                                   end,
-                                   Reason = case fxml:get_subtag(NewPacket,
-                                                                <<"status">>)
-                                                of
-                                              false -> <<"">>;
-                                              Status_el ->
-                                                  fxml:get_tag_cdata(Status_el)
-                                            end,
-                                   remove_online_user(From, NewState, IsSubscriber, Reason);
-                               <<"error">> ->
-                                   ErrorText = <<"It is not allowed to send error messages to the"
-                                       " room. The participant (~s) has sent an error "
-                                       "message (~s) and got kicked from the room">>,
-                                   expulse_participant(Packet, From, StateData,
-                                                       translate:translate(Lang,
-                                                                           ErrorText));
-                               <<"">> ->
-                                   if not IsOnline ->
-                                          add_new_user(From, Nick, Packet, StateData);
-                                      true ->
-                                          case is_nick_change(From, Nick, StateData) of
-                                            true ->
-                                                case {nick_collision(From, Nick, StateData),
-                                                      mod_muc:can_use_nick(StateData#state.server_host,
-                                                                           StateData#state.host,
-                                                                           From, Nick),
-                                                      {(StateData#state.config)#config.allow_visitor_nickchange,
-                                                       is_visitor(From, StateData)}}
-                                                    of
-                                                  {_, _, {false, true}} ->
-                                                      ErrText =
-                                                          <<"Visitors are not allowed to change their "
-                                                            "nicknames in this room">>,
-                                                      Err = jlib:make_error_reply(Packet,
-                                                                                  ?ERRT_NOT_ALLOWED(Lang,
-                                                                                                    ErrText)),
-                                                      ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                                                             Nick),
-                                                                   From, Err),
-                                                      StateData;
-                                                  {true, _, _} ->
-                                                      Lang = fxml:get_attr_s(<<"xml:lang">>,
-                                                                            Attrs),
-                                                      ErrText =
-                                                          <<"That nickname is already in use by another "
-                                                            "occupant">>,
-                                                      Err = jlib:make_error_reply(Packet,
-                                                                                  ?ERRT_CONFLICT(Lang,
-                                                                                                 ErrText)),
-                                                      ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                                                             Nick), % TODO: s/Nick/""/
-                                                                   From, Err),
-                                                      StateData;
-                                                  {_, false, _} ->
-                                                      ErrText =
-                                                          <<"That nickname is registered by another "
-                                                            "person">>,
-                                                      Err = jlib:make_error_reply(Packet,
-                                                                                  ?ERRT_CONFLICT(Lang,
-                                                                                                 ErrText)),
-                                                      ejabberd_router:route(jid:replace_resource(StateData#state.jid,
-                                                                                             Nick),
-                                                                  From, Err),
-                                                      StateData;
-                                                  _ ->
-                                                      case is_initial_presence(From, StateData) of
-                                                          true ->
-                                                              subscriber_becomes_available(
-                                                                From, Nick, Packet, StateData);
-                                                          false ->
-                                                              change_nick(From, Nick, StateData)
-                                                      end
-                                                end;
-                                            _NotNickChange ->
-                                                case is_initial_presence(From, StateData) of
-                                                    true ->
-                                                        subscriber_becomes_available(
-                                                          From, Nick, Packet, StateData);
-                                                    false ->
-                                                        Stanza = maybe_strip_status_from_presence(
-                                                                   From, Packet, StateData),
-                                                        NewState = add_user_presence(From, Stanza,
-                                                                                     StateData),
-                                                        send_new_presence(From, NewState, StateData),
-                                                        NewState
-                                                end
-                                          end
-                                   end
-                             end,
-                close_room_if_temporary_and_empty(StateData1)
+            #presence{} = Packet ->
+                close_room_if_temporary_and_empty(
+                  do_process_presence(From, Nick, Packet, StateData))
           end;
        true ->
-          {next_state, normal_state, StateData}
+           {next_state, normal_state, StateData}
     end.
 
+-spec do_process_presence(jid(), binary(), presence(), state()) ->
+                                state().
+do_process_presence(From, Nick, #presence{type = available, lang = Lang} = Packet,
+                   StateData) ->
+    case is_user_online(From, StateData) of
+       false ->
+           add_new_user(From, Nick, Packet, StateData);
+       true ->
+           case is_nick_change(From, Nick, StateData) of
+               true ->
+                   case {nick_collision(From, Nick, StateData),
+                         mod_muc:can_use_nick(StateData#state.server_host,
+                                              StateData#state.host,
+                                              From, Nick),
+                         {(StateData#state.config)#config.allow_visitor_nickchange,
+                          is_visitor(From, StateData)}} of
+                       {_, _, {false, true}} ->
+                           ErrText = <<"Visitors are not allowed to change their "
+                                       "nicknames in this room">>,
+                           Err = xmpp:err_not_allowed(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             jid:replace_resource(StateData#state.jid, Nick),
+                             From, Packet, Err),
+                           StateData;
+                       {true, _, _} ->
+                           ErrText = <<"That nickname is already in use by another "
+                                       "occupant">>,
+                           Err = xmpp:err_conflict(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             jid:replace_resource(StateData#state.jid, Nick),
+                             From, Packet, Err),
+                           StateData;
+                       {_, false, _} ->
+                           ErrText = <<"That nickname is registered by another "
+                                       "person">>,
+                           Err = xmpp:err_conflict(ErrText, Lang),
+                           ejabberd_router:route_error(
+                             jid:replace_resource(StateData#state.jid, Nick),
+                             From, Packet, Err),
+                           StateData;
+                       _ ->
+                           case is_initial_presence(From, StateData) of
+                               true ->
+                                   subscriber_becomes_available(
+                                     From, Nick, Packet, StateData);
+                               false ->
+                                   change_nick(From, Nick, StateData)
+                           end
+                   end;
+               _NotNickChange ->
+                   case is_initial_presence(From, StateData) of
+                       true ->
+                           subscriber_becomes_available(
+                             From, Nick, Packet, StateData);
+                       false ->
+                           Stanza = maybe_strip_status_from_presence(
+                                      From, Packet, StateData),
+                           NewState = add_user_presence(From, Stanza,
+                                                        StateData),
+                           send_new_presence(From, NewState, StateData),
+                           NewState
+                   end
+           end
+    end;
+do_process_presence(From, Nick, #presence{type = unavailable} = Packet,
+                   StateData) ->
+    IsSubscriber = is_subscriber(From, StateData),
+    NewPacket = case {(StateData#state.config)#config.allow_visitor_status,
+                     is_visitor(From, StateData)} of
+                   {false, true} ->
+                       strip_status(Packet);
+                   _ -> Packet
+               end,
+    NewState = add_user_presence_un(From, NewPacket, StateData),
+    case (?DICT):find(Nick, StateData#state.nicks) of
+       {ok, [_, _ | _]} -> ok;
+       _ -> send_new_presence(From, NewState, StateData)
+    end,
+    Reason = xmpp:get_text(NewPacket#presence.status),
+    remove_online_user(From, NewState, IsSubscriber, Reason);
+do_process_presence(From, _Nick, #presence{type = error, lang = Lang} = Packet,
+                   StateData) ->
+    ErrorText = <<"It is not allowed to send error messages to the"
+                 " room. The participant (~s) has sent an error "
+                 "message (~s) and got kicked from the room">>,
+    expulse_participant(Packet, From, StateData,
+                       translate:translate(Lang, ErrorText)).
+
+-spec maybe_strip_status_from_presence(jid(), presence(),
+                                      state()) -> presence().
 maybe_strip_status_from_presence(From, Packet, StateData) ->
     case {(StateData#state.config)#config.allow_visitor_status,
          is_visitor(From, StateData)} of
@@ -1148,6 +997,8 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
        _Allowed -> Packet
     end.
 
+-spec subscriber_becomes_available(jid(), binary(), presence(),
+                                  state()) -> state().
 subscriber_becomes_available(From, Nick, Packet, StateData) ->
     Stanza = maybe_strip_status_from_presence(From, Packet, StateData),
     State1 = add_user_presence(From, Stanza, StateData),
@@ -1159,9 +1010,10 @@ subscriber_becomes_available(From, Nick, Packet, StateData) ->
     send_initial_presence(From, State3, StateData),
     State3.
 
+-spec close_room_if_temporary_and_empty(state()) -> fsm_transition().
 close_room_if_temporary_and_empty(StateData1) ->
     case not (StateData1#state.config)#config.persistent
-          andalso (?DICT):to_list(StateData1#state.users) == []
+          andalso (?DICT):size(StateData1#state.users) == 0
        of
       true ->
          ?INFO_MSG("Destroyed MUC room ~s because it's temporary "
@@ -1172,10 +1024,12 @@ close_room_if_temporary_and_empty(StateData1) ->
       _ -> {next_state, normal_state, StateData1}
     end.
 
+-spec is_user_online(jid(), state()) -> boolean().
 is_user_online(JID, StateData) ->
     LJID = jid:tolower(JID),
     (?DICT):is_key(LJID, StateData#state.users).
 
+-spec is_subscriber(jid(), state()) -> boolean().
 is_subscriber(JID, StateData) ->
     LJID = jid:tolower(JID),
     case (?DICT):find(LJID, StateData#state.users) of
@@ -1186,6 +1040,7 @@ is_subscriber(JID, StateData) ->
     end.
 
 %% Check if the user is occupant of the room, or at least is an admin or owner.
+-spec is_occupant_or_admin(jid(), state()) -> boolean().
 is_occupant_or_admin(JID, StateData) ->
     FAffiliation = get_affiliation(JID, StateData),
     FRole = get_role(JID, StateData),
@@ -1200,6 +1055,8 @@ is_occupant_or_admin(JID, StateData) ->
 %%%
 %%% Handle IQ queries of vCard
 %%%
+-spec is_user_online_iq(binary(), jid(), state()) ->
+                              {boolean(), binary(), jid()}.
 is_user_online_iq(StanzaId, JID, StateData)
     when JID#jid.lresource /= <<"">> ->
     {is_user_online(JID, StateData), StanzaId, JID};
@@ -1207,93 +1064,55 @@ is_user_online_iq(StanzaId, JID, StateData)
     when JID#jid.lresource == <<"">> ->
     try stanzaid_unpack(StanzaId) of
       {OriginalId, Resource} ->
-         JIDWithResource = jid:replace_resource(JID,
-                                                     Resource),
+         JIDWithResource = jid:replace_resource(JID, Resource),
          {is_user_online(JIDWithResource, StateData), OriginalId,
           JIDWithResource}
     catch
       _:_ -> {is_user_online(JID, StateData), StanzaId, JID}
     end.
 
-handle_iq_vcard(FromFull, ToJID, StanzaId, NewId,
-               Packet) ->
+-spec handle_iq_vcard(jid(), binary(), iq()) -> {jid(), iq()}.
+handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
     ToBareJID = jid:remove_resource(ToJID),
-    IQ = jlib:iq_query_info(Packet),
-    handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId,
-                    NewId, IQ, Packet).
-
-handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId,
-                _NewId, #iq{type = get, xmlns = ?NS_VCARD}, Packet)
-    when ToBareJID /= ToJID ->
-    {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
-handle_iq_vcard2(_FromFull, ToJID, _ToBareJID,
-                _StanzaId, NewId, _IQ, Packet) ->
-    {ToJID, change_stanzaid(NewId, Packet)}.
+    case SubEls of
+       [SubEl] when Type == get, ToBareJID /= ToJID ->
+           case xmpp:get_ns(SubEl) of
+               ?NS_VCARD ->
+                   {ToBareJID, change_stanzaid(ToJID, IQ)};
+               _ ->
+                   {ToJID, xmpp:set_id(IQ, NewId)}
+           end;
+       _ ->
+           {ToJID, xmpp:set_id(IQ, NewId)}
+    end.
 
+-spec stanzaid_pack(binary(), binary()) -> binary().
 stanzaid_pack(OriginalId, Resource) ->
     <<"berd",
       (jlib:encode_base64(<<"ejab\000",
                       OriginalId/binary, "\000",
                       Resource/binary>>))/binary>>.
 
+-spec stanzaid_unpack(binary()) -> {binary(), binary()}.
 stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
     StanzaId = jlib:decode_base64(StanzaIdBase64),
     [<<"ejab">>, OriginalId, Resource] =
        str:tokens(StanzaId, <<"\000">>),
     {OriginalId, Resource}.
 
-change_stanzaid(NewId, Packet) ->
-    #xmlel{name = Name, attrs = Attrs, children = Els} =
-       jlib:remove_attr(<<"id">>, Packet),
-    #xmlel{name = Name, attrs = [{<<"id">>, NewId} | Attrs],
-          children = Els}.
-
-change_stanzaid(PreviousId, ToJID, Packet) ->
+-spec change_stanzaid(jid(), iq()) -> iq().
+change_stanzaid(ToJID, #iq{id = PreviousId} = Packet) ->
     NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
-    change_stanzaid(NewId, Packet).
-
-%%%
-%%%
-
-role_to_list(Role) ->
-    case Role of
-      moderator -> <<"moderator">>;
-      participant -> <<"participant">>;
-      visitor -> <<"visitor">>;
-      none -> <<"none">>
-    end.
-
-affiliation_to_list(Affiliation) ->
-    case Affiliation of
-      owner -> <<"owner">>;
-      admin -> <<"admin">>;
-      member -> <<"member">>;
-      outcast -> <<"outcast">>;
-      none -> <<"none">>
-    end.
-
-list_to_role(Role) ->
-    case Role of
-      <<"moderator">> -> moderator;
-      <<"participant">> -> participant;
-      <<"visitor">> -> visitor;
-      <<"none">> -> none
-    end.
-
-list_to_affiliation(Affiliation) ->
-    case Affiliation of
-      <<"owner">> -> owner;
-      <<"admin">> -> admin;
-      <<"member">> -> member;
-      <<"outcast">> -> outcast;
-      <<"none">> -> none
-    end.
+    xmpp:set_id(Packet, NewId).
 
 %% Decide the fate of the message and its sender
 %% Returns: continue_delivery | forget_message | {expulse_sender, Reason}
-decide_fate_message(<<"error">>, Packet, From,
-                   StateData) ->
-    PD = case check_error_kick(Packet) of
+-spec decide_fate_message(message(), jid(), state()) ->
+                                continue_delivery | forget_message |
+                                {expulse_sender, binary()}.
+decide_fate_message(#message{type = error, error = Err},
+                   From, StateData) ->
+    PD = case check_error_kick(Err) of
           %% If this is an error stanza and its condition matches a criteria
           true ->
               Reason =
@@ -1311,67 +1130,61 @@ decide_fate_message(<<"error">>, Packet, From,
          end;
       Other -> Other
     end;
-decide_fate_message(_, _, _, _) -> continue_delivery.
+decide_fate_message(_, _, _) -> continue_delivery.
 
 %% Check if the elements of this error stanza indicate
 %% that the sender is a dead participant.
 %% If so, return true to kick the participant.
-check_error_kick(Packet) ->
-    case get_error_condition(Packet) of
-      <<"gone">> -> true;
-      <<"internal-server-error">> -> true;
-      <<"item-not-found">> -> true;
-      <<"jid-malformed">> -> true;
-      <<"recipient-unavailable">> -> true;
-      <<"redirect">> -> true;
-      <<"remote-server-not-found">> -> true;
-      <<"remote-server-timeout">> -> true;
-      <<"service-unavailable">> -> true;
-      _ -> false
-    end.
-
-get_error_condition(Packet) ->
-    case catch get_error_condition2(Packet) of
-      {condition, ErrorCondition} -> ErrorCondition;
-      {'EXIT', _} -> <<"badformed error stanza">>
-    end.
+-spec check_error_kick(error()) -> boolean().
+check_error_kick(#error{reason = Reason}) ->
+    case Reason of
+       #gone{} -> true;
+       'internal-server-error' -> true;
+       'item-not-found' -> true;
+       'jid-malformed' -> true;
+       'recipient-unavailable' -> true;
+       #redirect{} -> true;
+       'remote-server-not-found' -> true;
+       'remote-server-timeout' -> true;
+       'service-unavailable' -> true;
+       _ -> false
+    end;
+check_error_kick(undefined) ->
+    false.
 
-get_error_condition2(Packet) ->
-    #xmlel{children = EEls} = fxml:get_subtag(Packet,
-                                            <<"error">>),
-    [Condition] = [Name
-                  || #xmlel{name = Name,
-                            attrs = [{<<"xmlns">>, ?NS_STANZAS}],
-                            children = []}
-                         <- EEls],
-    {condition, Condition}.
+-spec get_error_condition(error()) -> string().
+get_error_condition(#error{reason = Reason}) ->
+    case Reason of
+       #gone{} -> "gone";
+       #redirect{} -> "redirect";
+       Atom -> atom_to_list(Atom)
+    end;
+get_error_condition(undefined) ->
+    "undefined".
 
+-spec make_reason(stanza(), jid(), state(), binary()) -> binary().
 make_reason(Packet, From, StateData, Reason1) ->
     {ok, #user{nick = FromNick}} = (?DICT):find(jid:tolower(From), StateData#state.users),
-    Condition = get_error_condition(Packet),
+    Condition = get_error_condition(xmpp:get_error(Packet)),
     iolist_to_binary(io_lib:format(Reason1, [FromNick, Condition])).
 
+-spec expulse_participant(stanza(), jid(), state(), binary()) ->
+                                state().
 expulse_participant(Packet, From, StateData, Reason1) ->
     IsSubscriber = is_subscriber(From, StateData),
     Reason2 = make_reason(Packet, From, StateData, Reason1),
     NewState = add_user_presence_un(From,
-                                   #xmlel{name = <<"presence">>,
-                                          attrs =
-                                              [{<<"type">>,
-                                                <<"unavailable">>}],
-                                          children =
-                                              [#xmlel{name = <<"status">>,
-                                                      attrs = [],
-                                                      children =
-                                                          [{xmlcdata,
-                                                            Reason2}]}]},
+                                   #presence{type = unavailable,
+                                             status = xmpp:mk_text(Reason2)},
                                    StateData),
     send_new_presence(From, NewState, StateData),
     remove_online_user(From, NewState, IsSubscriber).
 
+-spec set_affiliation(jid(), affiliation(), state()) -> state().
 set_affiliation(JID, Affiliation, StateData) ->
     set_affiliation(JID, Affiliation, StateData, <<"">>).
 
+-spec set_affiliation(jid(), affiliation(), state(), binary()) -> state().
 set_affiliation(JID, Affiliation, StateData, Reason) ->
     LJID = jid:remove_resource(jid:tolower(JID)),
     Affiliations = case Affiliation of
@@ -1383,6 +1196,7 @@ set_affiliation(JID, Affiliation, StateData, Reason) ->
                   end,
     StateData#state{affiliations = Affiliations}.
 
+-spec get_affiliation(jid(), state()) -> affiliation().
 get_affiliation(JID, StateData) ->
     {_AccessRoute, _AccessCreate, AccessAdmin,
      _AccessPersistent} =
@@ -1423,6 +1237,7 @@ get_affiliation(JID, StateData) ->
       _ -> Res
     end.
 
+-spec get_service_affiliation(jid(), state()) -> owner | none.
 get_service_affiliation(JID, StateData) ->
     {_AccessRoute, _AccessCreate, AccessAdmin,
      _AccessPersistent} =
@@ -1434,6 +1249,7 @@ get_service_affiliation(JID, StateData) ->
       _ -> none
     end.
 
+-spec set_role(jid(), role(), state()) -> state().
 set_role(JID, Role, StateData) ->
     LJID = jid:tolower(JID),
     LJIDs = case LJID of
@@ -1482,6 +1298,7 @@ set_role(JID, Role, StateData) ->
                     end,
     StateData#state{users = Users, nicks = Nicks}.
 
+-spec get_role(jid(), state()) -> role().
 get_role(JID, StateData) ->
     LJID = jid:tolower(JID),
     case (?DICT):find(LJID, StateData#state.users) of
@@ -1489,6 +1306,7 @@ get_role(JID, StateData) ->
       _ -> none
     end.
 
+-spec get_default_role(affiliation(), state()) -> role().
 get_default_role(Affiliation, StateData) ->
     case Affiliation of
       owner -> moderator;
@@ -1507,12 +1325,15 @@ get_default_role(Affiliation, StateData) ->
          end
     end.
 
+-spec is_visitor(jid(), state()) -> boolean().
 is_visitor(Jid, StateData) ->
     get_role(Jid, StateData) =:= visitor.
 
+-spec is_moderator(jid(), state()) -> boolean().
 is_moderator(Jid, StateData) ->
     get_role(Jid, StateData) =:= moderator.
 
+-spec get_max_users(state()) -> non_neg_integer().
 get_max_users(StateData) ->
     MaxUsers = (StateData#state.config)#config.max_users,
     ServiceMaxUsers = get_service_max_users(StateData),
@@ -1520,18 +1341,21 @@ get_max_users(StateData) ->
        true -> ServiceMaxUsers
     end.
 
+-spec get_service_max_users(state()) -> pos_integer().
 get_service_max_users(StateData) ->
     gen_mod:get_module_opt(StateData#state.server_host,
                           mod_muc, max_users,
                            fun(I) when is_integer(I), I>0 -> I end,
                            ?MAX_USERS_DEFAULT).
 
+-spec get_max_users_admin_threshold(state()) -> pos_integer().
 get_max_users_admin_threshold(StateData) ->
     gen_mod:get_module_opt(StateData#state.server_host,
                           mod_muc, max_users_admin_threshold,
                            fun(I) when is_integer(I), I>0 -> I end,
                            5).
 
+-spec get_user_activity(jid(), state()) -> #activity{}.
 get_user_activity(JID, StateData) ->
     case treap:lookup(jid:tolower(JID),
                      StateData#state.activity)
@@ -1552,6 +1376,7 @@ get_user_activity(JID, StateData) ->
                    presence_shaper = PresenceShaper}
     end.
 
+-spec store_user_activity(jid(), #activity{}, state()) -> state().
 store_user_activity(JID, UserActivity, StateData) ->
     MinMessageInterval =
        trunc(gen_mod:get_module_opt(StateData#state.server_host,
@@ -1613,6 +1438,7 @@ store_user_activity(JID, UserActivity, StateData) ->
                 end,
     StateData1.
 
+-spec clean_treap(treap:treap(), integer()) -> treap:treap().
 clean_treap(Treap, CleanPriority) ->
     case treap:is_empty(Treap) of
       true -> Treap;
@@ -1624,6 +1450,7 @@ clean_treap(Treap, CleanPriority) ->
          end
     end.
 
+-spec prepare_room_queue(state()) -> state().
 prepare_room_queue(StateData) ->
     case queue:out(StateData#state.room_queue) of
       {{value, {message, From}}, _RoomQueue} ->
@@ -1647,6 +1474,7 @@ prepare_room_queue(StateData) ->
       {empty, _} -> StateData
     end.
 
+-spec update_online_user(jid(), #user{}, state()) -> state().
 update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
                              is_subscriber = IsSubscriber} = User, StateData) ->
     LJID = jid:tolower(JID),
@@ -1681,6 +1509,7 @@ update_online_user(JID, #user{nick = Nick, subscriptions = Nodes,
     end,
     NewStateData.
 
+-spec add_online_user(jid(), binary(), role(), boolean(), [binary()], state()) -> state().
 add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
     tab_add_online_user(JID, StateData),
     User = #user{jid = JID, nick = Nick, role = Role,
@@ -1693,9 +1522,11 @@ add_online_user(JID, Nick, Role, IsSubscriber, Nodes, StateData) ->
     end,
     StateData1.
 
+-spec remove_online_user(jid(), state(), boolean()) -> state().
 remove_online_user(JID, StateData, IsSubscriber) ->
     remove_online_user(JID, StateData, IsSubscriber, <<"">>).
 
+-spec remove_online_user(jid(), state(), boolean(), binary()) -> state().
 remove_online_user(JID, StateData, _IsSubscriber = true, _Reason) ->
     LJID = jid:tolower(JID),
     Users = case (?DICT):find(LJID, StateData#state.users) of
@@ -1723,38 +1554,23 @@ remove_online_user(JID, StateData, _IsSubscriber, Reason) ->
            end,
     StateData#state{users = Users, nicks = Nicks}.
 
-filter_presence(#xmlel{name = <<"presence">>,
-                      attrs = Attrs, children = Els}) ->
-    FEls = lists:filter(fun (El) ->
-                               case El of
-                                 {xmlcdata, _} -> false;
-                                 #xmlel{attrs = Attrs1} ->
-                                        XMLNS = fxml:get_attr_s(<<"xmlns">>,
-                                                               Attrs1),
-                                        NS_MUC = ?NS_MUC,
-                                        Size = byte_size(NS_MUC),
-                                        case XMLNS of
-                                            <<NS_MUC:Size/binary, _/binary>> ->
-                                                false;
-                                            _ ->
-                                                true
-                                        end
-                               end
-                       end,
-                       Els),
-    #xmlel{name = <<"presence">>, attrs = Attrs,
-          children = FEls}.
-
-strip_status(#xmlel{name = <<"presence">>,
-                   attrs = Attrs, children = Els}) ->
-    FEls = lists:filter(fun (#xmlel{name = <<"status">>}) ->
-                               false;
-                           (_) -> true
-                       end,
-                       Els),
-    #xmlel{name = <<"presence">>, attrs = Attrs,
-          children = FEls}.
-
+-spec filter_presence(presence()) -> presence().
+filter_presence(Presence) ->
+    Els = lists:filter(
+           fun(El) ->
+                   XMLNS = xmpp:get_ns(El),
+                   case catch binary:part(XMLNS, 0, size(?NS_MUC)) of
+                       ?NS_MUC -> false;
+                       _ -> true
+                   end
+           end, xmpp:get_els(Presence)),
+    xmpp:set_els(Presence, Els).
+
+-spec strip_status(presence()) -> presence().
+strip_status(Presence) ->
+    Presence#presence{status = []}.
+
+-spec add_user_presence(jid(), presence(), state()) -> state().
 add_user_presence(JID, Presence, StateData) ->
     LJID = jid:tolower(JID),
     FPresence = filter_presence(Presence),
@@ -1765,6 +1581,7 @@ add_user_presence(JID, Presence, StateData) ->
                           StateData#state.users),
     StateData#state{users = Users}.
 
+-spec add_user_presence_un(jid(), presence(), state()) -> state().
 add_user_presence_un(JID, Presence, StateData) ->
     LJID = jid:tolower(JID),
     FPresence = filter_presence(Presence),
@@ -1778,15 +1595,17 @@ add_user_presence_un(JID, Presence, StateData) ->
 
 %% Find and return a list of the full JIDs of the users of Nick.
 %% Return jid record.
+-spec find_jids_by_nick(binary(), state()) -> [jid()].
 find_jids_by_nick(Nick, StateData) ->
     case (?DICT):find(Nick, StateData#state.nicks) of
       {ok, [User]} -> [jid:make(User)];
       {ok, Users} -> [jid:make(LJID) || LJID <- Users];
-      error -> false
+      error -> []
     end.
 
 %% Find and return the full JID of the user of Nick with
 %% highest-priority presence.  Return jid record.
+-spec find_jid_by_nick(binary(), state()) -> jid() | false.
 find_jid_by_nick(Nick, StateData) ->
     case (?DICT):find(Nick, StateData#state.nicks) of
       {ok, [User]} -> jid:make(User);
@@ -1811,6 +1630,8 @@ find_jid_by_nick(Nick, StateData) ->
       error -> false
     end.
 
+-spec higher_presence(undefined | presence(),
+                     undefined | presence()) -> boolean().
 higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
     Pri1 = get_priority_from_presence(Pres1),
     Pri2 = get_priority_from_presence(Pres2),
@@ -1818,26 +1639,20 @@ higher_presence(Pres1, Pres2) when Pres1 /= undefined, Pres2 /= undefined ->
 higher_presence(Pres1, Pres2) ->
     Pres1 > Pres2.
 
-get_priority_from_presence(PresencePacket) ->
-    case fxml:get_subtag(PresencePacket, <<"priority">>) of
-      false -> 0;
-      SubEl ->
-         case catch
-                jlib:binary_to_integer(fxml:get_tag_cdata(SubEl))
-             of
-           P when is_integer(P) -> P;
-           _ -> 0
-         end
+-spec get_priority_from_presence(presence()) -> integer().
+get_priority_from_presence(#presence{priority = Prio}) ->
+    case Prio of
+        undefined -> 0;
+        _ -> Prio
     end.
 
-find_nick_by_jid(Jid, StateData) ->
-    [{_, #user{nick = Nick}}] = lists:filter(fun ({_,
-                                                  #user{jid = FJid}}) ->
-                                                    FJid == Jid
-                                            end,
-                                            (?DICT):to_list(StateData#state.users)),
+-spec find_nick_by_jid(jid(), state()) -> binary().
+find_nick_by_jid(JID, StateData) ->
+    LJID = jid:tolower(JID),
+    {ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
     Nick.
 
+-spec is_nick_change(jid(), binary(), state()) -> boolean().
 is_nick_change(JID, Nick, StateData) ->
     LJID = jid:tolower(JID),
     case Nick of
@@ -1848,16 +1663,20 @@ is_nick_change(JID, Nick, StateData) ->
          Nick /= OldNick
     end.
 
+-spec nick_collision(jid(), binary(), state()) -> boolean().
 nick_collision(User, Nick, StateData) ->
     UserOfNick = find_jid_by_nick(Nick, StateData),
     (UserOfNick /= false andalso
       jid:remove_resource(jid:tolower(UserOfNick))
        /= jid:remove_resource(jid:tolower(User))).
 
-add_new_user(From, Nick,
-            #xmlel{name = Name, attrs = Attrs, children = Els} = Packet,
-            StateData) ->
-    Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
+-spec add_new_user(jid(), binary(), presence() | iq(), state()) ->
+                         state() |
+                         {error, error()} |
+                         {ignore, state()} |
+                         {result, xmpp_element(), state()}.
+add_new_user(From, Nick, Packet, StateData) ->
+    Lang = xmpp:get_lang(Packet),
     UserRoomJID = jid:replace_resource(StateData#state.jid, Nick),
     MaxUsers = get_max_users(StateData),
     MaxAdminUsers = MaxUsers +
@@ -1874,7 +1693,7 @@ add_new_user(From, Nick,
                                fun(I) when is_integer(I), I>0 -> I end,
                                10),
     Collision = nick_collision(From, Nick, StateData),
-    IsSubscribeRequest = Name /= <<"presence">>,
+    IsSubscribeRequest = not is_record(Packet, presence),
     case {(ServiceAffiliation == owner orelse
             ((Affiliation == admin orelse Affiliation == owner)
               andalso NUsers < MaxAdminUsers)
@@ -1887,72 +1706,72 @@ add_new_user(From, Nick,
        of
       {false, _, _, _} when NUsers >= MaxUsers orelse NUsers >= MaxAdminUsers ->
          Txt = <<"Too many users in this conference">>,
-         Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         Err = xmpp:err_resource_constraint(Txt, Lang),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {false, _, _, _} when NConferences >= MaxConferences ->
          Txt = <<"You have joined too many conferences">>,
-         Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, Txt),
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         Err = xmpp:err_resource_constraint(Txt, Lang),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {false, _, _, _} ->
-         Err = ?ERR_SERVICE_UNAVAILABLE,
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         Err = xmpp:err_service_unavailable(),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {_, _, _, none} ->
          Err = case Affiliation of
                    outcast ->
                        ErrText = <<"You have been banned from this room">>,
-                       ?ERRT_FORBIDDEN(Lang, ErrText);
+                       xmpp:err_forbidden(ErrText, Lang);
                    _ ->
                        ErrText = <<"Membership is required to enter this room">>,
-                       ?ERRT_REGISTRATION_REQUIRED(Lang, ErrText)
+                       xmpp:err_registration_required(ErrText, Lang)
                end,
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {_, true, _, _} ->
          ErrText = <<"That nickname is already in use by another occupant">>,
-         Err = ?ERRT_CONFLICT(Lang, ErrText),
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         Err = xmpp:err_conflict(ErrText, Lang),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {_, _, false, _} ->
          ErrText = <<"That nickname is registered by another person">>,
-         Err = ?ERRT_CONFLICT(Lang, ErrText),
-         ErrPacket = jlib:make_error_reply(Packet, Err),
+         Err = xmpp:err_conflict(ErrText, Lang),
+         ErrPacket = xmpp:make_error(Packet, Err),
          if not IsSubscribeRequest ->
                  ejabberd_router:route(UserRoomJID, From, ErrPacket),
                  StateData;
             true ->
-                 {error, Err, StateData}
+                 {error, Err}
          end;
       {_, _, _, Role} ->
          case check_password(ServiceAffiliation, Affiliation,
-                             Els, From, StateData)
+                             Packet, From, StateData)
              of
            true ->
                Nodes = get_subscription_nodes(Packet),
@@ -1965,7 +1784,7 @@ add_new_user(From, Nick,
                                                           Nodes, StateData)),
                              send_existing_presences(From, NewState),
                              send_initial_presence(From, NewState, StateData),
-                             Shift = count_stanza_shift(Nick, Els, NewState),
+                             Shift = count_stanza_shift(Nick, Packet, NewState),
                              case send_history(From, Shift, NewState) of
                                  true -> ok;
                                  _ -> send_subject(From, StateData)
@@ -1985,20 +1804,20 @@ add_new_user(From, Nick,
                              NewStateData#state{robots = Robots}
                      end,
                  if not IsSubscribeRequest -> ResultState;
-                    true -> {result, subscription_nodes_to_events(Nodes), ResultState}
+                    true -> {result, subscribe_result(Packet), ResultState}
                  end;
            nopass ->
                ErrText = <<"A password is required to enter this room">>,
-               Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
-               ErrPacket = jlib:make_error_reply(Packet, Err),
+               Err = xmpp:err_not_authorized(ErrText, Lang),
+               ErrPacket = xmpp:make_error(Packet, Err),
                if not IsSubscribeRequest ->
                        ejabberd_router:route(UserRoomJID, From, ErrPacket),
                        StateData;
                   true ->
-                       {error, Err, StateData}
+                       {error, Err}
                end;
            captcha_required ->
-               SID = fxml:get_attr_s(<<"id">>, Attrs),
+               SID = xmpp:get_id(Packet),
                RoomJID = StateData#state.jid,
                To = jid:replace_resource(RoomJID, Nick),
                Limiter = {From#jid.luser, From#jid.lserver},
@@ -2006,9 +1825,7 @@ add_new_user(From, Nick,
                                                     Lang, Limiter, From)
                    of
                  {ok, ID, CaptchaEls} ->
-                     MsgPkt = #xmlel{name = <<"message">>,
-                                     attrs = [{<<"id">>, ID}],
-                                     children = CaptchaEls},
+                     MsgPkt = #message{id = ID, sub_els = CaptchaEls},
                      Robots = (?DICT):store(From, {Nick, Packet},
                                             StateData#state.robots),
                      ejabberd_router:route(RoomJID, From, MsgPkt),
@@ -2020,49 +1837,51 @@ add_new_user(From, Nick,
                      end;
                  {error, limit} ->
                      ErrText = <<"Too many CAPTCHA requests">>,
-                     Err = ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText),
-                     ErrPacket = jlib:make_error_reply(Packet, Err),
+                     Err = xmpp:err_resource_constraint(ErrText, Lang),
+                     ErrPacket = xmpp:make_error(Packet, Err),
                      if not IsSubscribeRequest ->
                              ejabberd_router:route(UserRoomJID, From, ErrPacket),
                              StateData;
                         true ->
-                             {error, Err, StateData}
+                             {error, Err}
                      end;
                  _ ->
                      ErrText = <<"Unable to generate a CAPTCHA">>,
-                     Err = ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText),
-                     ErrPacket = jlib:make_error_reply(Packet, Err),
+                     Err = xmpp:err_internal_server_error(ErrText, Lang),
+                     ErrPacket = xmpp:make_error(Packet, Err),
                      if not IsSubscribeRequest ->
                              ejabberd_router:route(UserRoomJID, From, ErrPacket),
                              StateData;
                         true ->
-                             {error, Err, StateData}
+                             {error, Err}
                      end
                end;
            _ ->
                ErrText = <<"Incorrect password">>,
-               Err = ?ERRT_NOT_AUTHORIZED(Lang, ErrText),
-               ErrPacket = jlib:make_error_reply(Packet, Err),
+               Err = xmpp:err_not_authorized(ErrText, Lang),
+               ErrPacket = xmpp:make_error(Packet, Err),
                if not IsSubscribeRequest ->
                        ejabberd_router:route(UserRoomJID, From, ErrPacket),
                        StateData;
                   true ->
-                       {error, Err, StateData}
+                       {error, Err}
                end
          end
     end.
 
-check_password(owner, _Affiliation, _Els, _From,
+-spec check_password(affiliation(), affiliation(),
+                    stanza(), jid(), state()) -> boolean() | nopass.
+check_password(owner, _Affiliation, _Packet, _From,
               _StateData) ->
     %% Don't check pass if user is owner in MUC service (access_admin option)
     true;
-check_password(_ServiceAffiliation, Affiliation, Els,
+check_password(_ServiceAffiliation, Affiliation, Packet,
               From, StateData) ->
     case (StateData#state.config)#config.password_protected
        of
       false -> check_captcha(Affiliation, From, StateData);
       true ->
-         Pass = extract_password(Els),
+         Pass = extract_password(Packet),
          case Pass of
            false -> nopass;
            _ ->
@@ -2073,6 +1892,7 @@ check_password(_ServiceAffiliation, Affiliation, Els,
          end
     end.
 
+-spec check_captcha(affiliation(), jid(), state()) -> true | captcha_required.
 check_captcha(Affiliation, From, StateData) ->
     case (StateData#state.config)#config.captcha_protected
           andalso ejabberd_captcha:is_feature_available()
@@ -2101,47 +1921,52 @@ check_captcha(Affiliation, From, StateData) ->
       _ -> true
     end.
 
-extract_password([]) -> false;
-extract_password([#xmlel{attrs = Attrs} = El | Els]) ->
-    case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-      ?NS_MUC ->
-         case fxml:get_subtag(El, <<"password">>) of
-           false -> false;
-           SubEl -> fxml:get_tag_cdata(SubEl)
-         end;
-      _ -> extract_password(Els)
-    end;
-extract_password([_ | Els]) -> extract_password(Els).
+-spec extract_password(stanza()) -> binary() | false.
+extract_password(Packet) ->
+    case xmpp:get_subtag(Packet, #muc{}) of
+       #muc{password = Password} when is_binary(Password) ->
+           Password;
+       _ ->
+           false
+    end.
 
-count_stanza_shift(Nick, Els, StateData) ->
-    HL = lqueue_to_list(StateData#state.history),
-    Since = extract_history(Els, <<"since">>),
-    Shift0 = case Since of
-              false -> 0;
-              _ ->
-                  Sin = calendar:datetime_to_gregorian_seconds(Since),
-                  count_seconds_shift(Sin, HL)
-            end,
-    Seconds = extract_history(Els, <<"seconds">>),
-    Shift1 = case Seconds of
-              false -> 0;
-              _ ->
-                  Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
-                         - Seconds,
-                  count_seconds_shift(Sec, HL)
-            end,
-    MaxStanzas = extract_history(Els, <<"maxstanzas">>),
-    Shift2 = case MaxStanzas of
-              false -> 0;
-              _ -> count_maxstanzas_shift(MaxStanzas, HL)
-            end,
-    MaxChars = extract_history(Els, <<"maxchars">>),
-    Shift3 = case MaxChars of
-              false -> 0;
-              _ -> count_maxchars_shift(Nick, MaxChars, HL)
-            end,
-    lists:max([Shift0, Shift1, Shift2, Shift3]).
+-spec count_stanza_shift(binary(), stanza(), state()) -> non_neg_integer().
+count_stanza_shift(Nick, Packet, StateData) ->
+    case xmpp:get_subtag(Packet, #muc_history{}) of
+       #muc_history{since = Since,
+                    seconds = Seconds,
+                    maxstanzas = MaxStanzas,
+                    maxchars = MaxChars} ->
+           HL = lqueue_to_list(StateData#state.history),
+           Shift0 = case Since of
+                        undefined -> 0;
+                        _ ->
+                            Sin = calendar:datetime_to_gregorian_seconds(
+                                    calendar:now_to_datetime(Since)),
+                            count_seconds_shift(Sin, HL)
+                    end,
+           Shift1 = case Seconds of
+                        undefined -> 0;
+                        _ ->
+                            Sec = calendar:datetime_to_gregorian_seconds(
+                                    calendar:universal_time()) - Seconds,
+                            count_seconds_shift(Sec, HL)
+                    end,
+           Shift2 = case MaxStanzas of
+                        undefined -> 0;
+                        _ -> count_maxstanzas_shift(MaxStanzas, HL)
+                    end,
+           Shift3 = case MaxChars of
+                        undefined -> 0;
+                        _ -> count_maxchars_shift(Nick, MaxChars, HL)
+                    end,
+           lists:max([Shift0, Shift1, Shift2, Shift3]);
+       false ->
+           0
+    end.
 
+-spec count_seconds_shift(non_neg_integer(),
+                         [history_element()]) -> non_neg_integer().
 count_seconds_shift(Seconds, HistoryList) ->
     lists:sum(lists:map(fun ({_Nick, _Packet, _HaveSubject,
                              TimeStamp, _Size}) ->
@@ -2153,12 +1978,16 @@ count_seconds_shift(Seconds, HistoryList) ->
                        end,
                        HistoryList)).
 
+-spec count_maxstanzas_shift(non_neg_integer(),
+                            [history_element()]) -> non_neg_integer().
 count_maxstanzas_shift(MaxStanzas, HistoryList) ->
     S = length(HistoryList) - MaxStanzas,
     if S =< 0 -> 0;
        true -> S
     end.
 
+-spec count_maxchars_shift(binary(), non_neg_integer(),
+                          [history_element()]) -> integer().
 count_maxchars_shift(Nick, MaxSize, HistoryList) ->
     NLen = byte_size(Nick) + 1,
     Sizes = lists:map(fun ({_Nick, _Packet, _HaveSubject,
@@ -2168,41 +1997,20 @@ count_maxchars_shift(Nick, MaxSize, HistoryList) ->
                      HistoryList),
     calc_shift(MaxSize, Sizes).
 
+-spec calc_shift(non_neg_integer(), [non_neg_integer()]) -> integer().
 calc_shift(MaxSize, Sizes) ->
     Total = lists:sum(Sizes),
     calc_shift(MaxSize, Total, 0, Sizes).
 
+-spec calc_shift(non_neg_integer(), integer(), integer(),
+                [non_neg_integer()]) -> integer().
 calc_shift(_MaxSize, _Size, Shift, []) -> Shift;
 calc_shift(MaxSize, Size, Shift, [S | TSizes]) ->
     if MaxSize >= Size -> Shift;
        true -> calc_shift(MaxSize, Size - S, Shift + 1, TSizes)
     end.
 
-extract_history([], _Type) -> false;
-extract_history([#xmlel{attrs = Attrs} = El | Els],
-               Type) ->
-    case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-      ?NS_MUC ->
-         AttrVal = fxml:get_path_s(El,
-                                  [{elem, <<"history">>}, {attr, Type}]),
-         case Type of
-           <<"since">> ->
-               case jlib:datetime_string_to_timestamp(AttrVal) of
-                 undefined -> false;
-                 TS -> calendar:now_to_universal_time(TS)
-               end;
-           _ ->
-               case catch jlib:binary_to_integer(AttrVal) of
-                 IntVal when is_integer(IntVal) and (IntVal >= 0) ->
-                     IntVal;
-                 _ -> false
-               end
-         end;
-      _ -> extract_history(Els, Type)
-    end;
-extract_history([_ | Els], Type) ->
-    extract_history(Els, Type).
-
+-spec is_room_overcrowded(state()) -> boolean().
 is_room_overcrowded(StateData) ->
     MaxUsersPresence = gen_mod:get_module_opt(StateData#state.server_host,
        mod_muc, max_users_presence,
@@ -2210,10 +2018,12 @@ is_room_overcrowded(StateData) ->
        ?DEFAULT_MAX_USERS_PRESENCE),
     (?DICT):size(StateData#state.users) > MaxUsersPresence.
 
+-spec presence_broadcast_allowed(jid(), state()) -> boolean().
 presence_broadcast_allowed(JID, StateData) ->
     Role = get_role(JID, StateData),
     lists:member(Role, (StateData#state.config)#config.presence_broadcast).
 
+-spec is_initial_presence(jid(), state()) -> boolean().
 is_initial_presence(From, StateData) ->
     LJID = jid:tolower(From),
     case (?DICT):find(LJID, StateData#state.users) of
@@ -2223,18 +2033,22 @@ is_initial_presence(From, StateData) ->
            true
     end.
 
+-spec send_initial_presence(jid(), state(), state()) -> ok.
 send_initial_presence(NJID, StateData, OldStateData) ->
     send_new_presence1(NJID, <<"">>, true, StateData, OldStateData).
 
+-spec send_update_presence(jid(), state(), state()) -> ok.
 send_update_presence(JID, StateData, OldStateData) ->
     send_update_presence(JID, <<"">>, StateData, OldStateData).
 
+-spec send_update_presence(jid(), binary(), state(), state()) -> ok.
 send_update_presence(JID, Reason, StateData, OldStateData) ->
     case is_room_overcrowded(StateData) of
        true -> ok;
        false -> send_update_presence1(JID, Reason, StateData, OldStateData)
     end.
 
+-spec send_update_presence1(jid(), binary(), state(), state()) -> ok.
 send_update_presence1(JID, Reason, StateData, OldStateData) ->
     LJID = jid:tolower(JID),
     LJIDs = case LJID of
@@ -2258,12 +2072,15 @@ send_update_presence1(JID, Reason, StateData, OldStateData) ->
                  end,
                  LJIDs).
 
+-spec send_new_presence(jid(), state(), state()) -> ok.
 send_new_presence(NJID, StateData, OldStateData) ->
     send_new_presence(NJID, <<"">>, false, StateData, OldStateData).
 
+-spec send_new_presence(jid(), binary(), state(), state()) -> ok.
 send_new_presence(NJID, Reason, StateData, OldStateData) ->
     send_new_presence(NJID, Reason, false, StateData, OldStateData).
 
+-spec send_new_presence(jid(), binary(), boolean(), state(), state()) -> ok.
 send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
     case is_room_overcrowded(StateData) of
        true -> ok;
@@ -2271,6 +2088,7 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
                                    OldStateData)
     end.
 
+-spec is_ra_changed(jid() | ljid(), boolean(), state(), state()) -> boolean().
 is_ra_changed(_, _IsInitialPresence = true, _, _) ->
     false;
 is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
@@ -2289,6 +2107,7 @@ is_ra_changed(LJID, _IsInitialPresence = false, NewStateData, OldStateData) ->
            (NewRole /= OldRole) or (NewAff /= OldAff)
     end.
 
+-spec send_new_presence1(jid(), binary(), boolean(), state(), state()) -> ok.
 send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
     LNJID = jid:tolower(NJID),
     #user{nick = Nick} = (?DICT):fetch(LNJID, StateData#state.users),
@@ -2301,15 +2120,9 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
     {Role1, Presence1} =
         case presence_broadcast_allowed(NJID, StateData) of
             true -> {Role0, Presence0};
-            false ->
-                {none,
-                 #xmlel{name = <<"presence">>,
-                        attrs = [{<<"type">>, <<"unavailable">>}],
-                        children = []}
-                }
+            false -> {none, #presence{type = unavailable}}
         end,
     Affiliation = get_affiliation(LJID, StateData),
-    SAffiliation = affiliation_to_list(Affiliation),
     UserList =
         case not (presence_broadcast_allowed(NJID, StateData) orelse
              presence_broadcast_allowed(NJID, OldStateData)) of
@@ -2323,59 +2136,38 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
              {Role, Presence} = if LNJID == LUJID -> {Role0, Presence0};
                                    true -> {Role1, Presence1}
                                 end,
-             SRole = role_to_list(Role),
-             ItemAttrs = case Info#user.role == moderator orelse
-                             (StateData#state.config)#config.anonymous
-                             == false
-                         of
-                             true ->
-                                 [{<<"jid">>,
-                                   jid:to_string(RealJID)},
-                                  {<<"affiliation">>, SAffiliation},
-                                  {<<"role">>, SRole}];
-                             _ ->
-                                 [{<<"affiliation">>, SAffiliation},
-                                  {<<"role">>, SRole}]
-                         end,
-             ItemEls = case Reason of
-                           <<"">> -> [];
-                           _ ->
-                               [#xmlel{name = <<"reason">>,
-                                       attrs = [],
-                                       children =
-                                           [{xmlcdata, Reason}]}]
-                       end,
-             StatusEls = status_els(IsInitialPresence, NJID, Info,
-                                    StateData),
-             Pres = if Presence == undefined -> #xmlel{name = <<"presence">>};
+             Item0 = #muc_item{affiliation = Affiliation,
+                               role = Role},
+             Item1 = case Info#user.role == moderator orelse
+                         (StateData#state.config)#config.anonymous
+                         == false of
+                         true -> Item0#muc_item{jid = RealJID};
+                         false -> Item0
+                     end,
+             Item = if is_binary(Reason), Reason /= <<"">> ->
+                            Item1#muc_item{reason = Reason};
+                       true ->
+                            Item1
+                    end,
+             StatusCodes = status_codes(IsInitialPresence, NJID, Info,
+                                        StateData),
+             Pres = if Presence == undefined -> #presence{};
                        true -> Presence
                     end,
-             Packet = fxml:append_subtags(Pres,
-                                          [#xmlel{name = <<"x">>,
-                                                  attrs =
-                                                      [{<<"xmlns">>,
-                                                        ?NS_MUC_USER}],
-                                                  children =
-                                                      [#xmlel{name =
-                                                                  <<"item">>,
-                                                              attrs
-                                                              =
-                                                                  ItemAttrs,
-                                                              children
-                                                              =
-                                                                  ItemEls}
-                                                       | StatusEls]}]),
+             Packet = xmpp:set_subtag(
+                        Pres, #muc_user{items = [Item],
+                                        status_codes = StatusCodes}),
              Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
                          true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
                          false -> ?NS_MUCSUB_NODES_PRESENCE
                      end,
              send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
                           Info#user.jid, Packet, Node1, StateData),
-             Type = fxml:get_tag_attr_s(<<"type">>, Packet),
+             Type = xmpp:get_type(Packet),
              IsSubscriber = Info#user.is_subscriber,
              IsOccupant = Info#user.last_presence /= undefined,
              if (IsSubscriber and not IsOccupant) and
-                (IsInitialPresence or (Type == <<"unavailable">>)) ->
+                (IsInitialPresence or (Type == unavailable)) ->
                      Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
                      send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
                                   Info#user.jid, Packet, Node2, StateData);
@@ -2385,12 +2177,14 @@ send_new_presence1(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
       end,
       UserList).
 
+-spec send_existing_presences(jid(), state()) -> ok.
 send_existing_presences(ToJID, StateData) ->
     case is_room_overcrowded(StateData) of
        true -> ok;
        false -> send_existing_presences1(ToJID, StateData)
     end.
 
+-spec send_existing_presences1(jid(), state()) -> ok.
 send_existing_presences1(ToJID, StateData) ->
     LToJID = jid:tolower(ToJID),
     {ok, #user{jid = RealToJID, role = Role}} =
@@ -2410,46 +2204,23 @@ send_existing_presences1(ToJID, StateData) ->
                  {_, false} -> ok;
                  _ ->
                      FromAffiliation = get_affiliation(LJID, StateData),
-                     ItemAttrs = case Role == moderator orelse
-                                     (StateData#state.config)#config.anonymous
-                                     == false
-                                 of
-                                     true ->
-                                         [{<<"jid">>,
-                                           jid:to_string(FromJID)},
-                                          {<<"affiliation">>,
-                                           affiliation_to_list(FromAffiliation)},
-                                          {<<"role">>,
-                                           role_to_list(FromRole)}];
-                                     _ ->
-                                         [{<<"affiliation">>,
-                                           affiliation_to_list(FromAffiliation)},
-                                          {<<"role">>,
-                                           role_to_list(FromRole)}]
-                                 end,
-                     Packet = fxml:append_subtags(
-                                Presence,
-                                [#xmlel{name =
-                                            <<"x">>,
-                                        attrs =
-                                            [{<<"xmlns">>,
-                                              ?NS_MUC_USER}],
-                                        children =
-                                            [#xmlel{name
-                                                    =
-                                                        <<"item">>,
-                                                    attrs
-                                                    =
-                                                        ItemAttrs,
-                                                    children
-                                                    =
-                                                        []}]}]),
+                     Item0 = #muc_item{affiliation = FromAffiliation,
+                                       role = FromRole},
+                     Item = case Role == moderator orelse
+                                (StateData#state.config)#config.anonymous
+                                == false of
+                                true -> Item0#muc_item{jid = FromJID};
+                                false -> Item0
+                            end,
+                     Packet = xmpp:set_subtag(
+                                Presence, #muc_user{items = [Item]}),
                      send_wrapped(jid:replace_resource(StateData#state.jid, FromNick),
                                   RealToJID, Packet, ?NS_MUCSUB_NODES_PRESENCE, StateData)
              end
       end,
       (?DICT):to_list(StateData#state.nicks)).
 
+-spec set_nick(jid(), binary(), state()) -> state().
 set_nick(JID, Nick, State) ->
     LJID = jid:tolower(JID),
     {ok, #user{nick = OldNick}} = (?DICT):find(LJID, State#state.users),
@@ -2472,6 +2243,7 @@ set_nick(JID, Nick, State) ->
            end,
     State#state{users = Users, nicks = Nicks}.
 
+-spec change_nick(jid(), binary(), state()) -> state().
 change_nick(JID, Nick, StateData) ->
     LJID = jid:tolower(JID),
     {ok, #user{nick = OldNick}} = (?DICT):find(LJID, StateData#state.users),
@@ -2492,6 +2264,7 @@ change_nick(JID, Nick, StateData) ->
     add_to_log(nickchange, {OldNick, Nick}, StateData),
     NewStateData.
 
+-spec send_nick_changing(jid(), binary(), state(), boolean(), boolean()) -> ok.
 send_nick_changing(JID, OldNick, StateData,
                   SendOldUnavailable, SendNewAvailable) ->
     {ok,
@@ -2500,104 +2273,52 @@ send_nick_changing(JID, OldNick, StateData,
        (?DICT):find(jid:tolower(JID),
                     StateData#state.users),
     Affiliation = get_affiliation(JID, StateData),
-    SAffiliation = affiliation_to_list(Affiliation),
-    SRole = role_to_list(Role),
-    lists:foreach(fun ({_LJID, Info}) when Presence /= undefined ->
-                         ItemAttrs1 = case Info#user.role == moderator orelse
-                                             (StateData#state.config)#config.anonymous
-                                               == false
-                                          of
-                                        true ->
-                                            [{<<"jid">>,
-                                              jid:to_string(RealJID)},
-                                             {<<"affiliation">>, SAffiliation},
-                                             {<<"role">>, SRole},
-                                             {<<"nick">>, Nick}];
-                                        _ ->
-                                            [{<<"affiliation">>, SAffiliation},
-                                             {<<"role">>, SRole},
-                                             {<<"nick">>, Nick}]
-                                      end,
-                         ItemAttrs2 = case Info#user.role == moderator orelse
-                                             (StateData#state.config)#config.anonymous
-                                               == false
-                                          of
-                                        true ->
-                                            [{<<"jid">>,
-                                              jid:to_string(RealJID)},
-                                             {<<"affiliation">>, SAffiliation},
-                                             {<<"role">>, SRole}];
-                                        _ ->
-                                            [{<<"affiliation">>, SAffiliation},
-                                             {<<"role">>, SRole}]
-                                      end,
-                         Status110 = case JID == Info#user.jid of
-                                       true ->
-                                           [#xmlel{name = <<"status">>,
-                                                   attrs = [{<<"code">>, <<"110">>}]
-                                                                      }];
-                                       false ->
-                                           []
-                                   end,
-                         Packet1 = #xmlel{name = <<"presence">>,
-                                          attrs =
-                                              [{<<"type">>,
-                                                <<"unavailable">>}],
-                                          children =
-                                              [#xmlel{name = <<"x">>,
-                                                      attrs =
-                                                          [{<<"xmlns">>,
-                                                            ?NS_MUC_USER}],
-                                                      children =
-                                                          [#xmlel{name =
-                                                                      <<"item">>,
-                                                                  attrs =
-                                                                      ItemAttrs1,
-                                                                  children =
-                                                                      []},
-                                                           #xmlel{name =
-                                                                      <<"status">>,
-                                                                  attrs =
-                                                                      [{<<"code">>,
-                                                                        <<"303">>}],
-                                                                  children =
-                                                                      []}|Status110]}]},
-                         Packet2 = fxml:append_subtags(Presence,
-                                                      [#xmlel{name = <<"x">>,
-                                                              attrs =
-                                                                  [{<<"xmlns">>,
-                                                                    ?NS_MUC_USER}],
-                                                              children =
-                                                                  [#xmlel{name
-                                                                              =
-                                                                              <<"item">>,
-                                                                          attrs
-                                                                              =
-                                                                              ItemAttrs2,
-                                                                          children
-                                                                              =
-                                                                              []}|Status110]}]),
-                         if SendOldUnavailable ->
-                                 send_wrapped(jid:replace_resource(StateData#state.jid,
-                                                                   OldNick),
-                                              Info#user.jid, Packet1,
-                                              ?NS_MUCSUB_NODES_PRESENCE,
-                                              StateData);
-                            true -> ok
+    lists:foreach(
+      fun({_LJID, Info}) when Presence /= undefined ->
+             Item0 = #muc_item{affiliation = Affiliation, role = Role},
+             Item1 = case Info#user.role == moderator orelse
+                         (StateData#state.config)#config.anonymous
+                         == false of
+                         true -> Item0#muc_item{jid = RealJID, nick = Nick};
+                         false -> Item0#muc_item{nick = Nick}
+                     end,
+             Item2 = case Info#user.role == moderator orelse
+                         (StateData#state.config)#config.anonymous
+                         == false of
+                         true -> Item0#muc_item{jid = RealJID};
+                         false -> Item0
+                     end,
+             Status110 = case JID == Info#user.jid of
+                             true -> [110];
+                             false -> []
                          end,
-                         if SendNewAvailable ->
-                                 send_wrapped(jid:replace_resource(StateData#state.jid,
-                                                                   Nick),
-                                              Info#user.jid, Packet2,
-                                              ?NS_MUCSUB_NODES_PRESENCE,
-                                              StateData);
-                            true -> ok
-                         end;
-                     (_) ->
-                         ok
-                 end,
-                 (?DICT):to_list(StateData#state.users)).
+             Packet1 = #presence{type = unavailable,
+                                 sub_els = [#muc_user{
+                                               items = [Item1],
+                                               status_codes = [303|Status110]}]},
+             Packet2 = xmpp:set_subtag(Presence,
+                                       #muc_user{items = [Item2],
+                                                 status_codes = Status110}),
+             if SendOldUnavailable ->
+                     send_wrapped(
+                       jid:replace_resource(StateData#state.jid, OldNick),
+                       Info#user.jid, Packet1, ?NS_MUCSUB_NODES_PRESENCE,
+                       StateData);
+                true -> ok
+             end,
+             if SendNewAvailable ->
+                     send_wrapped(
+                       jid:replace_resource(StateData#state.jid, Nick),
+                       Info#user.jid, Packet2, ?NS_MUCSUB_NODES_PRESENCE,
+                       StateData);
+                true -> ok
+             end;
+        (_) ->
+             ok
+      end,
+      (?DICT):to_list(StateData#state.users)).
 
+-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
 maybe_send_affiliation(JID, Affiliation, StateData) ->
     LJID = jid:tolower(JID),
     IsOccupant = case LJID of
@@ -2617,18 +2338,13 @@ maybe_send_affiliation(JID, Affiliation, StateData) ->
          send_affiliation(LJID, Affiliation, StateData)
     end.
 
+-spec send_affiliation(ljid(), affiliation(), state()) -> ok.
 send_affiliation(LJID, Affiliation, StateData) ->
-    ItemAttrs = [{<<"jid">>, jid:to_string(LJID)},
-                {<<"affiliation">>, affiliation_to_list(Affiliation)},
-                {<<"role">>, <<"none">>}],
-    Message = #xmlel{name = <<"message">>,
-                    attrs = [{<<"id">>, randoms:get_string()}],
-                    children =
-                        [#xmlel{name = <<"x">>,
-                                attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
-                                children =
-                                    [#xmlel{name = <<"item">>,
-                                            attrs = ItemAttrs}]}]},
+    Item = #muc_item{jid = jid:make(LJID),
+                    affiliation = Affiliation,
+                    role = none},
+    Message = #message{id = randoms:get_string(),
+                      sub_els = [#muc_user{items = [Item]}]},
     Recipients = case (StateData#state.config)#config.anonymous of
                   true ->
                       (?DICT):filter(fun(_, #user{role = moderator}) ->
@@ -2643,43 +2359,33 @@ send_affiliation(LJID, Affiliation, StateData) ->
                  StateData#state.server_host,
                  Recipients, Message).
 
-status_els(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
-    Status = case IsInitialPresence of
-              true ->
-                  S1 = case StateData#state.just_created of
-                         true ->
-                             [#xmlel{name = <<"status">>,
-                                     attrs = [{<<"code">>, <<"201">>}],
-                                     children = []}];
-                         false -> []
-                       end,
-                  S2 = case (StateData#state.config)#config.anonymous of
-                         true -> S1;
-                         false ->
-                             [#xmlel{name = <<"status">>,
-                                     attrs = [{<<"code">>, <<"100">>}],
-                                     children = []} | S1]
-                       end,
-                  S3 = case (StateData#state.config)#config.logging of
-                         true ->
-                             [#xmlel{name = <<"status">>,
-                                     attrs = [{<<"code">>, <<"170">>}],
-                                     children = []} | S2];
-                         false -> S2
-                       end,
-                  S3;
-              false -> []
-            end,
-    [#xmlel{name = <<"status">>,
-           attrs =
-               [{<<"code">>,
-                 <<"110">>}],
-           children = []} | Status];
-status_els(_IsInitialPresence, _JID, _Info, _StateData) -> [].
+-spec status_codes(boolean(), jid(), #user{}, state()) -> [pos_integer()].
+status_codes(IsInitialPresence, JID, #user{jid = JID}, StateData) ->
+    S0 = [110],
+    case IsInitialPresence of
+       true ->
+           S1 = case StateData#state.just_created of
+                    true -> [201|S0];
+                    false -> S0
+                end,
+           S2 = case (StateData#state.config)#config.anonymous of
+                    true -> S1;
+                    false -> [100|S1]
+                end,
+           S3 = case (StateData#state.config)#config.logging of
+                    true -> [170|S2];
+                    false -> S2
+                end,
+           S3;
+       false -> S0
+    end;
+status_codes(_IsInitialPresence, _JID, _Info, _StateData) -> [].
 
+-spec lqueue_new(non_neg_integer()) -> lqueue().
 lqueue_new(Max) ->
     #lqueue{queue = queue:new(), len = 0, max = Max}.
 
+-spec lqueue_in(term(), lqueue()) -> lqueue().
 %% If the message queue limit is set to 0, do not store messages.
 lqueue_in(_Item, LQ = #lqueue{max = 0}) -> LQ;
 %% Otherwise, rotate messages in the queue store.
@@ -2692,39 +2398,33 @@ lqueue_in(Item,
        true -> #lqueue{queue = Q2, len = Len + 1, max = Max}
     end.
 
+-spec lqueue_cut(queue:queue(), non_neg_integer()) -> queue:queue().
 lqueue_cut(Q, 0) -> Q;
 lqueue_cut(Q, N) ->
     {_, Q1} = queue:out(Q), lqueue_cut(Q1, N - 1).
 
+-spec lqueue_to_list(lqueue()) -> list().
 lqueue_to_list(#lqueue{queue = Q1}) ->
     queue:to_list(Q1).
 
-
+-spec add_message_to_history(binary(), jid(), message(), state()) -> state().
 add_message_to_history(FromNick, FromJID, Packet, StateData) ->
-    HaveSubject = case fxml:get_subtag(Packet, <<"subject">>)
-                     of
-                   false -> false;
-                   _ -> true
-                 end,
+    HaveSubject = Packet#message.subject /= [],
     TimeStamp = p1_time_compat:timestamp(),
     AddrPacket = case (StateData#state.config)#config.anonymous of
                   true -> Packet;
                   false ->
-                      Address = #xmlel{name = <<"address">>,
-                                       attrs = [{<<"type">>, <<"ofrom">>},
-                                                {<<"jid">>,
-                                                 jid:to_string(FromJID)}],
-                                       children = []},
-                      Addresses = #xmlel{name = <<"addresses">>,
-                                         attrs = [{<<"xmlns">>, ?NS_ADDRESS}],
-                                         children = [Address]},
-                      fxml:append_subtags(Packet, [Addresses])
+                      Addresses = #addresses{
+                                     list = [#address{type = ofrom,
+                                                      jid = FromJID}]},
+                      xmpp:set_subtag(Packet, Addresses)
                 end,
-    TSPacket = jlib:add_delay_info(AddrPacket, StateData#state.jid, TimeStamp),
-    SPacket =
-       jlib:replace_from_to(jid:replace_resource(StateData#state.jid,
-                                                      FromNick),
-                            StateData#state.jid, TSPacket),
+    TSPacket = xmpp_util:add_delay_info(
+                AddrPacket, StateData#state.jid, TimeStamp),
+    SPacket = xmpp:set_from_to(
+               TSPacket,
+               jid:replace_resource(StateData#state.jid, FromNick),
+               StateData#state.jid),
     Size = element_size(SPacket),
     Q1 = lqueue_in({FromNick, TSPacket, HaveSubject,
                    calendar:now_to_universal_time(TimeStamp), Size},
@@ -2732,6 +2432,7 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
     add_to_log(text, {FromNick, Packet}, StateData),
     StateData#state{history = Q1}.
 
+-spec send_history(jid(), integer(), state()) -> boolean().
 send_history(JID, Shift, StateData) ->
     lists:foldl(fun ({Nick, Packet, HaveSubject, _TimeStamp,
                      _Size},
@@ -2745,23 +2446,19 @@ send_history(JID, Shift, StateData) ->
                lists:nthtail(Shift,
                              lqueue_to_list(StateData#state.history))).
 
+-spec send_subject(jid(), state()) -> ok.
 send_subject(_JID, #state{subject_author = <<"">>}) -> ok;
 send_subject(JID, #state{subject_author = Nick} = StateData) ->
     Subject = StateData#state.subject,
-    Packet = #xmlel{name = <<"message">>,
-                   attrs = [{<<"type">>, <<"groupchat">>}],
-                   children =
-                       [#xmlel{name = <<"subject">>, attrs = [],
-                               children = [{xmlcdata, Subject}]}]},
+    Packet = #message{type = groupchat, subject = xmpp:mk_text(Subject)},
     ejabberd_router:route(jid:replace_resource(StateData#state.jid, Nick), JID,
                          Packet).
 
-check_subject(Packet) ->
-    case fxml:get_subtag(Packet, <<"subject">>) of
-      false -> false;
-      SubjEl -> fxml:get_tag_cdata(SubjEl)
-    end.
+-spec check_subject(message()) -> false | binary().
+check_subject(#message{subject = []}) -> false;
+check_subject(#message{subject = Subj}) -> xmpp:get_text(Subj).
 
+-spec can_change_subject(role(), state()) -> boolean().
 can_change_subject(Role, StateData) ->
     case (StateData#state.config)#config.allow_change_subj
        of
@@ -2772,99 +2469,89 @@ can_change_subject(Role, StateData) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Admin stuff
 
-process_iq_admin(From, set, Lang, SubEl, StateData) ->
-    #xmlel{children = Items} = SubEl,
+-spec process_iq_admin(jid(), iq(), #state{}) -> {error, error()} |
+                                                {result, undefined, #state{}} |
+                                                {result, muc_admin()}.
+process_iq_admin(_From, #iq{lang = Lang, sub_els = [#muc_admin{items = []}]},
+                _StateData) ->
+    Txt = <<"No 'item' element found">>,
+    {error, xmpp:err_bad_request(Txt, Lang)};
+process_iq_admin(From, #iq{type = set, lang = Lang,
+                          sub_els = [#muc_admin{items = Items}]},
+                StateData) ->
     process_admin_items_set(From, Items, Lang, StateData);
-process_iq_admin(From, get, Lang, SubEl, StateData) ->
-    case fxml:get_subtag(SubEl, <<"item">>) of
-      false ->
-         Txt = <<"No 'item' element found">>,
-         {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-      Item ->
-         FAffiliation = get_affiliation(From, StateData),
-         FRole = get_role(From, StateData),
-         case fxml:get_tag_attr(<<"role">>, Item) of
-           false ->
-               case fxml:get_tag_attr(<<"affiliation">>, Item) of
-                 false ->
-                     Txt = <<"No 'affiliation' attribute found">>,
-                     {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-                 {value, StrAffiliation} ->
-                     case catch list_to_affiliation(StrAffiliation) of
-                       {'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
-                       SAffiliation ->
-                           if (FAffiliation == owner) or
-                                (FAffiliation == admin) or
-                                ((FAffiliation == member) and not
-                                 (StateData#state.config)#config.anonymous) ->
-                                  Items = items_with_affiliation(SAffiliation,
-                                                                 StateData),
-                                  {result, Items, StateData};
-                              true ->
-                                  ErrText =
-                                      <<"Administrator privileges required">>,
-                                  {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
-                           end
-                     end
-               end;
-           {value, StrRole} ->
-               case catch list_to_role(StrRole) of
-                 {'EXIT', _} ->
-                     Txt = <<"Incorrect value of 'role' attribute">>,
-                     {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-                 SRole ->
-                     if FRole == moderator ->
-                            Items = items_with_role(SRole, StateData),
-                            {result, Items, StateData};
-                        true ->
-                            ErrText = <<"Moderator privileges required">>,
-                            {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
-                     end
-               end
-         end
-    end.
+process_iq_admin(From, #iq{type = get, lang = Lang,
+                          sub_els = [#muc_admin{items = [Item]}]},
+                StateData) ->
+    FAffiliation = get_affiliation(From, StateData),
+    FRole = get_role(From, StateData),
+    case Item of
+       #muc_item{role = undefined, affiliation = undefined} ->
+           Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+           {error, xmpp:err_bad_request(Txt, Lang)};
+       #muc_item{role = undefined, affiliation = Affiliation} ->
+           if (FAffiliation == owner) or
+              (FAffiliation == admin) or
+              ((FAffiliation == member) and
+               not (StateData#state.config)#config.anonymous) ->
+                   Items = items_with_affiliation(Affiliation, StateData),
+                   {result, #muc_admin{items = Items}};
+              true ->
+                   ErrText = <<"Administrator privileges required">>,
+                   {error, xmpp:err_forbidden(ErrText, Lang)}
+           end;
+       #muc_item{role = Role} ->
+           if FRole == moderator ->
+                   Items = items_with_role(Role, StateData),
+                   {result, #muc_admin{items = Items}};
+              true ->
+                   ErrText = <<"Moderator privileges required">>,
+                   {error, xmpp:err_forbidden(ErrText, Lang)}
+           end
+    end;
+process_iq_admin(_From, #iq{type = get, lang = Lang}, _StateData) ->
+    ErrText = <<"Too many <item/> elements">>,
+    {error, xmpp:err_bad_request(ErrText, Lang)}.
 
+-spec items_with_role(role(), state()) -> [muc_item()].
 items_with_role(SRole, StateData) ->
     lists:map(fun ({_, U}) -> user_to_item(U, StateData)
              end,
              search_role(SRole, StateData)).
 
+-spec items_with_affiliation(affiliation(), state()) -> [muc_item()].
 items_with_affiliation(SAffiliation, StateData) ->
-    lists:map(fun ({JID, {Affiliation, Reason}}) ->
-                     #xmlel{name = <<"item">>,
-                            attrs =
-                                [{<<"affiliation">>,
-                                  affiliation_to_list(Affiliation)},
-                                 {<<"jid">>, jid:to_string(JID)}],
-                            children =
-                                [#xmlel{name = <<"reason">>, attrs = [],
-                                        children = [{xmlcdata, Reason}]}]};
-                 ({JID, Affiliation}) ->
-                     #xmlel{name = <<"item">>,
-                            attrs =
-                                [{<<"affiliation">>,
-                                  affiliation_to_list(Affiliation)},
-                                 {<<"jid">>, jid:to_string(JID)}],
-                            children = []}
-             end,
-             search_affiliation(SAffiliation, StateData)).
+    lists:map(
+      fun({JID, {Affiliation, Reason}}) ->
+             #muc_item{affiliation = Affiliation, jid = JID,
+                       reason = if is_binary(Reason), Reason /= <<"">> ->
+                                        Reason;
+                                   true ->
+                                        undefined
+                                end};
+        ({JID, Affiliation}) ->
+             #muc_item{affiliation = Affiliation, jid = JID}
+      end,
+      search_affiliation(SAffiliation, StateData)).
 
+-spec user_to_item(#user{}, state()) -> muc_item().
 user_to_item(#user{role = Role, nick = Nick, jid = JID},
             StateData) ->
     Affiliation = get_affiliation(JID, StateData),
-    #xmlel{name = <<"item">>,
-          attrs =
-              [{<<"role">>, role_to_list(Role)},
-               {<<"affiliation">>, affiliation_to_list(Affiliation)},
-               {<<"nick">>, Nick},
-               {<<"jid">>, jid:to_string(JID)}],
-          children = []}.
+    #muc_item{role = Role,
+             affiliation = Affiliation,
+             nick = Nick,
+             jid = JID}.
 
+-spec search_role(role(), state()) -> [{ljid(), #user{}}].
 search_role(Role, StateData) ->
     lists:filter(fun ({_, #user{role = R}}) -> Role == R
                 end,
                 (?DICT):to_list(StateData#state.users)).
 
+-spec search_affiliation(affiliation(), state()) ->
+                               [{ljid(),
+                                 affiliation() | {affiliation(), binary()}}].
 search_affiliation(Affiliation, StateData) ->
     lists:filter(fun ({_, A}) ->
                         case A of
@@ -2874,11 +2561,14 @@ search_affiliation(Affiliation, StateData) ->
                 end,
                 (?DICT):to_list(StateData#state.affiliations)).
 
+-spec process_admin_items_set(jid(), [muc_item()], binary() | undefined,
+                             #state{}) -> {result, undefined, #state{}} |
+                                          {error, error()}.
 process_admin_items_set(UJID, Items, Lang, StateData) ->
     UAffiliation = get_affiliation(UJID, StateData),
     URole = get_role(UJID, StateData),
-    case find_changed_items(UJID, UAffiliation, URole,
-                           Items, Lang, StateData, [])
+    case catch find_changed_items(UJID, UAffiliation, URole,
+                                 Items, Lang, StateData, [])
        of
       {result, Res} ->
          ?INFO_MSG("Processing MUC admin query from ~s in "
@@ -2888,249 +2578,161 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
          NSD = lists:foldl(process_item_change(UJID),
                            StateData, lists:flatten(Res)),
          store_room(NSD),
-         {result, [], NSD};
-      Err -> Err
+         {result, undefined, NSD};
+      {error, Err} -> {error, Err}
     end.
 
+-spec process_item_change(jid()) -> function().
 process_item_change(UJID) ->
     fun(E, SD) ->
         process_item_change(E, SD, UJID)
     end.
 
-process_item_change(E, SD, UJID) ->
-    case catch case E of
-        {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
-            %% If the provided JID does not have username,
-            %% forget the affiliation completely
-            SD;
-        {JID, role, none, Reason} ->
-            catch
-                send_kickban_presence(UJID, JID,
-                    Reason,
-                    <<"307">>,
-                    SD),
-            set_role(JID, none, SD);
-        {JID, affiliation, none, Reason} ->
-            case (SD#state.config)#config.members_only of
-                true ->
-                    catch
-                        send_kickban_presence(UJID, JID,
-                            Reason,
-                            <<"321">>,
-                            none,
-                            SD),
-                    maybe_send_affiliation(JID, none, SD),
-                    SD1 = set_affiliation(JID, none, SD),
-                    set_role(JID, none, SD1);
-                _ ->
-                    SD1 = set_affiliation(JID, none, SD),
-                    send_update_presence(JID, SD1, SD),
-                    maybe_send_affiliation(JID, none, SD1),
-                    SD1
-            end;
-        {JID, affiliation, outcast, Reason} ->
-            catch
-                send_kickban_presence(UJID, JID,
-                    Reason,
-                    <<"301">>,
-                    outcast,
-                    SD),
-            maybe_send_affiliation(JID, outcast, SD),
-            set_affiliation(JID,
-                outcast,
-                set_role(JID, none, SD),
-                Reason);
-        {JID, affiliation, A, Reason}
-            when (A == admin) or (A == owner) ->
-            SD1 = set_affiliation(JID, A, SD, Reason),
-            SD2 = set_role(JID, moderator, SD1),
-            send_update_presence(JID, Reason, SD2, SD),
-            maybe_send_affiliation(JID, A, SD2),
-            SD2;
-        {JID, affiliation, member, Reason} ->
-            SD1 = set_affiliation(JID, member, SD, Reason),
-            SD2 = set_role(JID, participant, SD1),
-            send_update_presence(JID, Reason, SD2, SD),
-            maybe_send_affiliation(JID, member, SD2),
-            SD2;
-        {JID, role, Role, Reason} ->
-            SD1 = set_role(JID, Role, SD),
-            catch
-                send_new_presence(JID, Reason, SD1, SD),
-            SD1;
-        {JID, affiliation, A, _Reason} ->
-            SD1 = set_affiliation(JID, A, SD),
-            send_update_presence(JID, SD1, SD),
-            maybe_send_affiliation(JID, A, SD1),
-            SD1
-    end
-    of
-        {'EXIT', ErrReason} ->
-            ?ERROR_MSG("MUC ITEMS SET ERR: ~p~n", [ErrReason]),
-            SD;
-        NSD -> NSD
+-type admin_action() :: {jid(), affiliation | role,
+                        affiliation() | role(), binary()}.
+
+-spec process_item_change(admin_action(), state(), jid()) -> state().
+process_item_change(Item, SD, UJID) ->
+    try case Item of
+           {JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
+               %% If the provided JID does not have username,
+               %% forget the affiliation completely
+               SD;
+           {JID, role, none, Reason} ->
+               catch send_kickban_presence(UJID, JID, Reason, 307, SD),
+               set_role(JID, none, SD);
+           {JID, affiliation, none, Reason} ->
+               case (SD#state.config)#config.members_only of
+                   true ->
+                       catch send_kickban_presence(UJID, JID, Reason, 321, none, SD),
+                       maybe_send_affiliation(JID, none, SD),
+                       SD1 = set_affiliation(JID, none, SD),
+                       set_role(JID, none, SD1);
+                   _ ->
+                       SD1 = set_affiliation(JID, none, SD),
+                       send_update_presence(JID, SD1, SD),
+                       maybe_send_affiliation(JID, none, SD1),
+                       SD1
+               end;
+           {JID, affiliation, outcast, Reason} ->
+               catch send_kickban_presence(UJID, JID, Reason, 301, outcast, SD),
+               maybe_send_affiliation(JID, outcast, SD),
+               set_affiliation(JID, outcast, set_role(JID, none, SD), Reason);
+           {JID, affiliation, A, Reason} when (A == admin) or (A == owner) ->
+               SD1 = set_affiliation(JID, A, SD, Reason),
+               SD2 = set_role(JID, moderator, SD1),
+               send_update_presence(JID, Reason, SD2, SD),
+               maybe_send_affiliation(JID, A, SD2),
+               SD2;
+           {JID, affiliation, member, Reason} ->
+               SD1 = set_affiliation(JID, member, SD, Reason),
+               SD2 = set_role(JID, participant, SD1),
+               send_update_presence(JID, Reason, SD2, SD),
+               maybe_send_affiliation(JID, member, SD2),
+               SD2;
+           {JID, role, Role, Reason} ->
+               SD1 = set_role(JID, Role, SD),
+               catch send_new_presence(JID, Reason, SD1, SD),
+               SD1;
+           {JID, affiliation, A, _Reason} ->
+               SD1 = set_affiliation(JID, A, SD),
+               send_update_presence(JID, SD1, SD),
+               maybe_send_affiliation(JID, A, SD1),
+               SD1
+       end
+    catch E:R ->
+           ?ERROR_MSG("failed to set item ~p from ~s: ~p",
+                      [Item, jid:to_string(UJID),
+                       {E, {R, erlang:get_stacktrace()}}]),
+           SD
     end.
 
+-spec find_changed_items(jid(), affiliation(), role(),
+                        [muc_item()], binary(), state(), [admin_action()]) ->
+                               {result, [admin_action()]}.
 find_changed_items(_UJID, _UAffiliation, _URole, [],
                   _Lang, _StateData, Res) ->
     {result, Res};
+find_changed_items(_UJID, _UAffiliation, _URole,
+                  [#muc_item{jid = undefined, nick = undefined}|_],
+                  Lang, _StateData, _Res) ->
+    Txt = <<"Neither 'jid' nor 'nick' attribute found">>,
+    throw({error, xmpp:err_bad_request(Txt, Lang)});
+find_changed_items(_UJID, _UAffiliation, _URole,
+                  [#muc_item{role = undefined, affiliation = undefined}|_],
+                  Lang, _StateData, _Res) ->
+    Txt = <<"Neither 'role' nor 'affiliation' attribute found">>,
+    throw({error, xmpp:err_bad_request(Txt, Lang)});
 find_changed_items(UJID, UAffiliation, URole,
-                  [{xmlcdata, _} | Items], Lang, StateData, Res) ->
-    find_changed_items(UJID, UAffiliation, URole, Items,
-                      Lang, StateData, Res);
-find_changed_items(UJID, UAffiliation, URole,
-                  [#xmlel{name = <<"item">>, attrs = Attrs} = Item
-                   | Items],
+                  [#muc_item{jid = J, nick = Nick, reason = Reason0,
+                             role = Role, affiliation = Affiliation}|Items],
                   Lang, StateData, Res) ->
-    TJID = case fxml:get_attr(<<"jid">>, Attrs) of
-            {value, S} ->
-                case jid:from_string(S) of
-                  error ->
-                      ErrText = iolist_to_binary(
-                                   io_lib:format(translate:translate(
-                                                   Lang,
-                                                   <<"Jabber ID ~s is invalid">>),
-                                                 [S])),
-                      {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
-                  J -> {value, [J]}
-                end;
-            _ ->
-                case fxml:get_attr(<<"nick">>, Attrs) of
-                  {value, N} ->
-                      case find_jids_by_nick(N, StateData) of
-                        false ->
-                            ErrText = iolist_to_binary(
-                                         io_lib:format(
-                                           translate:translate(
-                                             Lang,
-                                             <<"Nickname ~s does not exist in the room">>),
-                                           [N])),
-                            {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
-                        J -> {value, J}
-                      end;
-                  _ ->
-                      Txt1 = <<"No 'nick' attribute found">>,
-                      {error, ?ERRT_BAD_REQUEST(Lang, Txt1)}
-                end
-          end,
-    case TJID of
-      {value, [JID | _] = JIDs} ->
-         TAffiliation = get_affiliation(JID, StateData),
-         TRole = get_role(JID, StateData),
-         case fxml:get_attr(<<"role">>, Attrs) of
-           false ->
-               case fxml:get_attr(<<"affiliation">>, Attrs) of
-                 false ->
-                     Txt2 = <<"No 'affiliation' attribute found">>,
-                     {error, ?ERRT_BAD_REQUEST(Lang, Txt2)};
-                 {value, StrAffiliation} ->
-                     case catch list_to_affiliation(StrAffiliation) of
-                       {'EXIT', _} ->
-                           ErrText1 = iolist_to_binary(
-                                         io_lib:format(
-                                           translate:translate(
-                                             Lang,
-                                             <<"Invalid affiliation: ~s">>),
-                                           [StrAffiliation])),
-                           {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText1)};
-                       SAffiliation ->
-                           ServiceAf = get_service_affiliation(JID, StateData),
-                           CanChangeRA = case can_change_ra(UAffiliation,
-                                                            URole,
-                                                            TAffiliation,
-                                                            TRole, affiliation,
-                                                            SAffiliation,
-                                                            ServiceAf)
-                                             of
-                                           nothing -> nothing;
-                                           true -> true;
-                                           check_owner ->
-                                               case search_affiliation(owner,
-                                                                       StateData)
-                                                   of
-                                                 [{OJID, _}] ->
-                                                     jid:remove_resource(OJID)
-                                                       /=
-                                                       jid:tolower(jid:remove_resource(UJID));
-                                                 _ -> true
-                                               end;
-                                           _ -> false
-                                         end,
-                           case CanChangeRA of
-                             nothing ->
-                                 find_changed_items(UJID, UAffiliation, URole,
-                                                    Items, Lang, StateData,
-                                                    Res);
-                             true ->
-                                 Reason = fxml:get_path_s(Item,
-                                                         [{elem, <<"reason">>},
-                                                          cdata]),
-                                 MoreRes = [{jid:remove_resource(Jidx),
-                                             affiliation, SAffiliation, Reason}
-                                            || Jidx <- JIDs],
-                                 find_changed_items(UJID, UAffiliation, URole,
-                                                    Items, Lang, StateData,
-                                                    [MoreRes | Res]);
-                             false ->
-                                Txt3 = <<"Changing role/affiliation is not allowed">>,
-                                {error, ?ERRT_NOT_ALLOWED(Lang, Txt3)}
-                           end
-                     end
-               end;
-           {value, StrRole} ->
-               case catch list_to_role(StrRole) of
-                 {'EXIT', _} ->
-                     ErrText1 = iolist_to_binary(
-                                   io_lib:format(translate:translate(
-                                                   Lang,
-                                                   <<"Invalid role: ~s">>),
-                                                 [StrRole])),
-                     {error, ?ERRT_BAD_REQUEST(Lang, ErrText1)};
-                 SRole ->
-                     ServiceAf = get_service_affiliation(JID, StateData),
-                     CanChangeRA = case can_change_ra(UAffiliation, URole,
-                                                      TAffiliation, TRole,
-                                                      role, SRole, ServiceAf)
-                                       of
-                                     nothing -> nothing;
-                                     true -> true;
-                                     check_owner ->
-                                         case search_affiliation(owner,
-                                                                 StateData)
-                                             of
-                                           [{OJID, _}] ->
-                                               jid:remove_resource(OJID)
-                                                 /=
-                                                 jid:tolower(jid:remove_resource(UJID));
-                                           _ -> true
-                                         end;
-                                     _ -> false
-                                   end,
-                     case CanChangeRA of
-                       nothing ->
-                           find_changed_items(UJID, UAffiliation, URole, Items,
-                                              Lang, StateData, Res);
-                       true ->
-                           Reason = fxml:get_path_s(Item,
-                                                   [{elem, <<"reason">>},
-                                                    cdata]),
-                           MoreRes = [{Jidx, role, SRole, Reason}
-                                      || Jidx <- JIDs],
-                           find_changed_items(UJID, UAffiliation, URole, Items,
-                                              Lang, StateData,
-                                              [MoreRes | Res]);
-                       _ ->
-                          Txt4 = <<"Changing role/affiliation is not allowed">>,
-                          {error, ?ERRT_NOT_ALLOWED(Lang, Txt4)}
-                     end
+    [JID | _] = JIDs = 
+       if J /= undefined ->
+               [J];
+          Nick /= undefined ->
+               case find_jids_by_nick(Nick, StateData) of
+                   [] ->
+                       ErrText = iolist_to_binary(
+                                   io_lib:format(
+                                     translate:translate(
+                                       Lang,
+                                       <<"Nickname ~s does not exist in the room">>),
+                                     [Nick])),
+                       throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
+                   JIDList ->
+                       JIDList
                end
-         end;
-      Err -> Err
-    end;
-find_changed_items(_UJID, _UAffiliation, _URole, _Items,
-                  _Lang, _StateData, _Res) ->
-    {error, ?ERR_BAD_REQUEST}.
+       end,
+    {RoleOrAff, RoleOrAffValue} = if Role == undefined ->
+                                         {affiliation, Affiliation};
+                                    true ->
+                                         {role, Role}
+                                 end,
+    TAffiliation = get_affiliation(JID, StateData),
+    TRole = get_role(JID, StateData),
+    ServiceAf = get_service_affiliation(JID, StateData),
+    CanChangeRA = case can_change_ra(UAffiliation,
+                                    URole,
+                                    TAffiliation,
+                                    TRole, RoleOrAff, RoleOrAffValue,
+                                    ServiceAf) of
+                     nothing -> nothing;
+                     true -> true;
+                     check_owner ->
+                         case search_affiliation(owner, StateData) of
+                             [{OJID, _}] ->
+                                 jid:remove_resource(OJID)
+                                     /=
+                                     jid:tolower(jid:remove_resource(UJID));
+                             _ -> true
+                         end;
+                     _ -> false
+                 end,
+    case CanChangeRA of
+       nothing ->
+           find_changed_items(UJID, UAffiliation, URole,
+                              Items, Lang, StateData,
+                              Res);
+       true ->
+           Reason = if is_binary(Reason0) -> Reason0;
+                       true -> <<"">>
+                    end,
+           MoreRes = [{jid:remove_resource(Jidx),
+                       RoleOrAff, RoleOrAffValue, Reason}
+                      || Jidx <- JIDs],
+           find_changed_items(UJID, UAffiliation, URole,
+                              Items, Lang, StateData,
+                              [MoreRes | Res]);
+       false ->
+           Txt = <<"Changing role/affiliation is not allowed">>,
+           throw({error, xmpp:err_not_allowed(Txt, Lang)})
+    end.
 
+-spec can_change_ra(affiliation(), role(), affiliation(), role(),
+                   affiliation, affiliation(), affiliation()) -> boolean();
+                  (affiliation(), role(), affiliation(), role(),
+                   role, role(), affiliation()) -> boolean().
 can_change_ra(_FAffiliation, _FRole, owner, _TRole,
              affiliation, owner, owner) ->
     %% A room owner tries to add as persistent owner a
@@ -3255,11 +2857,15 @@ can_change_ra(_FAffiliation, _FRole, _TAffiliation,
              _TRole, role, _Value, _ServiceAf) ->
     false.
 
+-spec send_kickban_presence(jid(), jid(), binary(),
+                           pos_integer(), state()) -> ok.
 send_kickban_presence(UJID, JID, Reason, Code, StateData) ->
     NewAffiliation = get_affiliation(JID, StateData),
     send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
                          StateData).
 
+-spec send_kickban_presence(jid(), jid(), binary(), pos_integer(),
+                           affiliation(), state()) -> ok.
 send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
                      StateData) ->
     LJID = jid:tolower(JID),
@@ -3288,77 +2894,51 @@ send_kickban_presence(UJID, JID, Reason, Code, NewAffiliation,
                  end,
                  LJIDs).
 
+-spec send_kickban_presence1(jid(), jid(), binary(), pos_integer(),
+                            affiliation(), state()) -> ok.
 send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
                       StateData) ->
     {ok, #user{jid = RealJID, nick = Nick}} =
        (?DICT):find(jid:tolower(UJID),
                     StateData#state.users),
-    SAffiliation = affiliation_to_list(Affiliation),
-    BannedJIDString = jid:to_string(RealJID),
     ActorNick = get_actor_nick(MJID, StateData),
-    lists:foreach(fun ({_LJID, Info}) ->
-                         JidAttrList = case Info#user.role == moderator orelse
-                                              (StateData#state.config)#config.anonymous
-                                                == false
-                                           of
-                                         true ->
-                                             [{<<"jid">>, BannedJIDString}];
-                                         false -> []
-                                       end,
-                         ItemAttrs = [{<<"affiliation">>, SAffiliation},
-                                      {<<"role">>, <<"none">>}]
-                                       ++ JidAttrList,
-                         ItemEls = case Reason of
-                                     <<"">> -> [];
-                                     _ ->
-                                         [#xmlel{name = <<"reason">>,
-                                                 attrs = [],
-                                                 children =
-                                                     [{xmlcdata, Reason}]}]
-                                   end,
-                         ItemElsActor = case MJID of
-                                          <<"">> -> [];
-                                          _ -> [#xmlel{name = <<"actor">>,
-                                               attrs =
-                                                   [{<<"nick">>, ActorNick}]}]
-                                   end,
-                         Packet = #xmlel{name = <<"presence">>,
-                                         attrs =
-                                             [{<<"type">>, <<"unavailable">>}],
-                                         children =
-                                             [#xmlel{name = <<"x">>,
-                                                     attrs =
-                                                         [{<<"xmlns">>,
-                                                           ?NS_MUC_USER}],
-                                                     children =
-                                                         [#xmlel{name =
-                                                                     <<"item">>,
-                                                                 attrs =
-                                                                     ItemAttrs,
-                                                                 children =
-                                                                      ItemElsActor ++  ItemEls},
-                                                          #xmlel{name =
-                                                                     <<"status">>,
-                                                                 attrs =
-                                                                     [{<<"code">>,
-                                                                       Code}],
-                                                                 children =
-                                                                     []}]}]},
-                         RoomJIDNick = jid:replace_resource(
-                                         StateData#state.jid, Nick),
-                         send_wrapped(RoomJIDNick, Info#user.jid, Packet,
-                                      ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
-                         IsSubscriber = Info#user.is_subscriber,
-                         IsOccupant = Info#user.last_presence /= undefined,
-                         if (IsSubscriber and not IsOccupant) ->
-                                 send_wrapped(RoomJIDNick, Info#user.jid, Packet,
-                                              ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
-                            true ->
-                                 ok
-                         end
-                 end,
-                 (?DICT):to_list(StateData#state.users)).
+    lists:foreach(
+      fun({_LJID, Info}) ->
+             Item0 = #muc_item{affiliation = Affiliation,
+                               role = none},
+             Item1 = case Info#user.role == moderator orelse
+                         (StateData#state.config)#config.anonymous
+                         == false of
+                         true -> Item0#muc_item{jid = RealJID};
+                         false -> Item0
+                     end,
+             Item2 = if is_binary(Reason), Reason /= <<"">> ->
+                             Item1#muc_item{reason = Reason};
+                        true ->
+                             Item1
+                     end,
+             Item = case ActorNick of
+                        <<"">> -> Item2;
+                        _ -> Item2#muc_item{actor = #muc_actor{nick = ActorNick}}
+                    end,
+             Packet = #presence{type = unavailable,
+                                sub_els = [#muc_user{items = [Item],
+                                                     status_codes = [Code]}]},
+             RoomJIDNick = jid:replace_resource(StateData#state.jid, Nick),
+             send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+                          ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
+             IsSubscriber = Info#user.is_subscriber,
+             IsOccupant = Info#user.last_presence /= undefined,
+             if (IsSubscriber and not IsOccupant) ->
+                     send_wrapped(RoomJIDNick, Info#user.jid, Packet,
+                                  ?NS_MUCSUB_NODES_PARTICIPANTS, StateData);
+                true ->
+                     ok
+             end
+      end,
+      (?DICT):to_list(StateData#state.users)).
 
+-spec get_actor_nick(binary() | jid(), state()) -> binary().
 get_actor_nick(<<"">>, _StateData) ->
     <<"">>;
 get_actor_nick(MJID, StateData) ->
@@ -3369,97 +2949,86 @@ get_actor_nick(MJID, StateData) ->
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Owner stuff
-
-process_iq_owner(From, set, Lang, SubEl, StateData) ->
+-spec process_iq_owner(jid(), iq(), state()) ->
+                             {result, undefined | muc_owner()} |
+                             {result, undefined | muc_owner(), state() | stop} |
+                             {error, error()}.
+process_iq_owner(From, #iq{type = set, lang = Lang,
+                          sub_els = [#muc_owner{destroy = Destroy,
+                                                config = Config,
+                                                items = Items}]},
+                StateData) ->
     FAffiliation = get_affiliation(From, StateData),
-    case FAffiliation of
-      owner ->
-         #xmlel{children = Els} = SubEl,
-         case fxml:remove_cdata(Els) of
-           [#xmlel{name = <<"x">>} = XEl] ->
-               case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
-                     fxml:get_tag_attr_s(<<"type">>, XEl)}
-                   of
-                 {?NS_XDATA, <<"cancel">>} -> {result, [], StateData};
-                 {?NS_XDATA, <<"submit">>} ->
-                     case is_allowed_log_change(XEl, StateData, From) andalso
-                            is_allowed_persistent_change(XEl, StateData, From)
-                              andalso
-                              is_allowed_room_name_desc_limits(XEl, StateData)
-                                andalso
-                                is_password_settings_correct(XEl, StateData)
-                         of
-                       true -> set_config(XEl, StateData, Lang);
-                       false -> {error, ?ERR_NOT_ACCEPTABLE}
-                     end;
-                 _ ->
-                    Txt = <<"Incorrect data form">>,
-                    {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
-               end;
-           [#xmlel{name = <<"destroy">>} = SubEl1] ->
-               ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
-                         [jid:to_string(StateData#state.jid),
-                          jid:to_string(From)]),
-               add_to_log(room_existence, destroyed, StateData),
-               destroy_room(SubEl1, StateData);
-           Items ->
-               process_admin_items_set(From, Items, Lang, StateData)
-         end;
-      _ ->
-         ErrText = <<"Owner privileges required">>,
-         {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+    if FAffiliation /= owner ->
+           ErrText = <<"Owner privileges required">>,
+           {error, xmpp:err_forbidden(ErrText, Lang)};
+       Destroy /= undefined, Config == undefined, Items == [] ->
+           ?INFO_MSG("Destroyed MUC room ~s by the owner ~s",
+                     [jid:to_string(StateData#state.jid), jid:to_string(From)]),
+           add_to_log(room_existence, destroyed, StateData),
+           destroy_room(Destroy, StateData);
+       Config /= undefined, Destroy == undefined, Items == [] ->
+           case Config of
+               #xdata{type = cancel} ->
+                   {result, undefined};
+               #xdata{type = submit} ->
+                   case is_allowed_log_change(Config, StateData, From) andalso
+                       is_allowed_persistent_change(Config, StateData, From) andalso
+                       is_allowed_room_name_desc_limits(Config, StateData) andalso
+                       is_password_settings_correct(Config, StateData) of
+                       true -> set_config(Config, StateData, Lang);
+                       false -> {error, xmpp:err_not_acceptable()}
+                   end;
+               _ ->
+                   Txt = <<"Incorrect data form">>,
+                   {error, xmpp:err_bad_request(Txt, Lang)}
+           end;
+       Items /= [], Config == undefined, Destroy == undefined ->
+           process_admin_items_set(From, Items, Lang, StateData);
+       true ->
+           {error, xmpp:err_bad_request()}
     end;
-process_iq_owner(From, get, Lang, SubEl, StateData) ->
+process_iq_owner(From, #iq{type = get, lang = Lang,
+                          sub_els = [#muc_owner{destroy = Destroy,
+                                                config = Config,
+                                                items = Items}]},
+                StateData) ->
     FAffiliation = get_affiliation(From, StateData),
-    case FAffiliation of
-      owner ->
-         #xmlel{children = Els} = SubEl,
-         case fxml:remove_cdata(Els) of
-           [] -> get_config(Lang, StateData, From);
-           [Item] ->
-               case fxml:get_tag_attr(<<"affiliation">>, Item) of
-                 false ->
-                     Txt = <<"No 'affiliation' attribute found">>,
-                     {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-                 {value, StrAffiliation} ->
-                     case catch list_to_affiliation(StrAffiliation) of
-                       {'EXIT', _} ->
-                           ErrText = iolist_to_binary(
-                                        io_lib:format(
-                                          translate:translate(
-                                            Lang,
-                                            <<"Invalid affiliation: ~s">>),
-                                          [StrAffiliation])),
-                           {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
-                       SAffiliation ->
-                           Items = items_with_affiliation(SAffiliation,
-                                                          StateData),
-                           {result, Items, StateData}
-                     end
-               end;
-           _ -> {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
-         end;
-      _ ->
-         ErrText = <<"Owner privileges required">>,
-         {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+    if FAffiliation /= owner ->
+           ErrText = <<"Owner privileges required">>,
+           {error, xmpp:err_forbidden(ErrText, Lang)};
+       Destroy == undefined, Config == undefined ->
+           case Items of
+               [] ->
+                   {result,
+                    #muc_owner{config = get_config(Lang, StateData, From)}};
+               [#muc_item{affiliation = undefined}] ->
+                   Txt = <<"No 'affiliation' attribute found">>,
+                   {error, xmpp:err_bad_request(Txt, Lang)};
+               [#muc_item{affiliation = Affiliation}] ->
+                   Items = items_with_affiliation(Affiliation, StateData),
+                   {result, #muc_owner{items = Items}};
+               [_|_] ->
+                   Txt = <<"Too many <item/> elements">>,
+                   {error, xmpp:err_bad_request(Txt, Lang)}
+           end;
+       true ->
+           {error, xmpp:err_bad_request()}
     end.
 
-is_allowed_log_change(XEl, StateData, From) ->
-    case lists:keymember(<<"muc#roomconfig_enablelogging">>,
-                        1, jlib:parse_xdata_submit(XEl))
-       of
-      false -> true;
-      true ->
-         allow ==
-           mod_muc_log:check_access_log(StateData#state.server_host,
-                                        From)
+-spec is_allowed_log_change(xdata(), state(), jid()) -> boolean().
+is_allowed_log_change(X, StateData, From) ->
+    case xmpp_util:has_xdata_var(<<"muc#roomconfig_enablelogging">>, X) of
+       false -> true;
+       true ->
+           allow ==
+               mod_muc_log:check_access_log(StateData#state.server_host,
+                                            From)
     end.
 
-is_allowed_persistent_change(XEl, StateData, From) ->
-    case
-      lists:keymember(<<"muc#roomconfig_persistentroom">>, 1,
-                     jlib:parse_xdata_submit(XEl))
-       of
+-spec is_allowed_persistent_change(xdata(), state(), jid()) -> boolean().
+is_allowed_persistent_change(X, StateData, From) ->
+    case xmpp_util:has_xdata_var(<<"muc#roomconfig_persistentroom">>, X) of
       false -> true;
       true ->
          {_AccessRoute, _AccessCreate, _AccessAdmin,
@@ -3472,58 +3041,57 @@ is_allowed_persistent_change(XEl, StateData, From) ->
 
 %% Check if the Room Name and Room Description defined in the Data Form
 %% are conformant to the configured limits
-is_allowed_room_name_desc_limits(XEl, StateData) ->
-    IsNameAccepted = case
-                      lists:keysearch(<<"muc#roomconfig_roomname">>, 1,
-                                      jlib:parse_xdata_submit(XEl))
-                        of
-                      {value, {_, [N]}} ->
-                          byte_size(N) =<
-                            gen_mod:get_module_opt(StateData#state.server_host,
-                                                   mod_muc, max_room_name,
-                                                    fun(infinity) -> infinity;
-                                                       (I) when is_integer(I),
-                                                                I>0 -> I
-                                                    end, infinity);
-                      _ -> true
+-spec is_allowed_room_name_desc_limits(xdata(), state()) -> boolean().
+is_allowed_room_name_desc_limits(XData, StateData) ->
+    IsNameAccepted = case xmpp_util:get_xdata_values(
+                           <<"muc#roomconfig_roomname">>, XData) of
+                        [N] ->
+                            byte_size(N) =<
+                                gen_mod:get_module_opt(
+                                  StateData#state.server_host,
+                                  mod_muc, max_room_name,
+                                  fun(infinity) -> infinity;
+                                     (I) when is_integer(I),
+                                              I>0 -> I
+                                  end, infinity);
+                        _ ->
+                            true
                     end,
-    IsDescAccepted = case
-                      lists:keysearch(<<"muc#roomconfig_roomdesc">>, 1,
-                                      jlib:parse_xdata_submit(XEl))
-                        of
-                      {value, {_, [D]}} ->
-                          byte_size(D) =<
-                            gen_mod:get_module_opt(StateData#state.server_host,
-                                                   mod_muc, max_room_desc,
-                                                    fun(infinity) -> infinity;
-                                                       (I) when is_integer(I),
-                                                                I>0 ->
-                                                            I
-                                                    end, infinity);
-                      _ -> true
+    IsDescAccepted = case xmpp_util:get_xdata_values(
+                           <<"muc#roomconfig_roomdesc">>, XData) of
+                        [D] ->
+                            byte_size(D) =<
+                                gen_mod:get_module_opt(
+                                  StateData#state.server_host,
+                                  mod_muc, max_room_desc,
+                                  fun(infinity) -> infinity;
+                                     (I) when is_integer(I),
+                                              I>0 ->
+                                          I
+                                  end, infinity);
+                        _ -> true
                     end,
     IsNameAccepted and IsDescAccepted.
 
 %% Return false if:
 %% "the password for a password-protected room is blank"
-is_password_settings_correct(XEl, StateData) ->
+-spec is_password_settings_correct(xdata(), state()) -> boolean().
+is_password_settings_correct(XData, StateData) ->
     Config = StateData#state.config,
     OldProtected = Config#config.password_protected,
     OldPassword = Config#config.password,
-    NewProtected = case
-                    lists:keysearch(<<"muc#roomconfig_passwordprotectedroom">>,
-                                    1, jlib:parse_xdata_submit(XEl))
-                      of
-                    {value, {_, [<<"1">>]}} -> true;
-                    {value, {_, [<<"0">>]}} -> false;
-                    _ -> undefined
+    NewProtected = case xmpp_util:get_xdata_values(
+                         <<"muc#roomconfig_passwordprotectedroom">>, XData) of
+                      [<<"1">>] -> true;
+                      [<<"true">>] -> true;
+                      [<<"0">>] -> false;
+                      [<<"false">>] -> false;
+                      _ -> undefined
                   end,
-    NewPassword = case
-                   lists:keysearch(<<"muc#roomconfig_roomsecret">>, 1,
-                                   jlib:parse_xdata_submit(XEl))
-                     of
-                   {value, {_, [P]}} -> P;
-                   _ -> undefined
+    NewPassword = case xmpp_util:get_xdata_values(
+                        <<"muc#roomconfig_roomsecret">>, XData) of
+                     [P] -> P;
+                     _ -> undefined
                  end,
     case {OldProtected, NewProtected, OldPassword,
          NewPassword}
@@ -3535,40 +3103,35 @@ is_password_settings_correct(XEl, StateData) ->
       _ -> true
     end.
 
--define(XFIELD(Type, Label, Var, Val),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"type">>, Type},
-                   {<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, Val}]}]}).
+-define(XFIELD(Type, Label, Var, Vals),
+       #xdata_field{type = Type,
+                    label = translate:translate(Lang, Label),
+                    var = Var,
+                    values = Vals}).
 
 -define(BOOLXFIELD(Label, Var, Val),
-       ?XFIELD(<<"boolean">>, Label, Var,
+       ?XFIELD(boolean, Label, Var,
                case Val of
-                 true -> <<"1">>;
-                 _ -> <<"0">>
+                 true -> [<<"1">>];
+                 _ -> [<<"0">>]
                end)).
 
 -define(STRINGXFIELD(Label, Var, Val),
-       ?XFIELD(<<"text-single">>, Label, Var, Val)).
+       ?XFIELD('text-single', Label, Var, [Val])).
 
 -define(PRIVATEXFIELD(Label, Var, Val),
-       ?XFIELD(<<"text-private">>, Label, Var, Val)).
+       ?XFIELD('text-private', Label, Var, [Val])).
 
 -define(JIDMULTIXFIELD(Label, Var, JIDList),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"type">>, <<"jid-multi">>},
-                   {<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, jid:to_string(JID)}]}
-                   || JID <- JIDList]}).
+       ?XFIELD('jid-multi', Label, Var,
+               [jid:to_string(JID) || JID <- JIDList])).
 
+-spec make_options([{binary(), binary()}], binary()) -> [xdata_option()].
+make_options(Options, Lang) ->
+    [#xdata_option{label = translate:translate(Lang, Label),
+                  value = Value} || {Label, Value} <- Options].
+
+-spec get_default_room_maxusers(state()) -> non_neg_integer().
 get_default_room_maxusers(RoomState) ->
     DefRoomOpts =
        gen_mod:get_module_opt(RoomState#state.server_host,
@@ -3578,342 +3141,193 @@ get_default_room_maxusers(RoomState) ->
     RoomState2 = set_opts(DefRoomOpts, RoomState),
     (RoomState2#state.config)#config.max_users.
 
+-spec get_config(binary(), state(), jid()) -> xdata().
 get_config(Lang, StateData, From) ->
-    {_AccessRoute, _AccessCreate, _AccessAdmin,
-     AccessPersistent} =
+    {_AccessRoute, _AccessCreate, _AccessAdmin, AccessPersistent} =
        StateData#state.access,
     ServiceMaxUsers = get_service_max_users(StateData),
-    DefaultRoomMaxUsers =
-       get_default_room_maxusers(StateData),
+    DefaultRoomMaxUsers = get_default_room_maxusers(StateData),
     Config = StateData#state.config,
-    {MaxUsersRoomInteger, MaxUsersRoomString} = case
-                                                 get_max_users(StateData)
-                                                   of
-                                                 N when is_integer(N) ->
-                                                     {N,
-                                                      jlib:integer_to_binary(N)};
-                                                 _ -> {0, <<"none">>}
-                                               end,
-    Res = [#xmlel{name = <<"title">>, attrs = [],
-                 children =
-                     [{xmlcdata,
-                       iolist_to_binary(
-                          io_lib:format(
-                            translate:translate(
-                              Lang,
-                              <<"Configuration of room ~s">>),
-                            [jid:to_string(StateData#state.jid)]))}]},
-          #xmlel{name = <<"field">>,
-                 attrs =
-                     [{<<"type">>, <<"hidden">>},
-                      {<<"var">>, <<"FORM_TYPE">>}],
-                 children =
-                     [#xmlel{name = <<"value">>, attrs = [],
-                             children =
-                                 [{xmlcdata,
-                                   <<"http://jabber.org/protocol/muc#roomconfig">>}]}]},
-          ?STRINGXFIELD(<<"Room title">>,
-                        <<"muc#roomconfig_roomname">>, (Config#config.title)),
-          ?STRINGXFIELD(<<"Room description">>,
-                        <<"muc#roomconfig_roomdesc">>,
-                        (Config#config.description))]
-           ++
-           case acl:match_rule(StateData#state.server_host,
-                               AccessPersistent, From)
-               of
-             allow ->
-                 [?BOOLXFIELD(<<"Make room persistent">>,
-                              <<"muc#roomconfig_persistentroom">>,
-                              (Config#config.persistent))];
-             _ -> []
-           end
-             ++
-             [?BOOLXFIELD(<<"Make room public searchable">>,
-                          <<"muc#roomconfig_publicroom">>,
-                          (Config#config.public)),
-              ?BOOLXFIELD(<<"Make participants list public">>,
-                          <<"public_list">>, (Config#config.public_list)),
-              ?BOOLXFIELD(<<"Make room password protected">>,
-                          <<"muc#roomconfig_passwordprotectedroom">>,
-                          (Config#config.password_protected)),
-              ?PRIVATEXFIELD(<<"Password">>,
-                             <<"muc#roomconfig_roomsecret">>,
-                             case Config#config.password_protected of
-                               true -> Config#config.password;
-                               false -> <<"">>
-                             end),
-              #xmlel{name = <<"field">>,
-                     attrs =
-                         [{<<"type">>, <<"list-single">>},
-                          {<<"label">>,
-                           translate:translate(Lang,
-                                               <<"Maximum Number of Occupants">>)},
-                          {<<"var">>, <<"muc#roomconfig_maxusers">>}],
-                     children =
-                         [#xmlel{name = <<"value">>, attrs = [],
-                                 children = [{xmlcdata, MaxUsersRoomString}]}]
-                           ++
-                           if is_integer(ServiceMaxUsers) -> [];
-                              true ->
-                                  [#xmlel{name = <<"option">>,
-                                          attrs =
-                                              [{<<"label">>,
-                                                translate:translate(Lang,
-                                                                    <<"No limit">>)}],
-                                          children =
-                                              [#xmlel{name = <<"value">>,
-                                                      attrs = [],
-                                                      children =
-                                                          [{xmlcdata,
-                                                            <<"none">>}]}]}]
-                           end
-                             ++
-                             [#xmlel{name = <<"option">>,
-                                     attrs =
-                                         [{<<"label">>,
-                                           jlib:integer_to_binary(N)}],
-                                     children =
-                                         [#xmlel{name = <<"value">>,
-                                                 attrs = [],
-                                                 children =
-                                                     [{xmlcdata,
-                                                       jlib:integer_to_binary(N)}]}]}
-                              || N
-                                     <- lists:usort([ServiceMaxUsers,
-                                                     DefaultRoomMaxUsers,
-                                                     MaxUsersRoomInteger
-                                                     | ?MAX_USERS_DEFAULT_LIST]),
-                                 N =< ServiceMaxUsers]},
-              #xmlel{name = <<"field">>,
-                     attrs =
-                         [{<<"type">>, <<"list-single">>},
-                          {<<"label">>,
-                           translate:translate(Lang,
-                                               <<"Present real Jabber IDs to">>)},
-                          {<<"var">>, <<"muc#roomconfig_whois">>}],
-                     children =
-                         [#xmlel{name = <<"value">>, attrs = [],
-                                 children =
-                                     [{xmlcdata,
-                                       if Config#config.anonymous ->
-                                              <<"moderators">>;
-                                          true -> <<"anyone">>
-                                       end}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"moderators only">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"moderators">>}]}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"anyone">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"anyone">>}]}]}]},
-              #xmlel{name = <<"field">>,
-                     attrs =
-                         [{<<"type">>, <<"list-multi">>},
-                          {<<"label">>,
-                           translate:translate(Lang,
-                                               <<"Roles for which Presence is Broadcasted">>)},
-                          {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}],
-                     children =
-                          lists:map(
-                            fun(Role) ->
-                                    #xmlel{name = <<"value">>, attrs = [],
-                                           children =
-                                               [{xmlcdata,
-                                                 atom_to_binary(Role, utf8)}]}
-                            end, Config#config.presence_broadcast
-                           ) ++
-                         [#xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"Moderator">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"moderator">>}]}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"Participant">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"participant">>}]}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"Visitor">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"visitor">>}]}]}
-                          ]},
-              ?BOOLXFIELD(<<"Make room members-only">>,
-                          <<"muc#roomconfig_membersonly">>,
-                          (Config#config.members_only)),
-              ?BOOLXFIELD(<<"Make room moderated">>,
-                          <<"muc#roomconfig_moderatedroom">>,
-                          (Config#config.moderated)),
-              ?BOOLXFIELD(<<"Default users as participants">>,
-                          <<"members_by_default">>,
-                          (Config#config.members_by_default)),
-              ?BOOLXFIELD(<<"Allow users to change the subject">>,
-                          <<"muc#roomconfig_changesubject">>,
-                          (Config#config.allow_change_subj)),
-              ?BOOLXFIELD(<<"Allow users to send private messages">>,
-                          <<"allow_private_messages">>,
-                          (Config#config.allow_private_messages)),
-              #xmlel{name = <<"field">>,
-                     attrs =
-                         [{<<"type">>, <<"list-single">>},
-                          {<<"label">>,
-                           translate:translate(Lang,
-                                               <<"Allow visitors to send private messages to">>)},
-                          {<<"var">>,
-                           <<"allow_private_messages_from_visitors">>}],
-                     children =
-                         [#xmlel{name = <<"value">>, attrs = [],
-                                 children =
-                                     [{xmlcdata,
-                                       case
-                                         Config#config.allow_private_messages_from_visitors
-                                           of
-                                         anyone -> <<"anyone">>;
-                                         moderators -> <<"moderators">>;
-                                         nobody -> <<"nobody">>
-                                       end}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"nobody">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata, <<"nobody">>}]}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"moderators only">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"moderators">>}]}]},
-                          #xmlel{name = <<"option">>,
-                                 attrs =
-                                     [{<<"label">>,
-                                       translate:translate(Lang,
-                                                           <<"anyone">>)}],
-                                 children =
-                                     [#xmlel{name = <<"value">>, attrs = [],
-                                             children =
-                                                 [{xmlcdata,
-                                                   <<"anyone">>}]}]}]},
-              ?BOOLXFIELD(<<"Allow users to query other users">>,
-                          <<"allow_query_users">>,
-                          (Config#config.allow_query_users)),
-              ?BOOLXFIELD(<<"Allow users to send invites">>,
-                          <<"muc#roomconfig_allowinvites">>,
-                          (Config#config.allow_user_invites)),
-              ?BOOLXFIELD(<<"Allow visitors to send status text in "
-                            "presence updates">>,
-                          <<"muc#roomconfig_allowvisitorstatus">>,
-                          (Config#config.allow_visitor_status)),
-              ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
-                          <<"muc#roomconfig_allowvisitornickchange">>,
-                          (Config#config.allow_visitor_nickchange)),
-              ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
-                          <<"muc#roomconfig_allowvoicerequests">>,
-                          (Config#config.allow_voice_requests)),
-              ?BOOLXFIELD(<<"Allow subscription">>,
-                          <<"muc#roomconfig_allow_subscription">>,
-                          (Config#config.allow_subscription)),
-              ?STRINGXFIELD(<<"Minimum interval between voice requests "
-                              "(in seconds)">>,
-                            <<"muc#roomconfig_voicerequestmininterval">>,
-                            (jlib:integer_to_binary(Config#config.voice_request_min_interval)))]
-               ++
-               case ejabberd_captcha:is_feature_available() of
-                 true ->
-                     [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
-                                  <<"captcha_protected">>,
-                                  (Config#config.captcha_protected))];
-                 false -> []
-               end ++
-                 [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
-                                  <<"muc#roomconfig_captcha_whitelist">>,
-                                  ((?SETS):to_list(Config#config.captcha_whitelist)))]
-                   ++
-                   case
-                     mod_muc_log:check_access_log(StateData#state.server_host,
-                                                  From)
-                       of
-                     allow ->
-                         [?BOOLXFIELD(<<"Enable logging">>,
-                                      <<"muc#roomconfig_enablelogging">>,
-                                      (Config#config.logging))];
-                     _ -> []
-                   end,
-    X = ejabberd_hooks:run_fold(get_room_config,
-                               StateData#state.server_host,
-                               Res,
-                               [StateData, From, Lang]),
-    {result,
-     [#xmlel{name = <<"instructions">>, attrs = [],
-            children =
-                [{xmlcdata,
-                  translate:translate(Lang,
-                                      <<"You need an x:data capable client to "
-                                        "configure room">>)}]},
-      #xmlel{name = <<"x">>,
-            attrs =
-                [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
-            children = X}],
-     StateData}.
-
-set_config(XEl, StateData, Lang) ->
-    XData = jlib:parse_xdata_submit(XEl),
-    case XData of
-      invalid -> {error, ?ERRT_BAD_REQUEST(Lang, <<"Incorrect data form">>)};
-      _ ->
-         case set_xoption(XData, StateData#state.config,
-                          StateData#state.server_host, Lang) of
-           #config{} = Config ->
-               Res = change_config(Config, StateData),
-               {result, _, NSD} = Res,
-               Type = case {(StateData#state.config)#config.logging,
-                            Config#config.logging}
-                          of
-                        {true, false} -> roomconfig_change_disabledlogging;
-                        {false, true} -> roomconfig_change_enabledlogging;
-                        {_, _} -> roomconfig_change
-                      end,
-               Users = [{U#user.jid, U#user.nick, U#user.role}
-                        || {_, U} <- (?DICT):to_list(StateData#state.users)],
-               add_to_log(Type, Users, NSD),
-               Res;
-           Err -> Err
-         end
+    {MaxUsersRoomInteger, MaxUsersRoomString} =
+       case get_max_users(StateData) of
+           N when is_integer(N) ->
+               {N, integer_to_binary(N)};
+           _ -> {0, <<"none">>}
+       end,
+    Title = iolist_to_binary(
+             io_lib:format(
+               translate:translate(Lang, <<"Configuration of room ~s">>),
+               [jid:to_string(StateData#state.jid)])),
+    Fs = [#xdata_field{type = hidden,
+                      var = <<"FORM_TYPE">>,
+                      values = [<<"http://jabber.org/protocol/muc#roomconfig">>]},
+         ?STRINGXFIELD(<<"Room title">>,
+                       <<"muc#roomconfig_roomname">>, (Config#config.title)),
+         ?STRINGXFIELD(<<"Room description">>,
+                       <<"muc#roomconfig_roomdesc">>,
+                       (Config#config.description))] ++
+       case acl:match_rule(StateData#state.server_host, AccessPersistent, From) of
+           allow ->
+               [?BOOLXFIELD(<<"Make room persistent">>,
+                            <<"muc#roomconfig_persistentroom">>,
+                            (Config#config.persistent))];
+           deny -> []
+       end ++
+       [?BOOLXFIELD(<<"Make room public searchable">>,
+                    <<"muc#roomconfig_publicroom">>,
+                    (Config#config.public)),
+        ?BOOLXFIELD(<<"Make participants list public">>,
+                    <<"public_list">>, (Config#config.public_list)),
+        ?BOOLXFIELD(<<"Make room password protected">>,
+                    <<"muc#roomconfig_passwordprotectedroom">>,
+                    (Config#config.password_protected)),
+        ?PRIVATEXFIELD(<<"Password">>,
+                       <<"muc#roomconfig_roomsecret">>,
+                       case Config#config.password_protected of
+                           true -> Config#config.password;
+                           false -> <<"">>
+                       end),
+        #xdata_field{type = 'list-single',
+                     label = translate:translate(
+                               Lang, <<"Maximum Number of Occupants">>),
+                     var = <<"muc#roomconfig_maxusers">>,
+                     values = [MaxUsersRoomString],
+                     options =
+                         if is_integer(ServiceMaxUsers) -> [];
+                            true -> make_options(
+                                      [{<<"No limit">>, <<"none">>}],
+                                      Lang)
+                         end ++
+                         make_options(
+                           [{integer_to_binary(N), integer_to_binary(N)}
+                            || N <- lists:usort([ServiceMaxUsers,
+                                                 DefaultRoomMaxUsers,
+                                                 MaxUsersRoomInteger
+                                                 | ?MAX_USERS_DEFAULT_LIST]),
+                               N =< ServiceMaxUsers],
+                           Lang)},
+        #xdata_field{type = 'list-single',
+                     label = translate:translate(
+                               Lang, <<"Present real Jabber IDs to">>),
+                     var = <<"muc#roomconfig_whois">>,
+                     values = [if Config#config.anonymous -> <<"moderators">>;
+                                  true -> <<"anyone">>
+                               end],
+                     options = make_options(
+                                 [{<<"moderators only">>, <<"moderators">>},
+                                  {<<"anyone">>, <<"anyone">>}],
+                                 Lang)},
+        #xdata_field{type = 'list-multi',
+                     label = translate:translate(
+                               Lang,
+                               <<"Roles for which Presence is Broadcasted">>),
+                     var = <<"muc#roomconfig_presencebroadcast">>,
+                     values = [atom_to_binary(Role, utf8)
+                               || Role <- Config#config.presence_broadcast],
+                     options = make_options(
+                                 [{<<"Moderator">>, <<"moderator">>},
+                                  {<<"Participant">>, <<"participant">>},
+                                  {<<"Visitor">>, <<"visitor">>}],
+                                 Lang)},
+        ?BOOLXFIELD(<<"Make room members-only">>,
+                    <<"muc#roomconfig_membersonly">>,
+                    (Config#config.members_only)),
+        ?BOOLXFIELD(<<"Make room moderated">>,
+                    <<"muc#roomconfig_moderatedroom">>,
+                    (Config#config.moderated)),
+        ?BOOLXFIELD(<<"Default users as participants">>,
+                    <<"members_by_default">>,
+                    (Config#config.members_by_default)),
+        ?BOOLXFIELD(<<"Allow users to change the subject">>,
+                    <<"muc#roomconfig_changesubject">>,
+                    (Config#config.allow_change_subj)),
+        ?BOOLXFIELD(<<"Allow users to send private messages">>,
+                    <<"allow_private_messages">>,
+                    (Config#config.allow_private_messages)),
+        #xdata_field{type = 'list-single',
+                     label = translate:translate(
+                               Lang,
+                               <<"Allow visitors to send private messages to">>),
+                     var = <<"allow_private_messages_from_visitors">>,
+                     values = [case Config#config.allow_private_messages_from_visitors of
+                                   anyone -> <<"anyone">>;
+                                   moderators -> <<"moderators">>;
+                                   nobody -> <<"nobody">>
+                               end],
+                     options = make_options(
+                                 [{<<"nobody">>, <<"nobody">>},
+                                  {<<"moderators only">>, <<"moderators">>},
+                                  {<<"anyone">>, <<"anyone">>}],
+                                 Lang)},
+        ?BOOLXFIELD(<<"Allow users to query other users">>,
+                    <<"allow_query_users">>,
+                    (Config#config.allow_query_users)),
+        ?BOOLXFIELD(<<"Allow users to send invites">>,
+                    <<"muc#roomconfig_allowinvites">>,
+                    (Config#config.allow_user_invites)),
+        ?BOOLXFIELD(<<"Allow visitors to send status text in "
+                      "presence updates">>,
+                    <<"muc#roomconfig_allowvisitorstatus">>,
+                    (Config#config.allow_visitor_status)),
+        ?BOOLXFIELD(<<"Allow visitors to change nickname">>,
+                    <<"muc#roomconfig_allowvisitornickchange">>,
+                    (Config#config.allow_visitor_nickchange)),
+        ?BOOLXFIELD(<<"Allow visitors to send voice requests">>,
+                    <<"muc#roomconfig_allowvoicerequests">>,
+                    (Config#config.allow_voice_requests)),
+        ?BOOLXFIELD(<<"Allow subscription">>,
+                    <<"muc#roomconfig_allow_subscription">>,
+                    (Config#config.allow_subscription)),
+        ?STRINGXFIELD(<<"Minimum interval between voice requests "
+                        "(in seconds)">>,
+                      <<"muc#roomconfig_voicerequestmininterval">>,
+                      integer_to_binary(Config#config.voice_request_min_interval))]
+       ++
+       case ejabberd_captcha:is_feature_available() of
+           true ->
+               [?BOOLXFIELD(<<"Make room CAPTCHA protected">>,
+                            <<"captcha_protected">>,
+                            (Config#config.captcha_protected))];
+           false -> []
+       end ++
+       [?JIDMULTIXFIELD(<<"Exclude Jabber IDs from CAPTCHA challenge">>,
+                        <<"muc#roomconfig_captcha_whitelist">>,
+                        ((?SETS):to_list(Config#config.captcha_whitelist)))]
+       ++
+       case mod_muc_log:check_access_log(StateData#state.server_host, From) of
+           allow ->
+               [?BOOLXFIELD(<<"Enable logging">>,
+                            <<"muc#roomconfig_enablelogging">>,
+                            (Config#config.logging))];
+           deny -> []
+       end,
+    Fields = ejabberd_hooks:run_fold(get_room_config,
+                                    StateData#state.server_host,
+                                    Fs,
+                                    [StateData, From, Lang]),
+    #xdata{type = form, title = Title, fields = Fields}.
+
+-spec set_config(xdata(), state(), binary()) -> {error, error()} |
+                                               {result, undefined, state()}.
+set_config(#xdata{fields = Fields}, StateData, Lang) ->
+    Options = [{Var, Vals} || #xdata_field{var = Var, values = Vals} <- Fields],
+    case set_xoption(Options, StateData#state.config,
+                    StateData#state.server_host, Lang) of
+       #config{} = Config ->
+           Res = change_config(Config, StateData),
+           {result, _, NSD} = Res,
+           Type = case {(StateData#state.config)#config.logging,
+                        Config#config.logging}
+                  of
+                      {true, false} -> roomconfig_change_disabledlogging;
+                      {false, true} -> roomconfig_change_enabledlogging;
+                      {_, _} -> roomconfig_change
+                  end,
+           Users = [{U#user.jid, U#user.nick, U#user.role}
+                    || {_, U} <- (?DICT):to_list(StateData#state.users)],
+           add_to_log(Type, Users, NSD),
+           Res;
+       Err -> Err
     end.
 
 -define(SET_BOOL_XOPT(Opt, Val),
@@ -3928,21 +3342,32 @@ set_config(XEl, StateData, Lang) ->
          _ ->
              Txt = <<"Value of '~s' should be boolean">>,
              ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-             {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
+             {error, xmpp:err_bad_request(ErrTxt, Lang)}
        end).
 
 -define(SET_NAT_XOPT(Opt, Val),
-       case catch jlib:binary_to_integer(Val) of
+       case catch binary_to_integer(Val) of
          I when is_integer(I), I > 0 ->
              set_xoption(Opts, Config#config{Opt = I}, ServerHost, Lang);
          _ ->
              Txt = <<"Value of '~s' should be integer">>,
              ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-             {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)}
+             {error, xmpp:err_bad_request(ErrTxt, Lang)}
        end).
 
--define(SET_STRING_XOPT(Opt, Val),
-       set_xoption(Opts, Config#config{Opt = Val}, ServerHost, Lang)).
+-define(SET_STRING_XOPT(Opt, Vals),
+       try 
+           V = case Vals of
+                   [] -> <<"">>;
+                   [Val] -> Val;
+                   _ when is_atom(Vals) -> Vals
+               end,
+           set_xoption(Opts, Config#config{Opt = V}, ServerHost, Lang)
+       catch _:_ ->
+               Txt = <<"Incorrect value of option '~s'">>,
+               ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
+               {error, xmpp:err_bad_request(ErrTxt, Lang)}
+       end).
 
 -define(SET_JIDMULTI_XOPT(Opt, Vals),
        begin
@@ -3957,15 +3382,17 @@ set_config(XEl, StateData, Lang) ->
          set_xoption(Opts, Config#config{Opt = Set}, ServerHost, Lang)
        end).
 
+-spec set_xoption([{binary(), [binary()]}], #config{},
+                 binary(), binary()) -> #config{} | {error, error()}.
 set_xoption([], Config, _ServerHost, _Lang) -> Config;
-set_xoption([{<<"muc#roomconfig_roomname">>, [Val]}
+set_xoption([{<<"muc#roomconfig_roomname">>, Vals}
             | Opts],
            Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(title, Val);
-set_xoption([{<<"muc#roomconfig_roomdesc">>, [Val]}
+    ?SET_STRING_XOPT(title, Vals);
+set_xoption([{<<"muc#roomconfig_roomdesc">>, Vals}
             | Opts],
            Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(description, Val);
+    ?SET_STRING_XOPT(description, Vals);
 set_xoption([{<<"muc#roomconfig_changesubject">>, [Val]}
             | Opts],
            Config, ServerHost, Lang) ->
@@ -3994,7 +3421,7 @@ set_xoption([{<<"allow_private_messages_from_visitors">>,
       _ ->
          Txt = <<"Value of 'allow_private_messages_from_visitors' "
                  "should be anyone|moderators|nobody">>,
-         {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+         {error, xmpp:err_bad_request(Txt, Lang)}
     end;
 set_xoption([{<<"muc#roomconfig_allowvisitorstatus">>,
              [Val]}
@@ -4045,10 +3472,10 @@ set_xoption([{<<"muc#roomconfig_passwordprotectedroom">>,
             | Opts],
            Config, ServerHost, Lang) ->
     ?SET_BOOL_XOPT(password_protected, Val);
-set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]}
+set_xoption([{<<"muc#roomconfig_roomsecret">>, Vals}
             | Opts],
            Config, ServerHost, Lang) ->
-    ?SET_STRING_XOPT(password, Val);
+    ?SET_STRING_XOPT(password, Vals);
 set_xoption([{<<"anonymous">>, [Val]} | Opts],
            Config, ServerHost, Lang) ->
     ?SET_BOOL_XOPT(anonymous, Val);
@@ -4069,7 +3496,7 @@ set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts],
         error ->
            Txt = <<"Value of 'muc#roomconfig_presencebroadcast' should "
                    "be moderator|participant|visitor">>,
-           {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+           {error, xmpp:err_bad_request(Txt, Lang)};
         {M, P, V} ->
             Res =
                 if M -> [moderator]; true -> [] end ++
@@ -4101,7 +3528,7 @@ set_xoption([{<<"muc#roomconfig_whois">>, [Val]}
       _ ->
          Txt = <<"Value of 'muc#roomconfig_whois' should be "
                  "moderators|anyone">>,
-         {error, ?ERRT_BAD_REQUEST(Lang, Txt)}
+         {error, xmpp:err_bad_request(Txt, Lang)}
     end;
 set_xoption([{<<"muc#roomconfig_maxusers">>, [Val]}
             | Opts],
@@ -4125,7 +3552,7 @@ set_xoption([{<<"FORM_TYPE">>, _} | Opts], Config, ServerHost, Lang) ->
 set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
     Txt = <<"Unknown option '~s'">>,
     ErrTxt = iolist_to_binary(io_lib:format(Txt, [Opt])),
-    Err = {error, ?ERRT_BAD_REQUEST(Lang, ErrTxt)},
+    Err = {error, xmpp:err_bad_request(ErrTxt, Lang)},
     case ejabberd_hooks:run_fold(set_room_option,
                                 ServerHost,
                                 Err,
@@ -4136,6 +3563,7 @@ set_xoption([{Opt, Vals} | Opts], Config, ServerHost, Lang) ->
            set_xoption(Opts, setelement(Pos, Config, Val), ServerHost, Lang)
     end.
 
+-spec change_config(#config{}, state()) -> {result, undefined, state()}.
 change_config(Config, StateData) ->
     send_config_change_info(Config, StateData),
     NSD = remove_subscriptions(StateData#state{config = Config}),
@@ -4154,57 +3582,54 @@ change_config(Config, StateData) ->
          Config#config.members_only}
        of
       {false, true} ->
-         NSD1 = remove_nonmembers(NSD), {result, [], NSD1};
-      _ -> {result, [], NSD}
+         NSD1 = remove_nonmembers(NSD), {result, undefined, NSD1};
+      _ -> {result, undefined, NSD}
     end.
 
+-spec send_config_change_info(#config{}, state()) -> ok.
 send_config_change_info(Config, #state{config = Config}) -> ok;
 send_config_change_info(New, #state{config = Old} = StateData) ->
     Codes = case {Old#config.logging, New#config.logging} of
-             {false, true} -> [<<"170">>];
-             {true, false} -> [<<"171">>];
+             {false, true} -> [170];
+             {true, false} -> [171];
              _ -> []
            end
              ++
              case {Old#config.anonymous, New#config.anonymous} of
-               {true, false} -> [<<"172">>];
-               {false, true} -> [<<"173">>];
+               {true, false} -> [172];
+               {false, true} -> [173];
                _ -> []
              end
                ++
                case Old#config{anonymous = New#config.anonymous,
                                logging = New#config.logging} of
                  New -> [];
-                 _ -> [<<"104">>]
+                 _ -> [104]
                end,
-    StatusEls = [#xmlel{name = <<"status">>,
-                       attrs = [{<<"code">>, Code}],
-                       children = []} || Code <- Codes],
-    Message = #xmlel{name = <<"message">>,
-                    attrs = [{<<"type">>, <<"groupchat">>},
-                             {<<"id">>, randoms:get_string()}],
-                    children = [#xmlel{name = <<"x">>,
-                                       attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
-                                       children = StatusEls}]},
+    Message = #message{type = groupchat,
+                      id = randoms:get_string(),
+                      sub_els = [#muc_user{status_codes = Codes}]},
     send_wrapped_multiple(StateData#state.jid,
                          StateData#state.users,
                          Message,
                          ?NS_MUCSUB_NODES_CONFIG,
                          StateData).
 
+-spec remove_nonmembers(state()) -> state().
 remove_nonmembers(StateData) ->
     lists:foldl(fun ({_LJID, #user{jid = JID}}, SD) ->
                        Affiliation = get_affiliation(JID, SD),
                        case Affiliation of
                          none ->
                              catch send_kickban_presence(<<"">>, JID, <<"">>,
-                                                         <<"322">>, SD),
+                                                         322, SD),
                              set_role(JID, none, SD);
                          _ -> SD
                        end
                end,
                StateData, (?DICT):to_list(StateData#state.users)).
 
+-spec set_opts([{atom(), any()}], state()) -> state().
 set_opts([], StateData) -> StateData;
 set_opts([{Opt, Val} | Opts], StateData) ->
     NSD = case Opt of
@@ -4342,7 +3767,7 @@ set_opts([{Opt, Val} | Opts], StateData) ->
 
 -define(MAKE_CONFIG_OPT(Opt), {Opt, Config#config.Opt}).
 
-
+-spec make_opts(state()) -> [{atom(), any()}].
 make_opts(StateData) ->
     Config = StateData#state.config,
     Subscribers = (?DICT):fold(
@@ -4381,243 +3806,221 @@ make_opts(StateData) ->
      {subject_author, StateData#state.subject_author},
      {subscribers, Subscribers}].
 
+-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
 destroy_room(DEl, StateData) ->
-    lists:foreach(fun ({_LJID, Info}) ->
-                         Nick = Info#user.nick,
-                         ItemAttrs = [{<<"affiliation">>, <<"none">>},
-                                      {<<"role">>, <<"none">>}],
-                         Packet = #xmlel{name = <<"presence">>,
-                                         attrs =
-                                             [{<<"type">>, <<"unavailable">>}],
-                                         children =
-                                             [#xmlel{name = <<"x">>,
-                                                     attrs =
-                                                         [{<<"xmlns">>,
-                                                           ?NS_MUC_USER}],
-                                                     children =
-                                                         [#xmlel{name =
-                                                                     <<"item">>,
-                                                                 attrs =
-                                                                     ItemAttrs,
-                                                                 children =
-                                                                     []},
-                                                          DEl]}]},
-                         send_wrapped(jid:replace_resource(StateData#state.jid,
-                                                           Nick),
-                                      Info#user.jid, Packet,
-                                      ?NS_MUCSUB_NODES_CONFIG, StateData)
-                 end,
-                 (?DICT):to_list(StateData#state.users)),
+    Destroy = DEl#muc_destroy{xmlns = ?NS_MUC_USER},
+    lists:foreach(
+      fun({_LJID, Info}) ->
+             Nick = Info#user.nick,
+             Item = #muc_item{affiliation = none,
+                              role = none},
+             Packet = #presence{
+                         type = unavailable,
+                         sub_els = [#muc_user{items = [Item],
+                                              destroy = Destroy}]},
+             send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
+                          Info#user.jid, Packet,
+                          ?NS_MUCSUB_NODES_CONFIG, StateData)
+      end,
+      (?DICT):to_list(StateData#state.users)),
     case (StateData#state.config)#config.persistent of
       true ->
          mod_muc:forget_room(StateData#state.server_host,
                              StateData#state.host, StateData#state.room);
       false -> ok
     end,
-    {result, [], stop}.
+    {result, undefined, stop}.
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Disco
 
--define(FEATURE(Var),
-       #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Var}],
-              children = []}).
-
 -define(CONFIG_OPT_TO_FEATURE(Opt, Fiftrue, Fiffalse),
        case Opt of
-         true -> ?FEATURE(Fiftrue);
-         false -> ?FEATURE(Fiffalse)
+         true -> Fiftrue;
+         false -> Fiffalse
        end).
 
-process_iq_disco_info(_From, set, Lang, _StateData) ->
+-spec process_iq_disco_info(jid(), iq(), state()) ->
+                                  {result, disco_info()} | {error, error()}.
+process_iq_disco_info(_From, #iq{type = set, lang = Lang}, _StateData) ->
     Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-    {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_info(_From, get, Lang, StateData) ->
+    {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_info(_From, #iq{type = get, lang = Lang}, StateData) ->
     Config = StateData#state.config,
-    {result,
-     [#xmlel{name = <<"identity">>,
-            attrs =
-                [{<<"category">>, <<"conference">>},
-                 {<<"type">>, <<"text">>},
-                 {<<"name">>, get_title(StateData)}],
-            children = []},
-      #xmlel{name = <<"feature">>,
-            attrs = [{<<"var">>, ?NS_VCARD}], children = []},
-      #xmlel{name = <<"feature">>,
-            attrs = [{<<"var">>, ?NS_MUC}], children = []},
-      ?CONFIG_OPT_TO_FEATURE((Config#config.public),
-                            <<"muc_public">>, <<"muc_hidden">>),
-      ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
-                            <<"muc_persistent">>, <<"muc_temporary">>),
-      ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
-                            <<"muc_membersonly">>, <<"muc_open">>),
-      ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
-                            <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
-      ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
-                            <<"muc_moderated">>, <<"muc_unmoderated">>),
-      ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
-                            <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
-       ++ case Config#config.allow_subscription of
-             true -> [?FEATURE(?NS_MUCSUB)];
-             false -> []
-         end
-       ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
-               Config#config.mam} of
-           {true, true} ->
-               [?FEATURE(?NS_MAM_TMP),
-                ?FEATURE(?NS_MAM_0),
-                ?FEATURE(?NS_MAM_1)];
-           _ ->
-               []
-         end
-       ++ iq_disco_info_extras(Lang, StateData),
-     StateData}.
-
--define(RFIELDT(Type, Var, Val),
-       #xmlel{name = <<"field">>,
-              attrs = [{<<"type">>, Type}, {<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, Val}]}]}).
-
--define(RFIELD(Label, Var, Val),
-       #xmlel{name = <<"field">>,
-              attrs =
-                  [{<<"label">>, translate:translate(Lang, Label)},
-                   {<<"var">>, Var}],
-              children =
-                  [#xmlel{name = <<"value">>, attrs = [],
-                          children = [{xmlcdata, Val}]}]}).
-
+    Feats = [?NS_VCARD, ?NS_MUC,
+            ?CONFIG_OPT_TO_FEATURE((Config#config.public),
+                                   <<"muc_public">>, <<"muc_hidden">>),
+            ?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
+                                   <<"muc_persistent">>, <<"muc_temporary">>),
+            ?CONFIG_OPT_TO_FEATURE((Config#config.members_only),
+                                   <<"muc_membersonly">>, <<"muc_open">>),
+            ?CONFIG_OPT_TO_FEATURE((Config#config.anonymous),
+                                   <<"muc_semianonymous">>, <<"muc_nonanonymous">>),
+            ?CONFIG_OPT_TO_FEATURE((Config#config.moderated),
+                                   <<"muc_moderated">>, <<"muc_unmoderated">>),
+            ?CONFIG_OPT_TO_FEATURE((Config#config.password_protected),
+                                   <<"muc_passwordprotected">>, <<"muc_unsecured">>)]
+       ++ case Config#config.allow_subscription of
+              true -> [?NS_MUCSUB];
+              false -> []
+          end
+       ++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
+                Config#config.mam} of
+              {true, true} ->
+                  [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
+              _ ->
+                  []
+          end,
+    {result, #disco_info{xdata = [iq_disco_info_extras(Lang, StateData)],
+                        identities = [#identity{category = <<"conference">>,
+                                                type = <<"text">>,
+                                                name = get_title(StateData)}],
+                        features = Feats}}.
+
+-spec mk_rfieldt('boolean' | 'fixed' | 'hidden' |
+                'jid-multi' | 'jid-single' | 'list-multi' |
+                'list-single' | 'text-multi' | 'text-private' |
+                'text-single', binary(), binary()) -> xdata_field().
+mk_rfieldt(Type, Var, Val) ->
+    #xdata_field{type = Type, var = Var, values = [Val]}.
+
+-spec mk_rfield(binary(), binary(), binary(), binary()) -> xdata_field().
+mk_rfield(Label, Var, Val, Lang) ->
+    #xdata_field{type = 'text-single',
+                label = translate:translate(Lang, Label),
+                var = Var,
+                values = [Val]}.
+
+-spec iq_disco_info_extras(binary(), state()) -> xdata().
 iq_disco_info_extras(Lang, StateData) ->
     Len = (?DICT):size(StateData#state.users),
-    RoomDescription =
-       (StateData#state.config)#config.description,
-    [#xmlel{name = <<"x">>,
-           attrs =
-               [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
-           children =
-               [?RFIELDT(<<"hidden">>, <<"FORM_TYPE">>,
-                         <<"http://jabber.org/protocol/muc#roominfo">>),
-                ?RFIELD(<<"Room description">>,
-                        <<"muc#roominfo_description">>, RoomDescription),
-                ?RFIELD(<<"Number of occupants">>,
-                        <<"muc#roominfo_occupants">>,
-                        (iolist_to_binary(integer_to_list(Len))))]}].
-
-process_iq_disco_items(_From, set, Lang, _StateData) ->
+    RoomDescription = (StateData#state.config)#config.description,
+    #xdata{type = result,
+          fields = [mk_rfieldt(hidden, <<"FORM_TYPE">>,
+                               "http://jabber.org/protocol/muc#roominfo"),
+                    mk_rfield(<<"Room description">>,
+                              <<"muc#roominfo_description">>,
+                              RoomDescription, Lang),
+                    mk_rfield(<<"Number of occupants">>,
+                              <<"muc#roominfo_occupants">>,
+                              integer_to_binary(Len), Lang)]}.
+
+-spec process_iq_disco_items(jid(), iq(), state()) ->
+                                   {error, error()} | {result, disco_items()}.
+process_iq_disco_items(_From, #iq{type = set, lang = Lang}, _StateData) ->
     Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
-    {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_disco_items(From, get, Lang, StateData) ->
+    {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_disco_items(From, #iq{type = get, lang = Lang}, StateData) ->
     case (StateData#state.config)#config.public_list of
       true ->
-         {result, get_mucroom_disco_items(StateData), StateData};
+         {result, get_mucroom_disco_items(StateData)};
       _ ->
          case is_occupant_or_admin(From, StateData) of
            true ->
-               {result, get_mucroom_disco_items(StateData), StateData};
+               {result, get_mucroom_disco_items(StateData)};
            _ ->
                Txt = <<"Only occupants or administrators can perform this query">>,
-               {error, ?ERRT_FORBIDDEN(Lang, Txt)}
+               {error, xmpp:err_forbidden(Txt, Lang)}
          end
     end.
 
-process_iq_captcha(_From, get, Lang, _SubEl,
-                  _StateData) ->
+-spec process_iq_captcha(jid(), iq(), state()) -> {error, error()} |
+                                                 {result, undefined}.
+process_iq_captcha(_From, #iq{type = get, lang = Lang}, _StateData) ->
     Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
-    {error, ?ERRT_NOT_ALLOWED(Lang, Txt)};
-process_iq_captcha(_From, set, Lang, SubEl,
-                  StateData) ->
+    {error, xmpp:err_not_allowed(Txt, Lang)};
+process_iq_captcha(_From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+                  _StateData) ->
     case ejabberd_captcha:process_reply(SubEl) of
-      ok -> {result, [], StateData};
+      ok -> {result, undefined};
       {error, malformed} ->
            Txt = <<"Incorrect CAPTCHA submit">>,
-           {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
+           {error, xmpp:err_bad_request(Txt, Lang)};
       _ ->
            Txt = <<"The CAPTCHA verification has failed">>,
-           {error, ?ERRT_NOT_ALLOWED(Lang, Txt)}
+           {error, xmpp:err_not_allowed(Txt, Lang)}
     end.
 
-process_iq_vcard(_From, get, _Lang, _SubEl, StateData) ->
+-spec process_iq_vcard(jid(), iq(), state()) ->
+                             {result, vcard_temp() | xmlel()} |
+                             {result, undefined, state()} |
+                             {error, error()}.
+process_iq_vcard(_From, #iq{type = get}, StateData) ->
     #state{config = #config{vcard = VCardRaw}} = StateData,
     case fxml_stream:parse_element(VCardRaw) of
-       #xmlel{children = VCardEls} ->
-           {result, VCardEls, StateData};
+       #xmlel{} = VCard ->
+           {result, VCard};
        {error, _} ->
-           {result, [], StateData}
+           {result, #vcard_temp{}}
     end;
-process_iq_vcard(From, set, Lang, SubEl, StateData) ->
+process_iq_vcard(From, #iq{type = set, lang = Lang, sub_els = [SubEl]},
+                StateData) ->
     case get_affiliation(From, StateData) of
        owner ->
-           VCardRaw = fxml:element_to_binary(SubEl),
+           VCardRaw = fxml:element_to_binary(xmpp:encode(SubEl)),
            Config = StateData#state.config,
            NewConfig = Config#config{vcard = VCardRaw},
            change_config(NewConfig, StateData);
        _ ->
            ErrText = <<"Owner privileges required">>,
-           {error, ?ERRT_FORBIDDEN(Lang, ErrText)}
+           {error, xmpp:err_forbidden(ErrText, Lang)}
     end.
 
-process_iq_mucsub(From, Packet,
+-spec process_iq_mucsub(jid(), iq(), state()) ->
+                              {error, error()} |
+                              {result, undefined | muc_subscribe(), state()} |
+                              {ignore, state()}.
+process_iq_mucsub(_From, #iq{type = set, lang = Lang,
+                            sub_els = [#muc_subscribe{}]},
+                 #state{config = #config{allow_subscription = false}}) ->
+    {error, xmpp:err_not_allowed(<<"Subscriptions are not allowed">>, Lang)};
+process_iq_mucsub(From,
                  #iq{type = set, lang = Lang,
-                     sub_el = #xmlel{name = <<"subscribe">>} = SubEl},
-                 #state{config = Config} = StateData) ->
-    case fxml:get_tag_attr_s(<<"nick">>, SubEl) of
-       <<"">> ->
-           Err = ?ERRT_BAD_REQUEST(Lang, <<"Missing 'nick' attribute">>),
-           {error, Err};
-       Nick when Config#config.allow_subscription ->
-           LJID = jid:tolower(From),
-           case (?DICT):find(LJID, StateData#state.users) of
-               {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
-                   Nodes = get_subscription_nodes(Packet),
-                   case {nick_collision(From, Nick, StateData),
-                         mod_muc:can_use_nick(StateData#state.server_host,
-                                              StateData#state.host,
-                                              From, Nick)} of
-                       {true, _} ->
-                           ErrText = <<"That nickname is already in use by another occupant">>,
-                           {error, ?ERRT_CONFLICT(Lang, ErrText)};
-                       {_, false} ->
-                           ErrText = <<"That nickname is registered by another person">>,
-                           {error, ?ERRT_CONFLICT(Lang, ErrText)};
-                       _ ->
-                           NewStateData = add_online_user(
-                                            From, Nick, Role, true, Nodes, StateData),
-                           {result, subscription_nodes_to_events(Nodes), NewStateData}
-                   end;
-               {ok, #user{role = Role}} ->
-                   Nodes = get_subscription_nodes(Packet),
+                     sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
+                 StateData) ->
+    LJID = jid:tolower(From),
+    case (?DICT):find(LJID, StateData#state.users) of
+       {ok, #user{role = Role, nick = Nick1}} when Nick1 /= Nick ->
+           Nodes = get_subscription_nodes(Packet),
+           case {nick_collision(From, Nick, StateData),
+                 mod_muc:can_use_nick(StateData#state.server_host,
+                                      StateData#state.host,
+                                      From, Nick)} of
+               {true, _} ->
+                   ErrText = <<"That nickname is already in use by another occupant">>,
+                   {error, xmpp:err_conflict(ErrText, Lang)};
+               {_, false} ->
+                   ErrText = <<"That nickname is registered by another person">>,
+                   {error, xmpp:err_conflict(ErrText, Lang)};
+               _ ->
                    NewStateData = add_online_user(
                                     From, Nick, Role, true, Nodes, StateData),
-                   {result, subscription_nodes_to_events(Nodes), NewStateData};
-               error ->
-                   add_new_user(From, Nick, Packet, StateData)
+                   {result, subscribe_result(Packet), NewStateData}
            end;
-       _ ->
-           Err = ?ERRT_NOT_ALLOWED(Lang, <<"Subscriptions are not allowed">>),
-           {error, Err}
+       {ok, #user{role = Role}} ->
+           Nodes = get_subscription_nodes(Packet),
+           NewStateData = add_online_user(
+                            From, Nick, Role, true, Nodes, StateData),
+           {result, subscribe_result(Packet), NewStateData};
+       error ->
+           add_new_user(From, Nick, Packet, StateData)
     end;
-process_iq_mucsub(From, _Packet,
-                 #iq{type = set,
-                     sub_el = #xmlel{name = <<"unsubscribe">>}},
+process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
                  StateData) ->
     LJID = jid:tolower(From),
     case ?DICT:find(LJID, StateData#state.users) of
        {ok, #user{is_subscriber = true} = User} ->
            NewStateData = remove_subscription(From, User, StateData),
            store_room(NewStateData),
-           {result, [], NewStateData};
+           {result, undefined, NewStateData};
        _ ->
-           {result, [], StateData}
+           {result, undefined, StateData}
     end;
-process_iq_mucsub(_From, _Packet, #iq{type = set, lang = Lang}, _StateData) ->
-    Txt = <<"Unrecognized subscription command">>,
-    {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
-process_iq_mucsub(_From, _Packet, #iq{type = get, lang = Lang}, _StateData) ->
+process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
     Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
-    {error, ?ERRT_BAD_REQUEST(Lang, Txt)}.
+    {error, xmpp:err_bad_request(Txt, Lang)}.
 
+-spec remove_subscription(jid(), #user{}, state()) -> state().
 remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
     case User#user.last_presence of
        undefined ->
@@ -4631,6 +4034,7 @@ remove_subscription(JID, #user{is_subscriber = true} = User, StateData) ->
 remove_subscription(_JID, #user{}, StateData) ->
     StateData.
 
+-spec remove_subscriptions(state()) -> state().
 remove_subscriptions(StateData) ->
     if not (StateData#state.config)#config.allow_subscription ->
            dict:fold(
@@ -4641,41 +4045,32 @@ remove_subscriptions(StateData) ->
            StateData
     end.
 
-get_subscription_nodes(#xmlel{name = <<"iq">>} = Packet) ->
-    case fxml:get_subtag_with_xmlns(Packet, <<"subscribe">>, ?NS_MUCSUB) of
-       #xmlel{children = Els} ->
-           lists:flatmap(
-             fun(#xmlel{name = <<"event">>, attrs = Attrs}) ->
-                     Node = fxml:get_attr_s(<<"node">>, Attrs),
-                     case lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
-                                              ?NS_MUCSUB_NODES_MESSAGES,
-                                              ?NS_MUCSUB_NODES_AFFILIATIONS,
-                                              ?NS_MUCSUB_NODES_SUBJECT,
-                                              ?NS_MUCSUB_NODES_CONFIG,
-                                              ?NS_MUCSUB_NODES_PARTICIPANTS]) of
-                         true ->
-                             [Node];
-                         false ->
-                             []
-                     end;
-                (_) ->
-                     []
-             end, Els);
-       false ->
-           []
-    end;
+-spec get_subscription_nodes(iq()) -> [binary()].
+get_subscription_nodes(#iq{sub_els = [#muc_subscribe{events = Nodes}]}) ->
+    lists:filter(
+      fun(Node) ->
+             lists:member(Node, [?NS_MUCSUB_NODES_PRESENCE,
+                                 ?NS_MUCSUB_NODES_MESSAGES,
+                                 ?NS_MUCSUB_NODES_AFFILIATIONS,
+                                 ?NS_MUCSUB_NODES_SUBJECT,
+                                 ?NS_MUCSUB_NODES_CONFIG,
+                                 ?NS_MUCSUB_NODES_PARTICIPANTS])
+      end, Nodes);
 get_subscription_nodes(_) ->
     [].
 
-subscription_nodes_to_events(Nodes) ->
-    [#xmlel{name = <<"event">>, attrs = [{<<"node">>, Node}]} || Node <- Nodes].
+-spec subscribe_result(iq()) -> muc_subscribe().
+subscribe_result(#iq{sub_els = [#muc_subscribe{nick = Nick}]} = Packet) ->
+    #muc_subscribe{nick = Nick, events = get_subscription_nodes(Packet)}.
 
+-spec get_title(state()) -> binary().
 get_title(StateData) ->
     case (StateData#state.config)#config.title of
       <<"">> -> StateData#state.room;
       Name -> Name
     end.
 
+-spec get_roomdesc_reply(jid(), state(), binary()) -> {item, binary()} | false.
 get_roomdesc_reply(JID, StateData, Tail) ->
     IsOccupantOrAdmin = is_occupant_or_admin(JID,
                                             StateData),
@@ -4689,352 +4084,215 @@ get_roomdesc_reply(JID, StateData, Tail) ->
        true -> false
     end.
 
+-spec get_roomdesc_tail(state(), binary()) -> binary().
 get_roomdesc_tail(StateData, Lang) ->
     Desc = case (StateData#state.config)#config.public of
             true -> <<"">>;
             _ -> translate:translate(Lang, <<"private, ">>)
           end,
-    Len = (?DICT):fold(fun (_, _, Acc) -> Acc + 1 end, 0,
-                      StateData#state.users),
+    Len = (?DICT):size(StateData#state.users),
     <<" (", Desc/binary,
       (iolist_to_binary(integer_to_list(Len)))/binary, ")">>.
 
+-spec get_mucroom_disco_items(state()) -> disco_items().
 get_mucroom_disco_items(StateData) ->
-    lists:map(fun ({_LJID, Info}) ->
+    Items = lists:map(
+             fun({_LJID, Info}) ->
                      Nick = Info#user.nick,
-                     #xmlel{name = <<"item">>,
-                            attrs =
-                                [{<<"jid">>,
-                                  jid:to_string({StateData#state.room,
-                                                      StateData#state.host,
-                                                      Nick})},
-                                 {<<"name">>, Nick}],
-                            children = []}
+                     #disco_item{jid = jid:make(StateData#state.room,
+                                                StateData#state.host,
+                                                Nick),
+                                 name = Nick}
              end,
-             (?DICT):to_list(StateData#state.users)).
+             (?DICT):to_list(StateData#state.users)),
+    #disco_items{items = Items}.
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Voice request support
 
-is_voice_request(Els) ->
-    lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
-                        El,
-                    false) ->
-                       case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-                         ?NS_XDATA ->
-                             case jlib:parse_xdata_submit(El) of
-                               [_ | _] = Fields ->
-                                   case {lists:keysearch(<<"FORM_TYPE">>, 1,
-                                                         Fields),
-                                         lists:keysearch(<<"muc#role">>, 1,
-                                                         Fields)}
-                                       of
-                                     {{value,
-                                       {_,
-                                        [<<"http://jabber.org/protocol/muc#request">>]}},
-                                      {value, {_, [<<"participant">>]}}} ->
-                                         true;
-                                     _ -> false
-                                   end;
-                               _ -> false
-                             end;
-                         _ -> false
-                       end;
-                   (_, Acc) -> Acc
-               end,
-               false, Els).
+-spec is_voice_request(message()) -> boolean().
+is_voice_request(Packet) ->
+    Els = xmpp:get_els(Packet),
+    lists:any(
+      fun(#xdata{} = X) ->
+             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#role">>, X)} of
+                 {[<<"http://jabber.org/protocol/muc#request">>],
+                  [<<"participant">>]} ->
+                     true;
+                 _ ->
+                     false
+             end;
+        (_) ->
+             false
+      end, Els).
 
+-spec prepare_request_form(jid(), binary(), binary()) -> message().
 prepare_request_form(Requester, Nick, Lang) ->
-    #xmlel{name = <<"message">>,
-          attrs = [{<<"type">>, <<"normal">>}],
-          children =
-              [#xmlel{name = <<"x">>,
-                      attrs =
-                          [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
-                      children =
-                          [#xmlel{name = <<"title">>, attrs = [],
-                                  children =
-                                      [{xmlcdata,
-                                        translate:translate(Lang,
-                                                            <<"Voice request">>)}]},
-                           #xmlel{name = <<"instructions">>, attrs = [],
-                                  children =
-                                      [{xmlcdata,
-                                        translate:translate(Lang,
-                                                            <<"Either approve or decline the voice "
-                                                              "request.">>)}]},
-                           #xmlel{name = <<"field">>,
-                                  attrs =
-                                      [{<<"var">>, <<"FORM_TYPE">>},
-                                       {<<"type">>, <<"hidden">>}],
-                                  children =
-                                      [#xmlel{name = <<"value">>, attrs = [],
-                                              children =
-                                                  [{xmlcdata,
-                                                    <<"http://jabber.org/protocol/muc#request">>}]}]},
-                           #xmlel{name = <<"field">>,
-                                  attrs =
-                                      [{<<"var">>, <<"muc#role">>},
-                                       {<<"type">>, <<"hidden">>}],
-                                  children =
-                                      [#xmlel{name = <<"value">>, attrs = [],
-                                              children =
-                                                  [{xmlcdata,
-                                                    <<"participant">>}]}]},
-                           ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
-                                         (jid:to_string(Requester))),
-                           ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>,
-                                         Nick),
-                           ?BOOLXFIELD(<<"Grant voice to this person?">>,
-                                       <<"muc#request_allow">>,
-                                       (jlib:binary_to_atom(<<"false">>)))]}]}.
-
-send_voice_request(From, StateData) ->
+    Title = translate:translate(Lang, <<"Voice request">>),
+    Instruction = translate:translate(
+                   Lang, <<"Either approve or decline the voice request.">>),
+    Fs = [#xdata_field{var = <<"FORM_TYPE">>,
+                      type = hidden,
+                      values = [<<"http://jabber.org/protocol/muc#request">>]},
+         #xdata_field{var = <<"muc#role">>,
+                      type = hidden,
+                      values = [<<"participant">>]},
+         ?STRINGXFIELD(<<"User JID">>, <<"muc#jid">>,
+                       jid:to_string(Requester)),
+         ?STRINGXFIELD(<<"Nickname">>, <<"muc#roomnick">>, Nick),
+         ?BOOLXFIELD(<<"Grant voice to this person?">>,
+                     <<"muc#request_allow">>, false)],
+    #message{type = normal,
+            sub_els = [#xdata{type = form,
+                              title = Title,
+                              instructions = [Instruction],
+                              fields = Fs}]}.
+
+-spec send_voice_request(jid(), binary(), state()) -> ok.
+send_voice_request(From, Lang, StateData) ->
     Moderators = search_role(moderator, StateData),
     FromNick = find_nick_by_jid(From, StateData),
     lists:foreach(fun ({_, User}) ->
                          ejabberd_router:route(
                            StateData#state.jid, User#user.jid,
-                           prepare_request_form(From, FromNick, <<"">>))
+                           prepare_request_form(From, FromNick, Lang))
                  end,
                  Moderators).
 
-is_voice_approvement(Els) ->
-    lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
-                        El,
-                    false) ->
-                       case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-                         ?NS_XDATA ->
-                             case jlib:parse_xdata_submit(El) of
-                               [_ | _] = Fs ->
-                                   case {lists:keysearch(<<"FORM_TYPE">>, 1,
-                                                         Fs),
-                                         lists:keysearch(<<"muc#role">>, 1,
-                                                         Fs),
-                                         lists:keysearch(<<"muc#request_allow">>,
-                                                         1, Fs)}
-                                       of
-                                     {{value,
-                                       {_,
-                                        [<<"http://jabber.org/protocol/muc#request">>]}},
-                                      {value, {_, [<<"participant">>]}},
-                                      {value, {_, [Flag]}}}
-                                         when Flag == <<"true">>;
-                                              Flag == <<"1">> ->
-                                         true;
-                                     _ -> false
-                                   end;
-                               _ -> false
-                             end;
-                         _ -> false
-                       end;
-                   (_, Acc) -> Acc
-               end,
-               false, Els).
-
-extract_jid_from_voice_approvement(Els) ->
-    lists:foldl(fun (#xmlel{name = <<"x">>} = El, error) ->
-                       Fields = case jlib:parse_xdata_submit(El) of
-                                  invalid -> [];
-                                  Res -> Res
-                                end,
-                       lists:foldl(fun ({<<"muc#jid">>, [JIDStr]}, error) ->
-                                           case jid:from_string(JIDStr) of
-                                             error -> error;
-                                             J -> {ok, J}
-                                           end;
-                                       (_, Acc) -> Acc
-                                   end,
-                                   error, Fields);
-                   (_, Acc) -> Acc
-               end,
-               error, Els).
+-spec is_voice_approvement(message()) -> boolean().
+is_voice_approvement(Packet) ->
+    Els = xmpp:get_els(Packet),
+    lists:any(
+      fun(#xdata{} = X) ->
+             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#role">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#request_allow">>, X)} of
+                 {[<<"http://jabber.org/protocol/muc#request">>],
+                  [<<"participant">>], [Flag]} when Flag == <<"true">>;
+                                                    Flag == <<"1">> ->
+                     true;
+                 _ ->
+                     false
+             end;
+        (_) ->
+             false
+      end, Els).
+
+-spec extract_jid_from_voice_approvement(message()) -> jid() | error.
+extract_jid_from_voice_approvement(Packet) ->
+    Els = xmpp:get_els(Packet),
+    lists:foldl(
+      fun(#xdata{} = X, error) ->
+             case {xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#role">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#request_allow">>, X),
+                   xmpp_util:get_xdata_values(<<"muc#jid">>, X)} of
+                 {[<<"http://jabber.org/protocol/muc#request">>],
+                  [<<"participant">>], [Flag], [J]} when Flag == <<"true">>;
+                                                         Flag == <<"1">> ->
+                     jid:from_string(J);
+                 _ ->
+                     error
+             end;
+        (_, Acc) ->
+             Acc
+      end, error, Els).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Invitation support
 
-is_invitation(Els) ->
-    lists:foldl(fun (#xmlel{name = <<"x">>, attrs = Attrs} =
-                        El,
-                    false) ->
-                       case fxml:get_attr_s(<<"xmlns">>, Attrs) of
-                         ?NS_MUC_USER ->
-                             case fxml:get_subtag(El, <<"invite">>) of
-                               false -> false;
-                               _ -> true
-                             end;
-                         _ -> false
-                       end;
-                   (_, Acc) -> Acc
-               end,
-               false, Els).
-
-check_invitation(From, Packet, Lang, StateData) ->
+-spec is_invitation(message()) -> boolean().
+is_invitation(Packet) ->
+    Els = xmpp:get_els(Packet),
+    lists:any(
+      fun(#muc_user{invites = [_|_]}) -> true;
+        (_) -> false
+      end, Els).
+
+-spec check_invitation(jid(), message(), state()) -> {error, error()} | jid().
+check_invitation(From, Packet, StateData) ->
+    Lang = xmpp:get_lang(Packet),
     FAffiliation = get_affiliation(From, StateData),
-    CanInvite =
-       (StateData#state.config)#config.allow_user_invites
-         orelse
-         FAffiliation == admin orelse FAffiliation == owner,
-    InviteEl = case fxml:get_subtag_with_xmlns(Packet, <<"x">>, ?NS_MUC_USER) of
-                  false ->
-                      Txt1 = <<"No 'x' element found">>,
-                      throw({error, ?ERRT_BAD_REQUEST(Lang, Txt1)});
-                  XEl ->
-                      case fxml:get_subtag(XEl, <<"invite">>) of
-                          false ->
-                              Txt2 = <<"No 'invite' element found">>,
-                              throw({error, ?ERRT_BAD_REQUEST(Lang, Txt2)});
-                          InviteEl1 ->
-                              InviteEl1
-                      end
-              end,
-    JID = case
-           jid:from_string(fxml:get_tag_attr_s(<<"to">>,
-                                                 InviteEl))
-             of
-           error ->
-                 Txt = <<"Incorrect value of 'to' attribute">>,
-                 throw({error, ?ERRT_JID_MALFORMED(Lang, Txt)});
-           JID1 -> JID1
-         end,
+    CanInvite = (StateData#state.config)#config.allow_user_invites
+       orelse
+       FAffiliation == admin orelse FAffiliation == owner,
     case CanInvite of
-      false ->
-         Txt3 = <<"Invitations are not allowed in this conference">>,
-         throw({error, ?ERRT_NOT_ALLOWED(Lang, Txt3)});
-      true ->
-         Reason = fxml:get_path_s(InviteEl,
-                                 [{elem, <<"reason">>}, cdata]),
-         ContinueEl = case fxml:get_path_s(InviteEl,
-                                          [{elem, <<"continue">>}])
-                          of
-                        <<>> -> [];
-                        Continue1 -> [Continue1]
-                      end,
-         IEl = [#xmlel{name = <<"invite">>,
-                       attrs = [{<<"from">>, jid:to_string(From)}],
-                       children =
-                           [#xmlel{name = <<"reason">>, attrs = [],
-                                   children = [{xmlcdata, Reason}]}]
-                             ++ ContinueEl}],
-         PasswdEl = case
-                      (StateData#state.config)#config.password_protected
-                        of
-                      true ->
-                          [#xmlel{name = <<"password">>, attrs = [],
-                                  children =
-                                      [{xmlcdata,
-                                        (StateData#state.config)#config.password}]}];
-                      _ -> []
-                    end,
-         Body = #xmlel{name = <<"body">>, attrs = [],
-                       children =
-                           [{xmlcdata,
-                             iolist_to_binary(
-                                [io_lib:format(
-                                  translate:translate(
-                                    Lang,
-                                    <<"~s invites you to the room ~s">>),
-                                  [jid:to_string(From),
-                                   jid:to_string({StateData#state.room,
-                                                       StateData#state.host,
-                                                       <<"">>})]),
-                               case
-                                 (StateData#state.config)#config.password_protected
-                                   of
+       false ->
+           Txt = <<"Invitations are not allowed in this conference">>,
+           {error, xmpp:err_not_allowed(Txt, Lang)};
+       true ->
+           case xmpp:get_subtag(Packet, #muc_user{}) of
+               #muc_user{invites = [#muc_invite{to = undefined}]} ->
+                   Txt = <<"No 'to' attribute found">>,
+                   {error, xmpp:err_bad_request(Txt, Lang)};
+               #muc_user{invites = [#muc_invite{to = JID, reason = Reason} = I]} ->
+                   Invite = I#muc_invite{to = undefined, from = From},
+                   Password = case (StateData#state.config)#config.password_protected of
+                                  true ->
+                                      (StateData#state.config)#config.password;
+                                  false ->
+                                      undefined
+                              end,
+                   XUser = #muc_user{password = Password, invites = [Invite]},
+                   XConference = #x_conference{jid = jid:make(StateData#state.room,
+                                                              StateData#state.host),
+                                               reason = Reason},
+                   Body = iolist_to_binary(
+                            [io_lib:format(
+                               translate:translate(
+                                 Lang,
+                                 <<"~s invites you to the room ~s">>),
+                               [jid:to_string(From),
+                                jid:to_string({StateData#state.room,
+                                               StateData#state.host,
+                                               <<"">>})]),
+                             case (StateData#state.config)#config.password_protected of
                                  true ->
                                      <<", ",
-                                       (translate:translate(Lang,
-                                                            <<"the password is">>))/binary,
+                                       (translate:translate(
+                                          Lang, <<"the password is">>))/binary,
                                        " '",
                                        ((StateData#state.config)#config.password)/binary,
                                        "'">>;
                                  _ -> <<"">>
-                               end
-                                 ,
-                                 case Reason of
-                                   <<"">> -> <<"">>;
-                                   _ -> <<" (", Reason/binary, ") ">>
-                                 end])}]},
-         Msg = #xmlel{name = <<"message">>,
-                      attrs = [{<<"type">>, <<"normal">>}],
-                      children =
-                          [#xmlel{name = <<"x">>,
-                                  attrs = [{<<"xmlns">>, ?NS_MUC_USER}],
-                                  children = IEl ++ PasswdEl},
-                           #xmlel{name = <<"x">>,
-                                  attrs =
-                                      [{<<"xmlns">>, ?NS_XCONFERENCE},
-                                       {<<"jid">>,
-                                        jid:to_string({StateData#state.room,
-                                                            StateData#state.host,
-                                                            <<"">>})}],
-                                  children = [{xmlcdata, Reason}]},
-                           Body]},
-         ejabberd_router:route(StateData#state.jid, JID, Msg),
-         JID
+                             end,
+                             case Reason of
+                                 <<"">> -> <<"">>;
+                                 _ -> <<" (", Reason/binary, ") ">>
+                             end]),
+                   Msg = #message{type = normal,
+                                  body = xmpp:mk_text(Body),
+                                  sub_els = [XUser, XConference]},
+                   ejabberd_router:route(StateData#state.jid, JID, Msg),
+                   JID;
+               #muc_user{invites = [_|_]} ->
+                   Txt = <<"Multiple <invite/> elements are not allowed">>,
+                   {error, xmpp:err_forbidden(Txt, Lang)};
+               _ ->
+                   Txt = <<"No <invite/> element found">>,
+                   {error, xmpp:err_bad_request(Txt, Lang)}
+           end
     end.
 
 %% Handle a message sent to the room by a non-participant.
 %% If it is a decline, send to the inviter.
 %% Otherwise, an error message is sent to the sender.
-handle_roommessage_from_nonparticipant(Packet, Lang,
-                                      StateData, From) ->
-    case catch check_decline_invitation(Packet) of
-      {true, Decline_data} ->
-         send_decline_invitation(Decline_data,
-                                 StateData#state.jid, From);
-      _ ->
-         send_error_only_occupants(Packet, Lang,
-                                   StateData#state.jid, From)
+-spec handle_roommessage_from_nonparticipant(message(), state(), jid()) -> ok.
+handle_roommessage_from_nonparticipant(Packet, StateData, From) ->
+    case xmpp:get_subtag(Packet, #muc_user{}) of
+       #muc_user{decline = #muc_decline{to = #jid{} = To} = Decline} = XUser ->
+           NewDecline = Decline#muc_decline{to = undefined, from = From},
+           NewXUser = XUser#muc_user{decline = NewDecline},
+           NewPacket = xmpp:set_subtag(Packet, NewXUser),
+           ejabberd_router:route(StateData#state.jid, To, NewPacket);
+       _ ->
+           ErrText = <<"Only occupants are allowed to send messages "
+                       "to the conference">>,
+           Err = xmpp:err_not_acceptable(ErrText, xmpp:get_lang(Packet)),
+           ejabberd_router:route_error(StateData#state.jid, From, Packet, Err)
     end.
 
-%% Check in the packet is a decline.
-%% If so, also returns the splitted packet.
-%% This function must be catched,
-%% because it crashes when the packet is not a decline message.
-check_decline_invitation(Packet) ->
-    #xmlel{name = <<"message">>} = Packet,
-    XEl = fxml:get_subtag(Packet, <<"x">>),
-    (?NS_MUC_USER) = fxml:get_tag_attr_s(<<"xmlns">>, XEl),
-    DEl = fxml:get_subtag(XEl, <<"decline">>),
-    ToString = fxml:get_tag_attr_s(<<"to">>, DEl),
-    ToJID = jid:from_string(ToString),
-    {true, {Packet, XEl, DEl, ToJID}}.
-
-%% Send the decline to the inviter user.
-%% The original stanza must be slightly modified.
-send_decline_invitation({Packet, XEl, DEl, ToJID},
-                       RoomJID, FromJID) ->
-    FromString =
-       jid:to_string(jid:remove_resource(FromJID)),
-    #xmlel{name = <<"decline">>, attrs = DAttrs,
-          children = DEls} =
-       DEl,
-    DAttrs2 = lists:keydelete(<<"to">>, 1, DAttrs),
-    DAttrs3 = [{<<"from">>, FromString} | DAttrs2],
-    DEl2 = #xmlel{name = <<"decline">>, attrs = DAttrs3,
-                 children = DEls},
-    XEl2 = replace_subelement(XEl, DEl2),
-    Packet2 = replace_subelement(Packet, XEl2),
-    ejabberd_router:route(RoomJID, ToJID, Packet2).
-
-%% Given an element and a new subelement,
-%% replace the instance of the subelement in element with the new subelement.
-replace_subelement(#xmlel{name = Name, attrs = Attrs,
-                         children = SubEls},
-                  NewSubEl) ->
-    {_, NameNewSubEl, _, _} = NewSubEl,
-    SubEls2 = lists:keyreplace(NameNewSubEl, 2, SubEls, NewSubEl),
-    #xmlel{name = Name, attrs = Attrs, children = SubEls2}.
-
-send_error_only_occupants(Packet, Lang, RoomJID, From) ->
-    ErrText =
-       <<"Only occupants are allowed to send messages "
-         "to the conference">>,
-    Err = jlib:make_error_reply(Packet,
-                               ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)),
-    ejabberd_router:route(RoomJID, From, Err).
-
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 % Logging
 
@@ -5055,6 +4313,7 @@ add_to_log(Type, Data, StateData) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Users number checking
 
+-spec tab_add_online_user(jid(), state()) -> ok.
 tab_add_online_user(JID, StateData) ->
     {LUser, LServer, LResource} = jid:tolower(JID),
     US = {LUser, LServer},
@@ -5062,8 +4321,10 @@ tab_add_online_user(JID, StateData) ->
     Host = StateData#state.host,
     catch ets:insert(muc_online_users,
                     #muc_online_users{us = US, resource = LResource,
-                                      room = Room, host = Host}).
+                                      room = Room, host = Host}),
+    ok.
 
+-spec tab_remove_online_user(jid(), state()) -> ok.
 tab_remove_online_user(JID, StateData) ->
     {LUser, LServer, LResource} = jid:tolower(JID),
     US = {LUser, LServer},
@@ -5071,8 +4332,10 @@ tab_remove_online_user(JID, StateData) ->
     Host = StateData#state.host,
     catch ets:delete_object(muc_online_users,
                            #muc_online_users{us = US, resource = LResource,
-                                             room = Room, host = Host}).
+                                             room = Room, host = Host}),
+    ok.
 
+-spec tab_count_user(jid()) -> non_neg_integer().
 tab_count_user(JID) ->
     {LUser, LServer, _} = jid:tolower(JID),
     US = {LUser, LServer},
@@ -5083,9 +4346,11 @@ tab_count_user(JID) ->
       _ -> 0
     end.
 
+-spec element_size(stanza()) -> non_neg_integer().
 element_size(El) ->
-    byte_size(fxml:element_to_binary(El)).
+    byte_size(fxml:element_to_binary(xmpp:encode(El))).
 
+-spec store_room(state()) -> ok.
 store_room(StateData) ->
     if (StateData#state.config)#config.persistent ->
            mod_muc:store_room(StateData#state.server_host,
@@ -5095,6 +4360,7 @@ store_room(StateData) ->
            ok
     end.
 
+-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
 send_wrapped(From, To, Packet, Node, State) ->
     LTo = jid:tolower(To),
     case ?DICT:find(LTo, State#state.users) of
@@ -5112,27 +4378,26 @@ send_wrapped(From, To, Packet, Node, State) ->
            ejabberd_router:route(From, To, Packet)
     end.
 
+-spec wrap(jid(), jid(), stanza(), binary()) -> message().
 wrap(From, To, Packet, Node) ->
-    Pkt1 = jlib:replace_from_to(From, To, Packet),
-    Pkt2 = #xmlel{attrs = Attrs} = jlib:remove_attr(<<"xmlns">>, Pkt1),
-    Pkt3 = Pkt2#xmlel{attrs = [{<<"xmlns">>, <<"jabber:client">>}|Attrs]},
-    Item = #xmlel{name = <<"item">>,
-                 attrs = [{<<"id">>, randoms:get_string()}],
-                 children = [Pkt3]},
-    Items = #xmlel{name = <<"items">>, attrs = [{<<"node">>, Node}],
-                  children = [Item]},
-    Event = #xmlel{name = <<"event">>,
-                  attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
-                  children = [Items]},
-    #xmlel{name = <<"message">>, children = [Event]}.
+    El = xmpp:encode(xmpp:set_from_to(Packet, From, To)),
+    #message{
+       sub_els = [#pubsub_event{
+                    items = [#pubsub_event_items{
+                                node = Node,
+                                items = [#pubsub_event_item{
+                                            id = randoms:get_string(),
+                                            xml_els = [El]}]}]}]}.
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Multicast
 
+-spec send_multiple(jid(), binary(), [#user{}], stanza()) -> ok.
 send_multiple(From, Server, Users, Packet) ->
     JIDs = [ User#user.jid || {_, User} <- ?DICT:to_list(Users)],
     ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet).
 
+-spec send_wrapped_multiple(jid(), [#user{}], stanza(), binary(), state()) -> ok.
 send_wrapped_multiple(From, Users, Packet, Node, State) ->
     lists:foreach(
       fun({_, #user{jid = To}}) ->
@@ -5141,10 +4406,6 @@ send_wrapped_multiple(From, Users, Packet, Node, State) ->
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Detect messange stanzas that don't have meaninful content
-
-has_body_or_subject(Packet) ->
-    [] /= lists:dropwhile(fun
-       (#xmlel{name = <<"body">>}) -> false;
-       (#xmlel{name = <<"subject">>}) -> false;
-       (_) -> true
-    end, Packet#xmlel.children).
+-spec has_body_or_subject(message()) -> boolean().
+has_body_or_subject(#message{body = Body, subject = Subj}) ->
+    Body /= [] orelse Subj /= [].
index 28d49bb3f94733fdbcfd7680cff84292dbd05080..e6d0fd7cde0a3d807ef16c0dd72efdff303e7260 100644 (file)
@@ -72,7 +72,7 @@ process_sm_iq(#iq{type = Type, lang = Lang,
     case filter_xmlels(Els0) of
        [] ->
            Txt = <<"No private data found in this query">>,
-           xmpp:make_error(IQ, xmpp:err_bad_format(Txt, Lang));
+           xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
        Data when Type == set ->
            set_data(LUser, LServer, Data),
            xmpp:make_iq_result(IQ);
index f17eefa21ad4ed4e529ec3a75d4186da0c42669a..369fb90c5daf902a908b6058a2f258c7c6518886 100644 (file)
         set_type/2, set_to/2, set_from/2, set_id/2,
         set_lang/2, set_error/2, set_els/2, set_from_to/3,
         format_error/1, is_stanza/1, set_subtag/2, get_subtag/2,
-        remove_subtag/2, has_subtag/2, decode_els/1, pp/1,
-        get_name/1, get_text/1, mk_text/1, mk_text/2]).
+        remove_subtag/2, has_subtag/2, decode_els/1, decode_els/2,
+        pp/1, get_name/1, get_text/1, mk_text/1, mk_text/2]).
 
 %% XMPP errors
 -export([err_bad_request/0, err_bad_request/2,
-        err_bad_format/0, err_bad_format/2,
-        err_not_allowed/0, err_not_allowed/2,
-        err_conflict/0, err_conflict/2,
-        err_forbidden/0, err_forbidden/2,
-        err_not_acceptable/0, err_not_acceptable/2,
-        err_internal_server_error/0, err_internal_server_error/2,
-        err_service_unavailable/0, err_service_unavailable/2,
-        err_item_not_found/0, err_item_not_found/2,
-        err_jid_malformed/0, err_jid_malformed/2,
-        err_not_authorized/0, err_not_authorized/2,
-        err_feature_not_implemented/0, err_feature_not_implemented/2]).
+         err_conflict/0, err_conflict/2,
+         err_feature_not_implemented/0, err_feature_not_implemented/2,
+         err_forbidden/0, err_forbidden/2,
+         err_gone/0, err_gone/2,
+         err_internal_server_error/0, err_internal_server_error/2,
+         err_item_not_found/0, err_item_not_found/2,
+         err_jid_malformed/0, err_jid_malformed/2,
+         err_not_acceptable/0, err_not_acceptable/2,
+         err_not_allowed/0, err_not_allowed/2,
+         err_not_authorized/0, err_not_authorized/2,
+        err_payment_required/0, err_payment_required/2,
+         err_policy_violation/0, err_policy_violation/2,
+         err_recipient_unavailable/0, err_recipient_unavailable/2,
+         err_redirect/0, err_redirect/2,
+         err_registration_required/0, err_registration_required/2,
+         err_remote_server_not_found/0, err_remote_server_not_found/2,
+         err_remote_server_timeout/0, err_remote_server_timeout/2,
+         err_resource_constraint/0, err_resource_constraint/2,
+         err_service_unavailable/0, err_service_unavailable/2,
+         err_subscription_required/0, err_subscription_required/2,
+         err_undefined_condition/0, err_undefined_condition/2,
+         err_unexpected_request/0, err_unexpected_request/2]).
 
 %% XMPP stream errors
 -export([serr_bad_format/0, serr_bad_format/2,
@@ -246,9 +257,12 @@ decode(Pkt, _Opts) ->
                (message()) -> message();
                (presence()) -> presence().
 decode_els(Stanza) ->
+    decode_els(Stanza, fun xmpp_codec:is_known_tag/1).
+
+decode_els(Stanza, MatchFun) ->
     Els = lists:map(
            fun(#xmlel{} = El) ->
-                   case xmpp_codec:is_known_tag(El) of
+                   case MatchFun(El) of
                        true -> decode(El);
                        false -> El
                    end;
@@ -287,10 +301,10 @@ set_subtag(Stanza, Tag) ->
     set_els(Stanza, NewEls).
 
 set_subtag([El|Els], Tag, TagName, XMLNS) ->
-    case {get_name(El), get_ns(El)} of
-       {TagName, XMLNS} ->
+    case match_tag(El, TagName, XMLNS) of
+       true ->
            [Tag|Els];
-       _ ->
+       false ->
            [El|set_subtag(Els, Tag, TagName, XMLNS)]
     end;
 set_subtag([], Tag, _, _) ->
@@ -304,14 +318,14 @@ get_subtag(Stanza, Tag) ->
     get_subtag(Els, TagName, XMLNS).
 
 get_subtag([El|Els], TagName, XMLNS) ->
-    case {get_name(El), get_ns(El)} of
-       {TagName, XMLNS} ->
+    case match_tag(El, TagName, XMLNS) of
+       true ->
            try
                decode(El)
            catch _:{xmpp_codec, _Why} ->
                    get_subtag(Els, TagName, XMLNS)
            end;
-       _ ->
+       false ->
            get_subtag(Els, TagName, XMLNS)
     end;
 get_subtag([], _, _) ->
@@ -328,10 +342,10 @@ remove_subtag(Stanza, Tag) ->
     set_els(Stanza, NewEls).
 
 remove_subtag([El|Els], TagName, XMLNS) ->
-    case {get_name(El), get_ns(El)} of
-       {TagName, XMLNS} ->
+    case match_tag(El, TagName, XMLNS) of
+       true ->
            remove_subtag(Els, TagName, XMLNS);
-       _ ->
+       false ->
            [El|remove_subtag(Els, TagName, XMLNS)]
     end;
 remove_subtag([], _, _) ->
@@ -345,10 +359,10 @@ has_subtag(Stanza, Tag) ->
     has_subtag(Els, TagName, XMLNS).
 
 has_subtag([El|Els], TagName, XMLNS) ->
-    case {get_name(El), get_ns(El)} of
-       {TagName, XMLNS} ->
+    case match_tag(El, TagName, XMLNS) of
+       true ->
            true;
-       _ ->
+       false ->
            has_subtag(Els, TagName, XMLNS)
     end;
 has_subtag([], _, _) ->
@@ -385,14 +399,6 @@ err_bad_request() ->
 err_bad_request(Text, Lang) ->
     err(modify, 'bad-request', 400, Text, Lang).
 
--spec err_bad_format() -> error().
-err_bad_format() ->
-    err(modify, 'bad-format', 406).
-
--spec err_bad_format(binary(), binary() | undefined) -> error().
-err_bad_format(Text, Lang) ->
-    err(modify, 'bad-format', 406, Text, Lang).
-
 -spec err_conflict() -> error().
 err_conflict() ->
     err(cancel, 'conflict', 409).
@@ -401,14 +407,6 @@ err_conflict() ->
 err_conflict(Text, Lang) ->
     err(cancel, 'conflict', 409, Text, Lang).
 
--spec err_not_allowed() -> error().
-err_not_allowed() ->
-    err(cancel, 'not-allowed', 405).
-
--spec err_not_allowed(binary(), binary() | undefined) -> error().
-err_not_allowed(Text, Lang) ->
-    err(cancel, 'not-allowed', 405, Text, Lang).
-
 -spec err_feature_not_implemented() -> error().
 err_feature_not_implemented() ->
     err(cancel, 'feature-not-implemented', 501).
@@ -417,14 +415,6 @@ err_feature_not_implemented() ->
 err_feature_not_implemented(Text, Lang) ->
     err(cancel, 'feature-not-implemented', 501, Text, Lang).
 
--spec err_item_not_found() -> error().
-err_item_not_found() ->
-    err(cancel, 'item-not-found', 404).
-
--spec err_item_not_found(binary(), binary() | undefined) -> error().
-err_item_not_found(Text, Lang) ->
-    err(cancel, 'item-not-found', 404, Text, Lang).
-
 -spec err_forbidden() -> error().
 err_forbidden() ->
     err(auth, 'forbidden', 403).
@@ -433,14 +423,18 @@ err_forbidden() ->
 err_forbidden(Text, Lang) ->
     err(auth, 'forbidden', 403, Text, Lang).
 
--spec err_not_acceptable() -> error().
-err_not_acceptable() ->
-    err(modify, 'not-acceptable', 406).
+%% RFC 6120 says error type SHOULD be "cancel".
+%% RFC 3920 and XEP-0082 says it SHOULD be "modify".
+-spec err_gone() -> error().
+err_gone() ->
+    err(modify, 'gone', 302).
 
--spec err_not_acceptable(binary(), binary() | undefined) -> error().
-err_not_acceptable(Text, Lang) ->
-    err(modify, 'not-acceptable', 406, Text, Lang).
+-spec err_gone(binary(), binary() | undefined) -> error().
+err_gone(Text, Lang) ->
+    err(modify, 'gone', 302, Text, Lang).
 
+%% RFC 6120 sasy error type SHOULD be "cancel".
+%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
 -spec err_internal_server_error() -> error().
 err_internal_server_error() ->
     err(wait, 'internal-server-error', 500).
@@ -449,13 +443,13 @@ err_internal_server_error() ->
 err_internal_server_error(Text, Lang) ->
     err(wait, 'internal-server-error', 500, Text, Lang).
 
--spec err_service_unavailable() -> error().
-err_service_unavailable() ->
-    err(cancel, 'service-unavailable', 503).
+-spec err_item_not_found() -> error().
+err_item_not_found() ->
+    err(cancel, 'item-not-found', 404).
 
--spec err_service_unavailable(binary(), binary() | undefined) -> error().
-err_service_unavailable(Text, Lang) ->
-    err(cancel, 'service-unavailable', 503, Text, Lang).
+-spec err_item_not_found(binary(), binary() | undefined) -> error().
+err_item_not_found(Text, Lang) ->
+    err(cancel, 'item-not-found', 404, Text, Lang).
 
 -spec err_jid_malformed() -> error().
 err_jid_malformed() ->
@@ -465,6 +459,22 @@ err_jid_malformed() ->
 err_jid_malformed(Text, Lang) ->
     err(modify, 'jid-malformed', 400, Text, Lang).
 
+-spec err_not_acceptable() -> error().
+err_not_acceptable() ->
+    err(modify, 'not-acceptable', 406).
+
+-spec err_not_acceptable(binary(), binary() | undefined) -> error().
+err_not_acceptable(Text, Lang) ->
+    err(modify, 'not-acceptable', 406, Text, Lang).
+
+-spec err_not_allowed() -> error().
+err_not_allowed() ->
+    err(cancel, 'not-allowed', 405).
+
+-spec err_not_allowed(binary(), binary() | undefined) -> error().
+err_not_allowed(Text, Lang) ->
+    err(cancel, 'not-allowed', 405, Text, Lang).
+
 -spec err_not_authorized() -> error().
 err_not_authorized() ->
     err(auth, 'not-authorized', 401).
@@ -473,6 +483,108 @@ err_not_authorized() ->
 err_not_authorized(Text, Lang) ->
     err(auth, 'not-authorized', 401, Text, Lang).
 
+-spec err_payment_required() -> error().
+err_payment_required() ->
+    err(auth, 'not-authorized', 402).
+
+-spec err_payment_required(binary(), binary() | undefined) -> error().
+err_payment_required(Text, Lang) ->
+    err(auth, 'not-authorized', 402, Text, Lang).
+
+%% <policy-violation/> is defined in neither RFC 3920 nor XEP-0086.
+%% We choose '403' error code (as in <forbidden/>).
+-spec err_policy_violation() -> error().
+err_policy_violation() ->
+    err(modify, 'policy-violation', 403).
+
+-spec err_policy_violation(binary(), binary() | undefined) -> error().
+err_policy_violation(Text, Lang) ->
+    err(modify, 'policy-violation', 403, Text, Lang).
+
+-spec err_recipient_unavailable() -> error().
+err_recipient_unavailable() ->
+    err(wait, 'recipient-unavailable', 404).
+
+-spec err_recipient_unavailable(binary(), binary() | undefined) -> error().
+err_recipient_unavailable(Text, Lang) ->
+    err(wait, 'recipient-unavailable', 404, Text, Lang).
+
+-spec err_redirect() -> error().
+err_redirect() ->
+    err(modify, 'redirect', 302).
+
+-spec err_redirect(binary(), binary() | undefined) -> error().
+err_redirect(Text, Lang) ->
+    err(modify, 'redirect', 302, Text, Lang).
+
+-spec err_registration_required() -> error().
+err_registration_required() ->
+    err(auth, 'registration-required', 407).
+
+-spec err_registration_required(binary(), binary() | undefined) -> error().
+err_registration_required(Text, Lang) ->
+    err(auth, 'registration-required', 407, Text, Lang).
+
+-spec err_remote_server_not_found() -> error().
+err_remote_server_not_found() ->
+    err(cancel, 'remote-server-not-found', 404).
+
+-spec err_remote_server_not_found(binary(), binary() | undefined) -> error().
+err_remote_server_not_found(Text, Lang) ->
+    err(cancel, 'remote-server-not-found', 404, Text, Lang).
+
+-spec err_remote_server_timeout() -> error().
+err_remote_server_timeout() ->
+    err(wait, 'remote-server-timeout', 504).
+
+-spec err_remote_server_timeout(binary(), binary() | undefined) -> error().
+err_remote_server_timeout(Text, Lang) ->
+    err(wait, 'remote-server-timeout', 504, Text, Lang).
+
+-spec err_resource_constraint() -> error().
+err_resource_constraint() ->
+    err(wait, 'resource-constraint', 500).
+
+-spec err_resource_constraint(binary(), binary() | undefined) -> error().
+err_resource_constraint(Text, Lang) ->
+    err(wait, 'resource-constraint', 500, Text, Lang).
+
+-spec err_service_unavailable() -> error().
+err_service_unavailable() ->
+    err(cancel, 'service-unavailable', 503).
+
+-spec err_service_unavailable(binary(), binary() | undefined) -> error().
+err_service_unavailable(Text, Lang) ->
+    err(cancel, 'service-unavailable', 503, Text, Lang).
+
+-spec err_subscription_required() -> error().
+err_subscription_required() ->
+    err(auth, 'subscription-required', 407).
+
+-spec err_subscription_required(binary(), binary() | undefined) -> error().
+err_subscription_required(Text, Lang) ->
+    err(auth, 'subscription-required', 407, Text, Lang).
+
+%% No error type is defined for <undefined-confition/>.
+%% We choose "modify" as it's used in RFC 6120 example.
+-spec err_undefined_condition() -> error().
+err_undefined_condition() ->
+    err(modify, 'undefined-condition', 500).
+
+-spec err_undefined_condition(binary(), binary() | undefined) -> error().
+err_undefined_condition(Text, Lang) ->
+    err(modify, 'undefined-condition', 500, Text, Lang).
+
+%% RFC 6120 says error type SHOULD be "wait" or "modify".
+%% RFC 3920 and XEP-0082 says it SHOULD be "wait".
+-spec err_unexpected_request() -> error().
+err_unexpected_request() ->
+    err(wait, 'unexpected-request', 400).
+
+-spec err_unexpected_request(binary(), binary() | undefined) -> error().
+err_unexpected_request(Text, Lang) ->
+    err(wait, 'unexpected-request', 400, Text, Lang).
+
 %%%===================================================================
 %%% Functions to construct stream errors
 %%%===================================================================
@@ -712,3 +824,7 @@ add_ns(#xmlel{name = Name} = El) when Name == <<"message">>;
     El#xmlel{attrs = Attrs};
 add_ns(El) ->
     El.
+
+-spec match_tag(xmlel() | xmpp_element(), binary(), binary()) -> boolean().
+match_tag(El, TagName, XMLNS) ->
+    get_name(El) == TagName andalso get_ns(El) == XMLNS.
index f6e5f0f1abbe00bb2ce8ef1c9d06eb6080e54717..c59c347f9763f4f8aabb1973eac5be82b9c25c8a 100644 (file)
@@ -15,6 +15,21 @@ decode(_el) -> decode(_el, []).
 decode({xmlel, _name, _attrs, _} = _el, Opts) ->
     IgnoreEls = proplists:get_bool(ignore_els, Opts),
     case {_name, get_attr(<<"xmlns">>, _attrs)} of
+      {<<"client-id">>, <<"urn:xmpp:sid:0">>} ->
+         decode_client_id(<<"urn:xmpp:sid:0">>, IgnoreEls, _el);
+      {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} ->
+         decode_stanza_id(<<"urn:xmpp:sid:0">>, IgnoreEls, _el);
+      {<<"addresses">>,
+       <<"http://jabber.org/protocol/address">>} ->
+         decode_addresses(<<"http://jabber.org/protocol/address">>,
+                          IgnoreEls, _el);
+      {<<"address">>,
+       <<"http://jabber.org/protocol/address">>} ->
+         decode_address(<<"http://jabber.org/protocol/address">>,
+                        IgnoreEls, _el);
+      {<<"nick">>, <<"http://jabber.org/protocol/nick">>} ->
+         decode_nick(<<"http://jabber.org/protocol/nick">>,
+                     IgnoreEls, _el);
       {<<"x">>, <<"jabber:x:expire">>} ->
          decode_expire(<<"jabber:x:expire">>, IgnoreEls, _el);
       {<<"x">>, <<"jabber:x:event">>} ->
@@ -53,6 +68,9 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
       {<<"instructions">>, <<"jabber:iq:search">>} ->
          decode_search_instructions(<<"jabber:iq:search">>,
                                     IgnoreEls, _el);
+      {<<"no-permanent-storage">>, <<"urn:xmpp:hints">>} ->
+         decode_hint_no_permanent_storage(<<"urn:xmpp:hints">>,
+                                          IgnoreEls, _el);
       {<<"no-permanent-store">>, <<"urn:xmpp:hints">>} ->
          decode_hint_no_permanent_store(<<"urn:xmpp:hints">>,
                                         IgnoreEls, _el);
@@ -160,12 +178,24 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
       {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_prefs(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
                           _el);
+      {<<"always">>, <<"urn:xmpp:mam:0">>} ->
+         decode_mam_always(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
+      {<<"always">>, <<"urn:xmpp:mam:1">>} ->
+         decode_mam_always(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
       {<<"always">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_always(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
                            _el);
+      {<<"never">>, <<"urn:xmpp:mam:0">>} ->
+         decode_mam_never(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
+      {<<"never">>, <<"urn:xmpp:mam:1">>} ->
+         decode_mam_never(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
       {<<"never">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_never(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
                           _el);
+      {<<"jid">>, <<"urn:xmpp:mam:0">>} ->
+         decode_mam_jid(<<"urn:xmpp:mam:0">>, IgnoreEls, _el);
+      {<<"jid">>, <<"urn:xmpp:mam:1">>} ->
+         decode_mam_jid(<<"urn:xmpp:mam:1">>, IgnoreEls, _el);
       {<<"jid">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_jid(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el);
       {<<"result">>, <<"urn:xmpp:mam:0">>} ->
@@ -185,6 +215,9 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
       {<<"query">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_query(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
                           _el);
+      {<<"withtext">>, <<"urn:xmpp:mam:tmp">>} ->
+         decode_mam_withtext(<<"urn:xmpp:mam:tmp">>, IgnoreEls,
+                             _el);
       {<<"with">>, <<"urn:xmpp:mam:tmp">>} ->
          decode_mam_with(<<"urn:xmpp:mam:tmp">>, IgnoreEls, _el);
       {<<"end">>, <<"urn:xmpp:mam:tmp">>} ->
@@ -216,6 +249,28 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
       {<<"after">>, <<"http://jabber.org/protocol/rsm">>} ->
          decode_rsm_after(<<"http://jabber.org/protocol/rsm">>,
                           IgnoreEls, _el);
+      {<<"unsubscribe">>, <<"urn:xmpp:mucsub:0">>} ->
+         decode_muc_unsubscribe(<<"urn:xmpp:mucsub:0">>,
+                                IgnoreEls, _el);
+      {<<"subscribe">>, <<"urn:xmpp:mucsub:0">>} ->
+         decode_muc_subscribe(<<"urn:xmpp:mucsub:0">>, IgnoreEls,
+                              _el);
+      {<<"event">>, <<"urn:xmpp:mucsub:0">>} ->
+         decode_muc_subscribe_event(<<"urn:xmpp:mucsub:0">>,
+                                    IgnoreEls, _el);
+      {<<"subscriptions">>, <<"urn:xmpp:mucsub:0">>} ->
+         decode_muc_subscriptions(<<"urn:xmpp:mucsub:0">>,
+                                  IgnoreEls, _el);
+      {<<"subscription">>, <<"urn:xmpp:mucsub:0">>} ->
+         decode_muc_subscription(<<"urn:xmpp:mucsub:0">>,
+                                 IgnoreEls, _el);
+      {<<"x">>, <<"jabber:x:conference">>} ->
+         decode_x_conference(<<"jabber:x:conference">>,
+                             IgnoreEls, _el);
+      {<<"unique">>,
+       <<"http://jabber.org/protocol/muc#unique">>} ->
+         decode_muc_unique(<<"http://jabber.org/protocol/muc#unique">>,
+                           IgnoreEls, _el);
       {<<"x">>, <<"http://jabber.org/protocol/muc">>} ->
          decode_muc(<<"http://jabber.org/protocol/muc">>,
                     IgnoreEls, _el);
@@ -223,10 +278,6 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
        <<"http://jabber.org/protocol/muc#admin">>} ->
          decode_muc_admin(<<"http://jabber.org/protocol/muc#admin">>,
                           IgnoreEls, _el);
-      {<<"reason">>,
-       <<"http://jabber.org/protocol/muc#admin">>} ->
-         decode_muc_admin_reason(<<"http://jabber.org/protocol/muc#admin">>,
-                                 IgnoreEls, _el);
       {<<"continue">>,
        <<"http://jabber.org/protocol/muc#admin">>} ->
          decode_muc_admin_continue(<<"http://jabber.org/protocol/muc#admin">>,
@@ -239,22 +290,26 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
        <<"http://jabber.org/protocol/muc#admin">>} ->
          decode_muc_admin_item(<<"http://jabber.org/protocol/muc#admin">>,
                                IgnoreEls, _el);
+      {<<"item">>,
+       <<"http://jabber.org/protocol/muc#owner">>} ->
+         decode_muc_owner_item(<<"http://jabber.org/protocol/muc#owner">>,
+                               IgnoreEls, _el);
       {<<"query">>,
        <<"http://jabber.org/protocol/muc#owner">>} ->
          decode_muc_owner(<<"http://jabber.org/protocol/muc#owner">>,
                           IgnoreEls, _el);
-      {<<"destroy">>,
-       <<"http://jabber.org/protocol/muc#owner">>} ->
-         decode_muc_owner_destroy(<<"http://jabber.org/protocol/muc#owner">>,
-                                  IgnoreEls, _el);
-      {<<"reason">>,
-       <<"http://jabber.org/protocol/muc#owner">>} ->
-         decode_muc_owner_reason(<<"http://jabber.org/protocol/muc#owner">>,
-                                 IgnoreEls, _el);
       {<<"password">>,
        <<"http://jabber.org/protocol/muc#owner">>} ->
-         decode_muc_owner_password(<<"http://jabber.org/protocol/muc#owner">>,
-                                   IgnoreEls, _el);
+         decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>,
+                             IgnoreEls, _el);
+      {<<"password">>,
+       <<"http://jabber.org/protocol/muc#user">>} ->
+         decode_muc_password(<<"http://jabber.org/protocol/muc#user">>,
+                             IgnoreEls, _el);
+      {<<"password">>,
+       <<"http://jabber.org/protocol/muc">>} ->
+         decode_muc_password(<<"http://jabber.org/protocol/muc">>,
+                             IgnoreEls, _el);
       {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} ->
          decode_muc_user(<<"http://jabber.org/protocol/muc#user">>,
                          IgnoreEls, _el);
@@ -280,16 +335,28 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
                                 IgnoreEls, _el);
       {<<"destroy">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
-         decode_muc_user_destroy(<<"http://jabber.org/protocol/muc#user">>,
-                                 IgnoreEls, _el);
+         decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>,
+                            IgnoreEls, _el);
+      {<<"destroy">>,
+       <<"http://jabber.org/protocol/muc#owner">>} ->
+         decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>,
+                            IgnoreEls, _el);
       {<<"decline">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
          decode_muc_user_decline(<<"http://jabber.org/protocol/muc#user">>,
                                  IgnoreEls, _el);
       {<<"reason">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
-         decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
-                                IgnoreEls, _el);
+         decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                           IgnoreEls, _el);
+      {<<"reason">>,
+       <<"http://jabber.org/protocol/muc#admin">>} ->
+         decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                           IgnoreEls, _el);
+      {<<"reason">>,
+       <<"http://jabber.org/protocol/muc#owner">>} ->
+         decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                           IgnoreEls, _el);
       {<<"history">>, <<"http://jabber.org/protocol/muc">>} ->
          decode_muc_history(<<"http://jabber.org/protocol/muc">>,
                             IgnoreEls, _el);
@@ -1002,6 +1069,10 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
        <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
          decode_error_policy_violation(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
                                        IgnoreEls, _el);
+      {<<"payment-required">>,
+       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
+         decode_error_payment_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
+                                       IgnoreEls, _el);
       {<<"not-authorized">>,
        <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
          decode_error_not_authorized(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
@@ -1183,6 +1254,16 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
 
 is_known_tag({xmlel, _name, _attrs, _} = _el) ->
     case {_name, get_attr(<<"xmlns">>, _attrs)} of
+      {<<"client-id">>, <<"urn:xmpp:sid:0">>} -> true;
+      {<<"stanza-id">>, <<"urn:xmpp:sid:0">>} -> true;
+      {<<"addresses">>,
+       <<"http://jabber.org/protocol/address">>} ->
+         true;
+      {<<"address">>,
+       <<"http://jabber.org/protocol/address">>} ->
+         true;
+      {<<"nick">>, <<"http://jabber.org/protocol/nick">>} ->
+         true;
       {<<"x">>, <<"jabber:x:expire">>} -> true;
       {<<"x">>, <<"jabber:x:event">>} -> true;
       {<<"id">>, <<"jabber:x:event">>} -> true;
@@ -1197,6 +1278,8 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"last">>, <<"jabber:iq:search">>} -> true;
       {<<"first">>, <<"jabber:iq:search">>} -> true;
       {<<"instructions">>, <<"jabber:iq:search">>} -> true;
+      {<<"no-permanent-storage">>, <<"urn:xmpp:hints">>} ->
+         true;
       {<<"no-permanent-store">>, <<"urn:xmpp:hints">>} ->
          true;
       {<<"store">>, <<"urn:xmpp:hints">>} -> true;
@@ -1248,8 +1331,14 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"prefs">>, <<"urn:xmpp:mam:0">>} -> true;
       {<<"prefs">>, <<"urn:xmpp:mam:1">>} -> true;
       {<<"prefs">>, <<"urn:xmpp:mam:tmp">>} -> true;
+      {<<"always">>, <<"urn:xmpp:mam:0">>} -> true;
+      {<<"always">>, <<"urn:xmpp:mam:1">>} -> true;
       {<<"always">>, <<"urn:xmpp:mam:tmp">>} -> true;
+      {<<"never">>, <<"urn:xmpp:mam:0">>} -> true;
+      {<<"never">>, <<"urn:xmpp:mam:1">>} -> true;
       {<<"never">>, <<"urn:xmpp:mam:tmp">>} -> true;
+      {<<"jid">>, <<"urn:xmpp:mam:0">>} -> true;
+      {<<"jid">>, <<"urn:xmpp:mam:1">>} -> true;
       {<<"jid">>, <<"urn:xmpp:mam:tmp">>} -> true;
       {<<"result">>, <<"urn:xmpp:mam:0">>} -> true;
       {<<"result">>, <<"urn:xmpp:mam:1">>} -> true;
@@ -1258,6 +1347,7 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"query">>, <<"urn:xmpp:mam:0">>} -> true;
       {<<"query">>, <<"urn:xmpp:mam:1">>} -> true;
       {<<"query">>, <<"urn:xmpp:mam:tmp">>} -> true;
+      {<<"withtext">>, <<"urn:xmpp:mam:tmp">>} -> true;
       {<<"with">>, <<"urn:xmpp:mam:tmp">>} -> true;
       {<<"end">>, <<"urn:xmpp:mam:tmp">>} -> true;
       {<<"start">>, <<"urn:xmpp:mam:tmp">>} -> true;
@@ -1277,13 +1367,19 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
          true;
       {<<"after">>, <<"http://jabber.org/protocol/rsm">>} ->
          true;
+      {<<"unsubscribe">>, <<"urn:xmpp:mucsub:0">>} -> true;
+      {<<"subscribe">>, <<"urn:xmpp:mucsub:0">>} -> true;
+      {<<"event">>, <<"urn:xmpp:mucsub:0">>} -> true;
+      {<<"subscriptions">>, <<"urn:xmpp:mucsub:0">>} -> true;
+      {<<"subscription">>, <<"urn:xmpp:mucsub:0">>} -> true;
+      {<<"x">>, <<"jabber:x:conference">>} -> true;
+      {<<"unique">>,
+       <<"http://jabber.org/protocol/muc#unique">>} ->
+         true;
       {<<"x">>, <<"http://jabber.org/protocol/muc">>} -> true;
       {<<"query">>,
        <<"http://jabber.org/protocol/muc#admin">>} ->
          true;
-      {<<"reason">>,
-       <<"http://jabber.org/protocol/muc#admin">>} ->
-         true;
       {<<"continue">>,
        <<"http://jabber.org/protocol/muc#admin">>} ->
          true;
@@ -1293,17 +1389,20 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"item">>,
        <<"http://jabber.org/protocol/muc#admin">>} ->
          true;
-      {<<"query">>,
+      {<<"item">>,
        <<"http://jabber.org/protocol/muc#owner">>} ->
          true;
-      {<<"destroy">>,
+      {<<"query">>,
        <<"http://jabber.org/protocol/muc#owner">>} ->
          true;
-      {<<"reason">>,
+      {<<"password">>,
        <<"http://jabber.org/protocol/muc#owner">>} ->
          true;
       {<<"password">>,
-       <<"http://jabber.org/protocol/muc#owner">>} ->
+       <<"http://jabber.org/protocol/muc#user">>} ->
+         true;
+      {<<"password">>,
+       <<"http://jabber.org/protocol/muc">>} ->
          true;
       {<<"x">>, <<"http://jabber.org/protocol/muc#user">>} ->
          true;
@@ -1325,12 +1424,21 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"destroy">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
          true;
+      {<<"destroy">>,
+       <<"http://jabber.org/protocol/muc#owner">>} ->
+         true;
       {<<"decline">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
          true;
       {<<"reason">>,
        <<"http://jabber.org/protocol/muc#user">>} ->
          true;
+      {<<"reason">>,
+       <<"http://jabber.org/protocol/muc#admin">>} ->
+         true;
+      {<<"reason">>,
+       <<"http://jabber.org/protocol/muc#owner">>} ->
+         true;
       {<<"history">>, <<"http://jabber.org/protocol/muc">>} ->
          true;
       {<<"query">>,
@@ -1773,6 +1881,9 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"policy-violation">>,
        <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
          true;
+      {<<"payment-required">>,
+       <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
+         true;
       {<<"not-authorized">>,
        <<"urn:ietf:params:xml:ns:xmpp-stanzas">>} ->
          true;
@@ -1914,7 +2025,7 @@ encode({disco_item, _, _, _} = Item) ->
     encode_disco_item(Item,
                      [{<<"xmlns">>,
                        <<"http://jabber.org/protocol/disco#items">>}]);
-encode({disco_items, _, _} = Query) ->
+encode({disco_items, _, _, _} = Query) ->
     encode_disco_items(Query,
                       [{<<"xmlns">>,
                         <<"http://jabber.org/protocol/disco#items">>}]);
@@ -2107,6 +2218,9 @@ encode({vcard_temp, _, _, _, _, _, _, _, _, _, _, _, _,
 encode({vcard_xupdate, undefined, _} = X) ->
     encode_vcard_xupdate(X,
                         [{<<"xmlns">>, <<"vcard-temp:x:update">>}]);
+encode({xdata_option, _, _} = Option) ->
+    encode_xdata_field_option(Option,
+                             [{<<"xmlns">>, <<"jabber:x:data">>}]);
 encode({xdata_field, _, _, _, _, _, _, _} = Field) ->
     encode_xdata_field(Field,
                       [{<<"xmlns">>, <<"jabber:x:data">>}]);
@@ -2206,11 +2320,9 @@ encode({muc_decline, _, _, _} = Decline) ->
     encode_muc_user_decline(Decline,
                            [{<<"xmlns">>,
                              <<"http://jabber.org/protocol/muc#user">>}]);
-encode({muc_user_destroy, _, _} = Destroy) ->
-    encode_muc_user_destroy(Destroy,
-                           [{<<"xmlns">>,
-                             <<"http://jabber.org/protocol/muc#user">>}]);
-encode({muc_invite, _, _, _} = Invite) ->
+encode({muc_destroy, _, _, _, _} = Destroy) ->
+    encode_muc_destroy(Destroy, []);
+encode({muc_invite, _, _, _, _} = Invite) ->
     encode_muc_user_invite(Invite,
                           [{<<"xmlns">>,
                             <<"http://jabber.org/protocol/muc#user">>}]);
@@ -2218,11 +2330,7 @@ encode({muc_user, _, _, _, _, _, _} = X) ->
     encode_muc_user(X,
                    [{<<"xmlns">>,
                      <<"http://jabber.org/protocol/muc#user">>}]);
-encode({muc_owner_destroy, _, _, _} = Destroy) ->
-    encode_muc_owner_destroy(Destroy,
-                            [{<<"xmlns">>,
-                              <<"http://jabber.org/protocol/muc#owner">>}]);
-encode({muc_owner, _, _} = Query) ->
+encode({muc_owner, _, _, _} = Query) ->
     encode_muc_owner(Query,
                     [{<<"xmlns">>,
                       <<"http://jabber.org/protocol/muc#owner">>}]);
@@ -2237,13 +2345,29 @@ encode({muc_admin, _} = Query) ->
 encode({muc, _, _} = X) ->
     encode_muc(X,
               [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}]);
+encode({muc_unique, _} = Unique) ->
+    encode_muc_unique(Unique,
+                     [{<<"xmlns">>,
+                       <<"http://jabber.org/protocol/muc#unique">>}]);
+encode({x_conference, _, _, _, _, _} = X) ->
+    encode_x_conference(X,
+                       [{<<"xmlns">>, <<"jabber:x:conference">>}]);
+encode({muc_subscriptions, _} = Subscriptions) ->
+    encode_muc_subscriptions(Subscriptions,
+                            [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]);
+encode({muc_subscribe, _, _} = Subscribe) ->
+    encode_muc_subscribe(Subscribe,
+                        [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]);
+encode({muc_unsubscribe} = Unsubscribe) ->
+    encode_muc_unsubscribe(Unsubscribe,
+                          [{<<"xmlns">>, <<"urn:xmpp:mucsub:0">>}]);
 encode({rsm_first, _, _} = First) ->
     encode_rsm_first(First,
                     [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]);
 encode({rsm_set, _, _, _, _, _, _, _} = Set) ->
     encode_rsm_set(Set,
                   [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}]);
-encode({mam_query, _, _, _, _, _, _, _} = Query) ->
+encode({mam_query, _, _, _, _, _, _, _, _} = Query) ->
     encode_mam_query(Query, []);
 encode({mam_archived, _, _} = Archived) ->
     encode_mam_archived(Archived,
@@ -2328,6 +2452,10 @@ encode({hint, 'no-permanent-store'} =
           No_permanent_store) ->
     encode_hint_no_permanent_store(No_permanent_store,
                                   [{<<"xmlns">>, <<"urn:xmpp:hints">>}]);
+encode({hint, 'no-permanent-storage'} =
+          No_permanent_storage) ->
+    encode_hint_no_permanent_storage(No_permanent_storage,
+                                    [{<<"xmlns">>, <<"urn:xmpp:hints">>}]);
 encode({search_item, _, _, _, _, _} = Item) ->
     encode_search_item(Item,
                       [{<<"xmlns">>, <<"jabber:iq:search">>}]);
@@ -2338,7 +2466,24 @@ encode({xevent, _, _, _, _, _} = X) ->
     encode_xevent(X, [{<<"xmlns">>, <<"jabber:x:event">>}]);
 encode({expire, _, _} = X) ->
     encode_expire(X,
-                 [{<<"xmlns">>, <<"jabber:x:expire">>}]).
+                 [{<<"xmlns">>, <<"jabber:x:expire">>}]);
+encode({nick, _} = Nick) ->
+    encode_nick(Nick,
+               [{<<"xmlns">>, <<"http://jabber.org/protocol/nick">>}]);
+encode({address, _, _, _, _, _} = Address) ->
+    encode_address(Address,
+                  [{<<"xmlns">>,
+                    <<"http://jabber.org/protocol/address">>}]);
+encode({addresses, _} = Addresses) ->
+    encode_addresses(Addresses,
+                    [{<<"xmlns">>,
+                      <<"http://jabber.org/protocol/address">>}]);
+encode({stanza_id, _, _} = Stanza_id) ->
+    encode_stanza_id(Stanza_id,
+                    [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]);
+encode({client_id, _} = Client_id) ->
+    encode_client_id(Client_id,
+                    [{<<"xmlns">>, <<"urn:xmpp:sid:0">>}]).
 
 get_name({last, _, _}) -> <<"query">>;
 get_name({version, _, _, _}) -> <<"query">>;
@@ -2355,7 +2500,7 @@ get_name({block_list, _}) -> <<"blocklist">>;
 get_name({identity, _, _, _, _}) -> <<"identity">>;
 get_name({disco_info, _, _, _, _}) -> <<"query">>;
 get_name({disco_item, _, _, _}) -> <<"item">>;
-get_name({disco_items, _, _}) -> <<"query">>;
+get_name({disco_items, _, _, _}) -> <<"query">>;
 get_name({private, _}) -> <<"query">>;
 get_name({bookmark_conference, _, _, _, _, _}) ->
     <<"conference">>;
@@ -2424,6 +2569,7 @@ get_name({vcard_temp, _, _, _, _, _, _, _, _, _, _, _,
          _}) ->
     <<"vCard">>;
 get_name({vcard_xupdate, undefined, _}) -> <<"x">>;
+get_name({xdata_option, _, _}) -> <<"option">>;
 get_name({xdata_field, _, _, _, _, _, _, _}) ->
     <<"field">>;
 get_name({xdata, _, _, _, _, _, _}) -> <<"x">>;
@@ -2456,18 +2602,22 @@ get_name({bytestreams, _, _, _, _, _, _}) ->
     <<"query">>;
 get_name({muc_history, _, _, _, _}) -> <<"history">>;
 get_name({muc_decline, _, _, _}) -> <<"decline">>;
-get_name({muc_user_destroy, _, _}) -> <<"destroy">>;
-get_name({muc_invite, _, _, _}) -> <<"invite">>;
+get_name({muc_destroy, _, _, _, _}) -> <<"destroy">>;
+get_name({muc_invite, _, _, _, _}) -> <<"invite">>;
 get_name({muc_user, _, _, _, _, _, _}) -> <<"x">>;
-get_name({muc_owner_destroy, _, _, _}) -> <<"destroy">>;
-get_name({muc_owner, _, _}) -> <<"query">>;
+get_name({muc_owner, _, _, _}) -> <<"query">>;
 get_name({muc_item, _, _, _, _, _, _, _}) -> <<"item">>;
 get_name({muc_actor, _, _}) -> <<"actor">>;
 get_name({muc_admin, _}) -> <<"query">>;
 get_name({muc, _, _}) -> <<"x">>;
+get_name({muc_unique, _}) -> <<"unique">>;
+get_name({x_conference, _, _, _, _, _}) -> <<"x">>;
+get_name({muc_subscriptions, _}) -> <<"subscriptions">>;
+get_name({muc_subscribe, _, _}) -> <<"subscribe">>;
+get_name({muc_unsubscribe}) -> <<"unsubscribe">>;
 get_name({rsm_first, _, _}) -> <<"first">>;
 get_name({rsm_set, _, _, _, _, _, _, _}) -> <<"set">>;
-get_name({mam_query, _, _, _, _, _, _, _}) ->
+get_name({mam_query, _, _, _, _, _, _, _, _}) ->
     <<"query">>;
 get_name({mam_archived, _, _}) -> <<"archived">>;
 get_name({mam_result, _, _, _, _}) -> <<"result">>;
@@ -2501,10 +2651,17 @@ get_name({hint, 'no-storage'}) -> <<"no-storage">>;
 get_name({hint, store}) -> <<"store">>;
 get_name({hint, 'no-permanent-store'}) ->
     <<"no-permanent-store">>;
+get_name({hint, 'no-permanent-storage'}) ->
+    <<"no-permanent-storage">>;
 get_name({search_item, _, _, _, _, _}) -> <<"item">>;
 get_name({search, _, _, _, _, _, _, _}) -> <<"query">>;
 get_name({xevent, _, _, _, _, _}) -> <<"x">>;
-get_name({expire, _, _}) -> <<"x">>.
+get_name({expire, _, _}) -> <<"x">>;
+get_name({nick, _}) -> <<"nick">>;
+get_name({address, _, _, _, _, _}) -> <<"address">>;
+get_name({addresses, _}) -> <<"addresses">>;
+get_name({stanza_id, _, _}) -> <<"stanza-id">>;
+get_name({client_id, _}) -> <<"client-id">>.
 
 get_ns({last, _, _}) -> <<"jabber:iq:last">>;
 get_ns({version, _, _, _}) -> <<"jabber:iq:version">>;
@@ -2527,7 +2684,7 @@ get_ns({disco_info, _, _, _, _}) ->
     <<"http://jabber.org/protocol/disco#info">>;
 get_ns({disco_item, _, _, _}) ->
     <<"http://jabber.org/protocol/disco#items">>;
-get_ns({disco_items, _, _}) ->
+get_ns({disco_items, _, _, _}) ->
     <<"http://jabber.org/protocol/disco#items">>;
 get_ns({private, _}) -> <<"jabber:iq:private">>;
 get_ns({bookmark_conference, _, _, _, _, _}) ->
@@ -2624,6 +2781,7 @@ get_ns({vcard_temp, _, _, _, _, _, _, _, _, _, _, _, _,
     <<"vcard-temp">>;
 get_ns({vcard_xupdate, undefined, _}) ->
     <<"vcard-temp:x:update">>;
+get_ns({xdata_option, _, _}) -> <<"jabber:x:data">>;
 get_ns({xdata_field, _, _, _, _, _, _, _}) ->
     <<"jabber:x:data">>;
 get_ns({xdata, _, _, _, _, _, _}) ->
@@ -2675,25 +2833,32 @@ get_ns({muc_history, _, _, _, _}) ->
     <<"http://jabber.org/protocol/muc">>;
 get_ns({muc_decline, _, _, _}) ->
     <<"http://jabber.org/protocol/muc#user">>;
-get_ns({muc_user_destroy, _, _}) ->
-    <<"http://jabber.org/protocol/muc#user">>;
-get_ns({muc_invite, _, _, _}) ->
+get_ns({muc_destroy, Xmlns, _, _, _}) -> Xmlns;
+get_ns({muc_invite, _, _, _, _}) ->
     <<"http://jabber.org/protocol/muc#user">>;
 get_ns({muc_user, _, _, _, _, _, _}) ->
     <<"http://jabber.org/protocol/muc#user">>;
-get_ns({muc_owner_destroy, _, _, _}) ->
-    <<"http://jabber.org/protocol/muc#owner">>;
-get_ns({muc_owner, _, _}) ->
+get_ns({muc_owner, _, _, _}) ->
     <<"http://jabber.org/protocol/muc#owner">>;
 get_ns({muc_admin, _}) ->
     <<"http://jabber.org/protocol/muc#admin">>;
 get_ns({muc, _, _}) ->
     <<"http://jabber.org/protocol/muc">>;
+get_ns({muc_unique, _}) ->
+    <<"http://jabber.org/protocol/muc#unique">>;
+get_ns({x_conference, _, _, _, _, _}) ->
+    <<"jabber:x:conference">>;
+get_ns({muc_subscriptions, _}) ->
+    <<"urn:xmpp:mucsub:0">>;
+get_ns({muc_subscribe, _, _}) ->
+    <<"urn:xmpp:mucsub:0">>;
+get_ns({muc_unsubscribe}) -> <<"urn:xmpp:mucsub:0">>;
 get_ns({rsm_first, _, _}) ->
     <<"http://jabber.org/protocol/rsm">>;
 get_ns({rsm_set, _, _, _, _, _, _, _}) ->
     <<"http://jabber.org/protocol/rsm">>;
-get_ns({mam_query, Xmlns, _, _, _, _, _, _}) -> Xmlns;
+get_ns({mam_query, Xmlns, _, _, _, _, _, _, _}) ->
+    Xmlns;
 get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>;
 get_ns({mam_result, Xmlns, _, _, _}) -> Xmlns;
 get_ns({mam_prefs, Xmlns, _, _, _}) -> Xmlns;
@@ -2729,12 +2894,22 @@ get_ns({hint, 'no-storage'}) -> <<"urn:xmpp:hints">>;
 get_ns({hint, store}) -> <<"urn:xmpp:hints">>;
 get_ns({hint, 'no-permanent-store'}) ->
     <<"urn:xmpp:hints">>;
+get_ns({hint, 'no-permanent-storage'}) ->
+    <<"urn:xmpp:hints">>;
 get_ns({search_item, _, _, _, _, _}) ->
     <<"jabber:iq:search">>;
 get_ns({search, _, _, _, _, _, _, _}) ->
     <<"jabber:iq:search">>;
 get_ns({xevent, _, _, _, _, _}) -> <<"jabber:x:event">>;
-get_ns({expire, _, _}) -> <<"jabber:x:expire">>.
+get_ns({expire, _, _}) -> <<"jabber:x:expire">>;
+get_ns({nick, _}) ->
+    <<"http://jabber.org/protocol/nick">>;
+get_ns({address, _, _, _, _, _}) ->
+    <<"http://jabber.org/protocol/address">>;
+get_ns({addresses, _}) ->
+    <<"http://jabber.org/protocol/address">>;
+get_ns({stanza_id, _, _}) -> <<"urn:xmpp:sid:0">>;
+get_ns({client_id, _}) -> <<"urn:xmpp:sid:0">>.
 
 dec_int(Val) -> dec_int(Val, infinity, infinity).
 
@@ -2801,7 +2976,7 @@ pp(identity, 4) -> [category, type, lang, name];
 pp(disco_info, 4) ->
     [node, identities, features, xdata];
 pp(disco_item, 3) -> [jid, name, node];
-pp(disco_items, 2) -> [node, items];
+pp(disco_items, 3) -> [node, items, rsm];
 pp(private, 1) -> [xml_els];
 pp(bookmark_conference, 5) ->
     [name, jid, autojoin, nick, password];
@@ -2876,6 +3051,7 @@ pp(vcard_temp, 29) ->
      org, categories, note, prodid, rev, sort_string, sound,
      uid, url, class, key, desc];
 pp(vcard_xupdate, 2) -> [us, hash];
+pp(xdata_option, 2) -> [label, value];
 pp(xdata_field, 7) ->
     [label, type, var, required, desc, values, options];
 pp(xdata, 6) ->
@@ -2905,23 +3081,28 @@ pp(bytestreams, 6) ->
 pp(muc_history, 4) ->
     [maxchars, maxstanzas, seconds, since];
 pp(muc_decline, 3) -> [reason, from, to];
-pp(muc_user_destroy, 2) -> [reason, jid];
-pp(muc_invite, 3) -> [reason, from, to];
+pp(muc_destroy, 4) -> [xmlns, jid, reason, password];
+pp(muc_invite, 4) -> [reason, from, to, continue];
 pp(muc_user, 6) ->
     [decline, destroy, invites, items, status_codes,
      password];
-pp(muc_owner_destroy, 3) -> [jid, reason, password];
-pp(muc_owner, 2) -> [destroy, config];
+pp(muc_owner, 3) -> [destroy, config, items];
 pp(muc_item, 7) ->
     [actor, continue, reason, affiliation, role, jid, nick];
 pp(muc_actor, 2) -> [jid, nick];
 pp(muc_admin, 1) -> [items];
 pp(muc, 2) -> [history, password];
+pp(muc_unique, 1) -> [name];
+pp(x_conference, 5) ->
+    [jid, password, reason, continue, thread];
+pp(muc_subscriptions, 1) -> [list];
+pp(muc_subscribe, 2) -> [nick, events];
+pp(muc_unsubscribe, 0) -> [];
 pp(rsm_first, 2) -> [index, data];
 pp(rsm_set, 7) ->
     ['after', before, count, first, index, last, max];
-pp(mam_query, 7) ->
-    [xmlns, id, start, 'end', with, rsm, xdata];
+pp(mam_query, 8) ->
+    [xmlns, id, start, 'end', with, withtext, rsm, xdata];
 pp(mam_archived, 2) -> [by, id];
 pp(mam_result, 4) -> [xmlns, queryid, id, sub_els];
 pp(mam_prefs, 4) -> [xmlns, default, always, never];
@@ -2954,6 +3135,11 @@ pp(search, 7) ->
 pp(xevent, 5) ->
     [offline, delivered, displayed, composing, id];
 pp(expire, 2) -> [seconds, stored];
+pp(nick, 1) -> [name];
+pp(address, 5) -> [type, jid, desc, node, delivered];
+pp(addresses, 1) -> [list];
+pp(stanza_id, 2) -> [by, id];
+pp(client_id, 1) -> [id];
 pp(_, _) -> no.
 
 join([], _Sep) -> <<>>;
@@ -3000,6 +3186,274 @@ dec_tzo(Val) ->
     M = jlib:binary_to_integer(M1),
     if H >= -12, H =< 12, M >= 0, M < 60 -> {H, M} end.
 
+decode_client_id(__TopXMLNS, __IgnoreEls,
+                {xmlel, <<"client-id">>, _attrs, _els}) ->
+    Id = decode_client_id_attrs(__TopXMLNS, _attrs,
+                               undefined),
+    {client_id, Id}.
+
+decode_client_id_attrs(__TopXMLNS,
+                      [{<<"id">>, _val} | _attrs], _Id) ->
+    decode_client_id_attrs(__TopXMLNS, _attrs, _val);
+decode_client_id_attrs(__TopXMLNS, [_ | _attrs], Id) ->
+    decode_client_id_attrs(__TopXMLNS, _attrs, Id);
+decode_client_id_attrs(__TopXMLNS, [], Id) ->
+    decode_client_id_attr_id(__TopXMLNS, Id).
+
+encode_client_id({client_id, Id}, _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_client_id_attr_id(Id, _xmlns_attrs),
+    {xmlel, <<"client-id">>, _attrs, _els}.
+
+decode_client_id_attr_id(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"id">>, <<"client-id">>, __TopXMLNS}});
+decode_client_id_attr_id(__TopXMLNS, _val) -> _val.
+
+encode_client_id_attr_id(_val, _acc) ->
+    [{<<"id">>, _val} | _acc].
+
+decode_stanza_id(__TopXMLNS, __IgnoreEls,
+                {xmlel, <<"stanza-id">>, _attrs, _els}) ->
+    {Id, By} = decode_stanza_id_attrs(__TopXMLNS, _attrs,
+                                     undefined, undefined),
+    {stanza_id, By, Id}.
+
+decode_stanza_id_attrs(__TopXMLNS,
+                      [{<<"id">>, _val} | _attrs], _Id, By) ->
+    decode_stanza_id_attrs(__TopXMLNS, _attrs, _val, By);
+decode_stanza_id_attrs(__TopXMLNS,
+                      [{<<"by">>, _val} | _attrs], Id, _By) ->
+    decode_stanza_id_attrs(__TopXMLNS, _attrs, Id, _val);
+decode_stanza_id_attrs(__TopXMLNS, [_ | _attrs], Id,
+                      By) ->
+    decode_stanza_id_attrs(__TopXMLNS, _attrs, Id, By);
+decode_stanza_id_attrs(__TopXMLNS, [], Id, By) ->
+    {decode_stanza_id_attr_id(__TopXMLNS, Id),
+     decode_stanza_id_attr_by(__TopXMLNS, By)}.
+
+encode_stanza_id({stanza_id, By, Id}, _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_stanza_id_attr_by(By,
+                                     encode_stanza_id_attr_id(Id,
+                                                              _xmlns_attrs)),
+    {xmlel, <<"stanza-id">>, _attrs, _els}.
+
+decode_stanza_id_attr_id(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"id">>, <<"stanza-id">>, __TopXMLNS}});
+decode_stanza_id_attr_id(__TopXMLNS, _val) -> _val.
+
+encode_stanza_id_attr_id(_val, _acc) ->
+    [{<<"id">>, _val} | _acc].
+
+decode_stanza_id_attr_by(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"by">>, <<"stanza-id">>, __TopXMLNS}});
+decode_stanza_id_attr_by(__TopXMLNS, _val) ->
+    case catch dec_jid(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"by">>, <<"stanza-id">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_stanza_id_attr_by(_val, _acc) ->
+    [{<<"by">>, enc_jid(_val)} | _acc].
+
+decode_addresses(__TopXMLNS, __IgnoreEls,
+                {xmlel, <<"addresses">>, _attrs, _els}) ->
+    List = decode_addresses_els(__TopXMLNS, __IgnoreEls,
+                               _els, []),
+    {addresses, List}.
+
+decode_addresses_els(__TopXMLNS, __IgnoreEls, [],
+                    List) ->
+    lists:reverse(List);
+decode_addresses_els(__TopXMLNS, __IgnoreEls,
+                    [{xmlel, <<"address">>, _attrs, _} = _el | _els],
+                    List) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/address">> ->
+         decode_addresses_els(__TopXMLNS, __IgnoreEls, _els,
+                              [decode_address(__TopXMLNS, __IgnoreEls, _el)
+                               | List]);
+      <<"http://jabber.org/protocol/address">> ->
+         decode_addresses_els(__TopXMLNS, __IgnoreEls, _els,
+                              [decode_address(<<"http://jabber.org/protocol/address">>,
+                                              __IgnoreEls, _el)
+                               | List]);
+      _ ->
+         decode_addresses_els(__TopXMLNS, __IgnoreEls, _els,
+                              List)
+    end;
+decode_addresses_els(__TopXMLNS, __IgnoreEls,
+                    [_ | _els], List) ->
+    decode_addresses_els(__TopXMLNS, __IgnoreEls, _els,
+                        List).
+
+encode_addresses({addresses, List}, _xmlns_attrs) ->
+    _els = lists:reverse('encode_addresses_$list'(List,
+                                                 [])),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"addresses">>, _attrs, _els}.
+
+'encode_addresses_$list'([], _acc) -> _acc;
+'encode_addresses_$list'([List | _els], _acc) ->
+    'encode_addresses_$list'(_els,
+                            [encode_address(List, []) | _acc]).
+
+decode_address(__TopXMLNS, __IgnoreEls,
+              {xmlel, <<"address">>, _attrs, _els}) ->
+    {Type, Jid, Desc, Node, Delivered} =
+       decode_address_attrs(__TopXMLNS, _attrs, undefined,
+                            undefined, undefined, undefined, undefined),
+    {address, Type, Jid, Desc, Node, Delivered}.
+
+decode_address_attrs(__TopXMLNS,
+                    [{<<"type">>, _val} | _attrs], _Type, Jid, Desc, Node,
+                    Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, _val, Jid,
+                        Desc, Node, Delivered);
+decode_address_attrs(__TopXMLNS,
+                    [{<<"jid">>, _val} | _attrs], Type, _Jid, Desc, Node,
+                    Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, Type, _val,
+                        Desc, Node, Delivered);
+decode_address_attrs(__TopXMLNS,
+                    [{<<"desc">>, _val} | _attrs], Type, Jid, _Desc, Node,
+                    Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, Type, Jid,
+                        _val, Node, Delivered);
+decode_address_attrs(__TopXMLNS,
+                    [{<<"node">>, _val} | _attrs], Type, Jid, Desc, _Node,
+                    Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, Type, Jid,
+                        Desc, _val, Delivered);
+decode_address_attrs(__TopXMLNS,
+                    [{<<"delivered">>, _val} | _attrs], Type, Jid, Desc,
+                    Node, _Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, Type, Jid,
+                        Desc, Node, _val);
+decode_address_attrs(__TopXMLNS, [_ | _attrs], Type,
+                    Jid, Desc, Node, Delivered) ->
+    decode_address_attrs(__TopXMLNS, _attrs, Type, Jid,
+                        Desc, Node, Delivered);
+decode_address_attrs(__TopXMLNS, [], Type, Jid, Desc,
+                    Node, Delivered) ->
+    {decode_address_attr_type(__TopXMLNS, Type),
+     decode_address_attr_jid(__TopXMLNS, Jid),
+     decode_address_attr_desc(__TopXMLNS, Desc),
+     decode_address_attr_node(__TopXMLNS, Node),
+     decode_address_attr_delivered(__TopXMLNS, Delivered)}.
+
+encode_address({address, Type, Jid, Desc, Node,
+               Delivered},
+              _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_address_attr_delivered(Delivered,
+                                          encode_address_attr_node(Node,
+                                                                   encode_address_attr_desc(Desc,
+                                                                                            encode_address_attr_jid(Jid,
+                                                                                                                    encode_address_attr_type(Type,
+                                                                                                                                             _xmlns_attrs))))),
+    {xmlel, <<"address">>, _attrs, _els}.
+
+decode_address_attr_type(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"type">>, <<"address">>, __TopXMLNS}});
+decode_address_attr_type(__TopXMLNS, _val) ->
+    case catch dec_enum(_val,
+                       [bcc, cc, noreply, ofrom, replyroom, replyto, to])
+       of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"type">>, <<"address">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_address_attr_type(_val, _acc) ->
+    [{<<"type">>, enc_enum(_val)} | _acc].
+
+decode_address_attr_jid(__TopXMLNS, undefined) ->
+    undefined;
+decode_address_attr_jid(__TopXMLNS, _val) ->
+    case catch dec_jid(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"jid">>, <<"address">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_address_attr_jid(undefined, _acc) -> _acc;
+encode_address_attr_jid(_val, _acc) ->
+    [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_address_attr_desc(__TopXMLNS, undefined) ->
+    undefined;
+decode_address_attr_desc(__TopXMLNS, _val) -> _val.
+
+encode_address_attr_desc(undefined, _acc) -> _acc;
+encode_address_attr_desc(_val, _acc) ->
+    [{<<"desc">>, _val} | _acc].
+
+decode_address_attr_node(__TopXMLNS, undefined) ->
+    undefined;
+decode_address_attr_node(__TopXMLNS, _val) -> _val.
+
+encode_address_attr_node(undefined, _acc) -> _acc;
+encode_address_attr_node(_val, _acc) ->
+    [{<<"node">>, _val} | _acc].
+
+decode_address_attr_delivered(__TopXMLNS, undefined) ->
+    undefined;
+decode_address_attr_delivered(__TopXMLNS, _val) ->
+    case catch dec_bool(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"delivered">>, <<"address">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_address_attr_delivered(undefined, _acc) -> _acc;
+encode_address_attr_delivered(_val, _acc) ->
+    [{<<"delivered">>, enc_bool(_val)} | _acc].
+
+decode_nick(__TopXMLNS, __IgnoreEls,
+           {xmlel, <<"nick">>, _attrs, _els}) ->
+    Name = decode_nick_els(__TopXMLNS, __IgnoreEls, _els,
+                          <<>>),
+    {nick, Name}.
+
+decode_nick_els(__TopXMLNS, __IgnoreEls, [], Name) ->
+    decode_nick_cdata(__TopXMLNS, Name);
+decode_nick_els(__TopXMLNS, __IgnoreEls,
+               [{xmlcdata, _data} | _els], Name) ->
+    decode_nick_els(__TopXMLNS, __IgnoreEls, _els,
+                   <<Name/binary, _data/binary>>);
+decode_nick_els(__TopXMLNS, __IgnoreEls, [_ | _els],
+               Name) ->
+    decode_nick_els(__TopXMLNS, __IgnoreEls, _els, Name).
+
+encode_nick({nick, Name}, _xmlns_attrs) ->
+    _els = encode_nick_cdata(Name, []),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"nick">>, _attrs, _els}.
+
+decode_nick_cdata(__TopXMLNS, <<>>) ->
+    erlang:error({xmpp_codec,
+                 {missing_cdata, <<>>, <<"nick">>, __TopXMLNS}});
+decode_nick_cdata(__TopXMLNS, _val) -> _val.
+
+encode_nick_cdata(_val, _acc) ->
+    [{xmlcdata, _val} | _acc].
+
 decode_expire(__TopXMLNS, __IgnoreEls,
              {xmlel, <<"x">>, _attrs, _els}) ->
     {Seconds, Stored} = decode_expire_attrs(__TopXMLNS,
@@ -3745,6 +4199,19 @@ encode_search_instructions_cdata(undefined, _acc) ->
 encode_search_instructions_cdata(_val, _acc) ->
     [{xmlcdata, _val} | _acc].
 
+decode_hint_no_permanent_storage(__TopXMLNS,
+                                __IgnoreEls,
+                                {xmlel, <<"no-permanent-storage">>, _attrs,
+                                 _els}) ->
+    {hint, 'no-permanent-storage'}.
+
+encode_hint_no_permanent_storage({hint,
+                                 'no-permanent-storage'},
+                                _xmlns_attrs) ->
+    _els = [],
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"no-permanent-storage">>, _attrs, _els}.
+
 decode_hint_no_permanent_store(__TopXMLNS, __IgnoreEls,
                               {xmlel, <<"no-permanent-store">>, _attrs,
                                _els}) ->
@@ -5380,7 +5847,8 @@ encode_mam_fin_attr_complete(_val, _acc) ->
 decode_mam_prefs(__TopXMLNS, __IgnoreEls,
                 {xmlel, <<"prefs">>, _attrs, _els}) ->
     {Never, Always} = decode_mam_prefs_els(__TopXMLNS,
-                                          __IgnoreEls, _els, [], []),
+                                          __IgnoreEls, _els, undefined,
+                                          undefined),
     {Default, Xmlns} = decode_mam_prefs_attrs(__TopXMLNS,
                                              _attrs, undefined, undefined),
     {mam_prefs, Xmlns, Default, Always, Never}.
@@ -5392,10 +5860,23 @@ decode_mam_prefs_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"always">>, _attrs, _} = _el | _els], Never,
                     Always) ->
     case get_attr(<<"xmlns">>, _attrs) of
-      <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
+      <<"">>
+         when __TopXMLNS == <<"urn:xmpp:mam:1">>;
+              __TopXMLNS == <<"urn:xmpp:mam:0">>;
+              __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
                               Never,
                               decode_mam_always(__TopXMLNS, __IgnoreEls, _el));
+      <<"urn:xmpp:mam:0">> ->
+         decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
+                              Never,
+                              decode_mam_always(<<"urn:xmpp:mam:0">>,
+                                                __IgnoreEls, _el));
+      <<"urn:xmpp:mam:1">> ->
+         decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
+                              Never,
+                              decode_mam_always(<<"urn:xmpp:mam:1">>,
+                                                __IgnoreEls, _el));
       <<"urn:xmpp:mam:tmp">> ->
          decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
                               Never,
@@ -5409,10 +5890,23 @@ decode_mam_prefs_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"never">>, _attrs, _} = _el | _els], Never,
                     Always) ->
     case get_attr(<<"xmlns">>, _attrs) of
-      <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
+      <<"">>
+         when __TopXMLNS == <<"urn:xmpp:mam:1">>;
+              __TopXMLNS == <<"urn:xmpp:mam:0">>;
+              __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
                               decode_mam_never(__TopXMLNS, __IgnoreEls, _el),
                               Always);
+      <<"urn:xmpp:mam:0">> ->
+         decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
+                              decode_mam_never(<<"urn:xmpp:mam:0">>,
+                                               __IgnoreEls, _el),
+                              Always);
+      <<"urn:xmpp:mam:1">> ->
+         decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
+                              decode_mam_never(<<"urn:xmpp:mam:1">>,
+                                               __IgnoreEls, _el),
+                              Always);
       <<"urn:xmpp:mam:tmp">> ->
          decode_mam_prefs_els(__TopXMLNS, __IgnoreEls, _els,
                               decode_mam_never(<<"urn:xmpp:mam:tmp">>,
@@ -5454,11 +5948,11 @@ encode_mam_prefs({mam_prefs, Xmlns, Default, Always,
                                                                       _xmlns_attrs)),
     {xmlel, <<"prefs">>, _attrs, _els}.
 
-'encode_mam_prefs_$never'([], _acc) -> _acc;
+'encode_mam_prefs_$never'(undefined, _acc) -> _acc;
 'encode_mam_prefs_$never'(Never, _acc) ->
     [encode_mam_never(Never, []) | _acc].
 
-'encode_mam_prefs_$always'([], _acc) -> _acc;
+'encode_mam_prefs_$always'(undefined, _acc) -> _acc;
 'encode_mam_prefs_$always'(Always, _acc) ->
     [encode_mam_always(Always, []) | _acc].
 
@@ -5497,12 +5991,31 @@ decode_mam_always_els(__TopXMLNS, __IgnoreEls, [],
 decode_mam_always_els(__TopXMLNS, __IgnoreEls,
                      [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) ->
     case get_attr(<<"xmlns">>, _attrs) of
-      <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
+      <<"">>
+         when __TopXMLNS == <<"urn:xmpp:mam:1">>;
+              __TopXMLNS == <<"urn:xmpp:mam:0">>;
+              __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
                                case decode_mam_jid(__TopXMLNS, __IgnoreEls,
                                                    _el)
                                    of
-                                 [] -> Jids;
+                                 undefined -> Jids;
+                                 _new_el -> [_new_el | Jids]
+                               end);
+      <<"urn:xmpp:mam:0">> ->
+         decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
+                               case decode_mam_jid(<<"urn:xmpp:mam:0">>,
+                                                   __IgnoreEls, _el)
+                                   of
+                                 undefined -> Jids;
+                                 _new_el -> [_new_el | Jids]
+                               end);
+      <<"urn:xmpp:mam:1">> ->
+         decode_mam_always_els(__TopXMLNS, __IgnoreEls, _els,
+                               case decode_mam_jid(<<"urn:xmpp:mam:1">>,
+                                                   __IgnoreEls, _el)
+                                   of
+                                 undefined -> Jids;
                                  _new_el -> [_new_el | Jids]
                                end);
       <<"urn:xmpp:mam:tmp">> ->
@@ -5510,7 +6023,7 @@ decode_mam_always_els(__TopXMLNS, __IgnoreEls,
                                case decode_mam_jid(<<"urn:xmpp:mam:tmp">>,
                                                    __IgnoreEls, _el)
                                    of
-                                 [] -> Jids;
+                                 undefined -> Jids;
                                  _new_el -> [_new_el | Jids]
                                end);
       _ ->
@@ -5545,11 +6058,30 @@ decode_mam_never_els(__TopXMLNS, __IgnoreEls, [],
 decode_mam_never_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"jid">>, _attrs, _} = _el | _els], Jids) ->
     case get_attr(<<"xmlns">>, _attrs) of
-      <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
+      <<"">>
+         when __TopXMLNS == <<"urn:xmpp:mam:1">>;
+              __TopXMLNS == <<"urn:xmpp:mam:0">>;
+              __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
                               case decode_mam_jid(__TopXMLNS, __IgnoreEls, _el)
                                   of
-                                [] -> Jids;
+                                undefined -> Jids;
+                                _new_el -> [_new_el | Jids]
+                              end);
+      <<"urn:xmpp:mam:0">> ->
+         decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
+                              case decode_mam_jid(<<"urn:xmpp:mam:0">>,
+                                                  __IgnoreEls, _el)
+                                  of
+                                undefined -> Jids;
+                                _new_el -> [_new_el | Jids]
+                              end);
+      <<"urn:xmpp:mam:1">> ->
+         decode_mam_never_els(__TopXMLNS, __IgnoreEls, _els,
+                              case decode_mam_jid(<<"urn:xmpp:mam:1">>,
+                                                  __IgnoreEls, _el)
+                                  of
+                                undefined -> Jids;
                                 _new_el -> [_new_el | Jids]
                               end);
       <<"urn:xmpp:mam:tmp">> ->
@@ -5557,7 +6089,7 @@ decode_mam_never_els(__TopXMLNS, __IgnoreEls,
                               case decode_mam_jid(<<"urn:xmpp:mam:tmp">>,
                                                   __IgnoreEls, _el)
                                   of
-                                [] -> Jids;
+                                undefined -> Jids;
                                 _new_el -> [_new_el | Jids]
                               end);
       _ ->
@@ -5759,104 +6291,125 @@ encode_mam_archived_attr_by(_val, _acc) ->
 
 decode_mam_query(__TopXMLNS, __IgnoreEls,
                 {xmlel, <<"query">>, _attrs, _els}) ->
-    {Xdata, End, Start, With, Rsm} =
+    {Xdata, Withtext, End, Start, With, Rsm} =
        decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
                             undefined, undefined, undefined, undefined,
-                            undefined),
+                            undefined, undefined),
     {Id, Xmlns} = decode_mam_query_attrs(__TopXMLNS, _attrs,
                                         undefined, undefined),
-    {mam_query, Xmlns, Id, Start, End, With, Rsm, Xdata}.
+    {mam_query, Xmlns, Id, Start, End, With, Withtext, Rsm,
+     Xdata}.
 
 decode_mam_query_els(__TopXMLNS, __IgnoreEls, [], Xdata,
-                    End, Start, With, Rsm) ->
-    {Xdata, End, Start, With, Rsm};
+                    Withtext, End, Start, With, Rsm) ->
+    {Xdata, Withtext, End, Start, With, Rsm};
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"start">>, _attrs, _} = _el | _els], Xdata,
-                    End, Start, With, Rsm) ->
+                    Withtext, End, Start, With, Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End,
+                              Xdata, Withtext, End,
                               decode_mam_start(__TopXMLNS, __IgnoreEls, _el),
                               With, Rsm);
       <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End,
+                              Xdata, Withtext, End,
                               decode_mam_start(<<"urn:xmpp:mam:tmp">>,
                                                __IgnoreEls, _el),
                               With, Rsm);
       _ ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With, Rsm)
+                              Xdata, Withtext, End, Start, With, Rsm)
     end;
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"end">>, _attrs, _} = _el | _els], Xdata,
-                    End, Start, With, Rsm) ->
+                    Withtext, End, Start, With, Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata,
+                              Xdata, Withtext,
                               decode_mam_end(__TopXMLNS, __IgnoreEls, _el),
                               Start, With, Rsm);
       <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata,
+                              Xdata, Withtext,
                               decode_mam_end(<<"urn:xmpp:mam:tmp">>,
                                              __IgnoreEls, _el),
                               Start, With, Rsm);
       _ ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With, Rsm)
+                              Xdata, Withtext, End, Start, With, Rsm)
     end;
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"with">>, _attrs, _} = _el | _els], Xdata,
-                    End, Start, With, Rsm) ->
+                    Withtext, End, Start, With, Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start,
+                              Xdata, Withtext, End, Start,
                               decode_mam_with(__TopXMLNS, __IgnoreEls, _el),
                               Rsm);
       <<"urn:xmpp:mam:tmp">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start,
+                              Xdata, Withtext, End, Start,
                               decode_mam_with(<<"urn:xmpp:mam:tmp">>,
                                               __IgnoreEls, _el),
                               Rsm);
       _ ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With, Rsm)
+                              Xdata, Withtext, End, Start, With, Rsm)
+    end;
+decode_mam_query_els(__TopXMLNS, __IgnoreEls,
+                    [{xmlel, <<"withtext">>, _attrs, _} = _el | _els],
+                    Xdata, Withtext, End, Start, With, Rsm) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">> when __TopXMLNS == <<"urn:xmpp:mam:tmp">> ->
+         decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
+                              Xdata,
+                              decode_mam_withtext(__TopXMLNS, __IgnoreEls,
+                                                  _el),
+                              End, Start, With, Rsm);
+      <<"urn:xmpp:mam:tmp">> ->
+         decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
+                              Xdata,
+                              decode_mam_withtext(<<"urn:xmpp:mam:tmp">>,
+                                                  __IgnoreEls, _el),
+                              End, Start, With, Rsm);
+      _ ->
+         decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
+                              Xdata, Withtext, End, Start, With, Rsm)
     end;
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
                     [{xmlel, <<"set">>, _attrs, _} = _el | _els], Xdata,
-                    End, Start, With, Rsm) ->
+                    Withtext, End, Start, With, Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"http://jabber.org/protocol/rsm">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With,
+                              Xdata, Withtext, End, Start, With,
                               decode_rsm_set(<<"http://jabber.org/protocol/rsm">>,
                                              __IgnoreEls, _el));
       _ ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With, Rsm)
+                              Xdata, Withtext, End, Start, With, Rsm)
     end;
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
-                    [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata, End,
-                    Start, With, Rsm) ->
+                    [{xmlel, <<"x">>, _attrs, _} = _el | _els], Xdata,
+                    Withtext, End, Start, With, Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"jabber:x:data">> ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
                               decode_xdata(<<"jabber:x:data">>, __IgnoreEls,
                                            _el),
-                              End, Start, With, Rsm);
+                              Withtext, End, Start, With, Rsm);
       _ ->
          decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                              Xdata, End, Start, With, Rsm)
+                              Xdata, Withtext, End, Start, With, Rsm)
     end;
 decode_mam_query_els(__TopXMLNS, __IgnoreEls,
-                    [_ | _els], Xdata, End, Start, With, Rsm) ->
+                    [_ | _els], Xdata, Withtext, End, Start, With, Rsm) ->
     decode_mam_query_els(__TopXMLNS, __IgnoreEls, _els,
-                        Xdata, End, Start, With, Rsm).
+                        Xdata, Withtext, End, Start, With, Rsm).
 
 decode_mam_query_attrs(__TopXMLNS,
                       [{<<"queryid">>, _val} | _attrs], _Id, Xmlns) ->
@@ -5872,14 +6425,15 @@ decode_mam_query_attrs(__TopXMLNS, [], Id, Xmlns) ->
      decode_mam_query_attr_xmlns(__TopXMLNS, Xmlns)}.
 
 encode_mam_query({mam_query, Xmlns, Id, Start, End,
-                 With, Rsm, Xdata},
+                 With, Withtext, Rsm, Xdata},
                 _xmlns_attrs) ->
     _els = lists:reverse('encode_mam_query_$xdata'(Xdata,
-                                                  'encode_mam_query_$end'(End,
-                                                                          'encode_mam_query_$start'(Start,
-                                                                                                    'encode_mam_query_$with'(With,
-                                                                                                                             'encode_mam_query_$rsm'(Rsm,
-                                                                                                                                                     [])))))),
+                                                  'encode_mam_query_$withtext'(Withtext,
+                                                                               'encode_mam_query_$end'(End,
+                                                                                                       'encode_mam_query_$start'(Start,
+                                                                                                                                 'encode_mam_query_$with'(With,
+                                                                                                                                                          'encode_mam_query_$rsm'(Rsm,
+                                                                                                                                                                                  []))))))),
     _attrs = encode_mam_query_attr_xmlns(Xmlns,
                                         encode_mam_query_attr_queryid(Id,
                                                                       _xmlns_attrs)),
@@ -5891,6 +6445,10 @@ encode_mam_query({mam_query, Xmlns, Id, Start, End,
                  [{<<"xmlns">>, <<"jabber:x:data">>}])
      | _acc].
 
+'encode_mam_query_$withtext'(undefined, _acc) -> _acc;
+'encode_mam_query_$withtext'(Withtext, _acc) ->
+    [encode_mam_withtext(Withtext, []) | _acc].
+
 'encode_mam_query_$end'(undefined, _acc) -> _acc;
 'encode_mam_query_$end'(End, _acc) ->
     [encode_mam_end(End, []) | _acc].
@@ -5925,6 +6483,37 @@ encode_mam_query_attr_xmlns(undefined, _acc) -> _acc;
 encode_mam_query_attr_xmlns(_val, _acc) ->
     [{<<"xmlns">>, _val} | _acc].
 
+decode_mam_withtext(__TopXMLNS, __IgnoreEls,
+                   {xmlel, <<"withtext">>, _attrs, _els}) ->
+    Cdata = decode_mam_withtext_els(__TopXMLNS, __IgnoreEls,
+                                   _els, <<>>),
+    Cdata.
+
+decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, [],
+                       Cdata) ->
+    decode_mam_withtext_cdata(__TopXMLNS, Cdata);
+decode_mam_withtext_els(__TopXMLNS, __IgnoreEls,
+                       [{xmlcdata, _data} | _els], Cdata) ->
+    decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, _els,
+                           <<Cdata/binary, _data/binary>>);
+decode_mam_withtext_els(__TopXMLNS, __IgnoreEls,
+                       [_ | _els], Cdata) ->
+    decode_mam_withtext_els(__TopXMLNS, __IgnoreEls, _els,
+                           Cdata).
+
+encode_mam_withtext(Cdata, _xmlns_attrs) ->
+    _els = encode_mam_withtext_cdata(Cdata, []),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"withtext">>, _attrs, _els}.
+
+decode_mam_withtext_cdata(__TopXMLNS, <<>>) ->
+    erlang:error({xmpp_codec,
+                 {missing_cdata, <<>>, <<"withtext">>, __TopXMLNS}});
+decode_mam_withtext_cdata(__TopXMLNS, _val) -> _val.
+
+encode_mam_withtext_cdata(_val, _acc) ->
+    [{xmlcdata, _val} | _acc].
+
 decode_mam_with(__TopXMLNS, __IgnoreEls,
                {xmlel, <<"with">>, _attrs, _els}) ->
     Cdata = decode_mam_with_els(__TopXMLNS, __IgnoreEls,
@@ -6455,10 +7044,10 @@ encode_rsm_before(Cdata, _xmlns_attrs) ->
     _attrs = _xmlns_attrs,
     {xmlel, <<"before">>, _attrs, _els}.
 
-decode_rsm_before_cdata(__TopXMLNS, <<>>) -> none;
+decode_rsm_before_cdata(__TopXMLNS, <<>>) -> <<>>;
 decode_rsm_before_cdata(__TopXMLNS, _val) -> _val.
 
-encode_rsm_before_cdata(none, _acc) -> _acc;
+encode_rsm_before_cdata(<<>>, _acc) -> _acc;
 encode_rsm_before_cdata(_val, _acc) ->
     [{xmlcdata, _val} | _acc].
 
@@ -6492,63 +7081,438 @@ encode_rsm_after_cdata(undefined, _acc) -> _acc;
 encode_rsm_after_cdata(_val, _acc) ->
     [{xmlcdata, _val} | _acc].
 
-decode_muc(__TopXMLNS, __IgnoreEls,
-          {xmlel, <<"x">>, _attrs, _els}) ->
-    History = decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
-                            undefined),
-    Password = decode_muc_attrs(__TopXMLNS, _attrs,
-                               undefined),
-    {muc, History, Password}.
+decode_muc_unsubscribe(__TopXMLNS, __IgnoreEls,
+                      {xmlel, <<"unsubscribe">>, _attrs, _els}) ->
+    {muc_unsubscribe}.
 
-decode_muc_els(__TopXMLNS, __IgnoreEls, [], History) ->
-    History;
+encode_muc_unsubscribe({muc_unsubscribe},
+                      _xmlns_attrs) ->
+    _els = [],
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"unsubscribe">>, _attrs, _els}.
+
+decode_muc_subscribe(__TopXMLNS, __IgnoreEls,
+                    {xmlel, <<"subscribe">>, _attrs, _els}) ->
+    Events = decode_muc_subscribe_els(__TopXMLNS,
+                                     __IgnoreEls, _els, []),
+    Nick = decode_muc_subscribe_attrs(__TopXMLNS, _attrs,
+                                     undefined),
+    {muc_subscribe, Nick, Events}.
+
+decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, [],
+                        Events) ->
+    lists:reverse(Events);
+decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls,
+                        [{xmlel, <<"event">>, _attrs, _} = _el | _els],
+                        Events) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">> when __TopXMLNS == <<"urn:xmpp:mucsub:0">> ->
+         decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els,
+                                  [decode_muc_subscribe_event(__TopXMLNS,
+                                                              __IgnoreEls, _el)
+                                   | Events]);
+      <<"urn:xmpp:mucsub:0">> ->
+         decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els,
+                                  [decode_muc_subscribe_event(<<"urn:xmpp:mucsub:0">>,
+                                                              __IgnoreEls, _el)
+                                   | Events]);
+      _ ->
+         decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els,
+                                  Events)
+    end;
+decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls,
+                        [_ | _els], Events) ->
+    decode_muc_subscribe_els(__TopXMLNS, __IgnoreEls, _els,
+                            Events).
+
+decode_muc_subscribe_attrs(__TopXMLNS,
+                          [{<<"nick">>, _val} | _attrs], _Nick) ->
+    decode_muc_subscribe_attrs(__TopXMLNS, _attrs, _val);
+decode_muc_subscribe_attrs(__TopXMLNS, [_ | _attrs],
+                          Nick) ->
+    decode_muc_subscribe_attrs(__TopXMLNS, _attrs, Nick);
+decode_muc_subscribe_attrs(__TopXMLNS, [], Nick) ->
+    decode_muc_subscribe_attr_nick(__TopXMLNS, Nick).
+
+encode_muc_subscribe({muc_subscribe, Nick, Events},
+                    _xmlns_attrs) ->
+    _els =
+       lists:reverse('encode_muc_subscribe_$events'(Events,
+                                                    [])),
+    _attrs = encode_muc_subscribe_attr_nick(Nick,
+                                           _xmlns_attrs),
+    {xmlel, <<"subscribe">>, _attrs, _els}.
+
+'encode_muc_subscribe_$events'([], _acc) -> _acc;
+'encode_muc_subscribe_$events'([Events | _els], _acc) ->
+    'encode_muc_subscribe_$events'(_els,
+                                  [encode_muc_subscribe_event(Events, [])
+                                   | _acc]).
+
+decode_muc_subscribe_attr_nick(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"nick">>, <<"subscribe">>,
+                  __TopXMLNS}});
+decode_muc_subscribe_attr_nick(__TopXMLNS, _val) ->
+    _val.
+
+encode_muc_subscribe_attr_nick(_val, _acc) ->
+    [{<<"nick">>, _val} | _acc].
+
+decode_muc_subscribe_event(__TopXMLNS, __IgnoreEls,
+                          {xmlel, <<"event">>, _attrs, _els}) ->
+    Node = decode_muc_subscribe_event_attrs(__TopXMLNS,
+                                           _attrs, undefined),
+    Node.
+
+decode_muc_subscribe_event_attrs(__TopXMLNS,
+                                [{<<"node">>, _val} | _attrs], _Node) ->
+    decode_muc_subscribe_event_attrs(__TopXMLNS, _attrs,
+                                    _val);
+decode_muc_subscribe_event_attrs(__TopXMLNS,
+                                [_ | _attrs], Node) ->
+    decode_muc_subscribe_event_attrs(__TopXMLNS, _attrs,
+                                    Node);
+decode_muc_subscribe_event_attrs(__TopXMLNS, [],
+                                Node) ->
+    decode_muc_subscribe_event_attr_node(__TopXMLNS, Node).
+
+encode_muc_subscribe_event(Node, _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_muc_subscribe_event_attr_node(Node,
+                                                 _xmlns_attrs),
+    {xmlel, <<"event">>, _attrs, _els}.
+
+decode_muc_subscribe_event_attr_node(__TopXMLNS,
+                                    undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"node">>, <<"event">>, __TopXMLNS}});
+decode_muc_subscribe_event_attr_node(__TopXMLNS,
+                                    _val) ->
+    _val.
+
+encode_muc_subscribe_event_attr_node(_val, _acc) ->
+    [{<<"node">>, _val} | _acc].
+
+decode_muc_subscriptions(__TopXMLNS, __IgnoreEls,
+                        {xmlel, <<"subscriptions">>, _attrs, _els}) ->
+    List = decode_muc_subscriptions_els(__TopXMLNS,
+                                       __IgnoreEls, _els, []),
+    {muc_subscriptions, List}.
+
+decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                            [], List) ->
+    lists:reverse(List);
+decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                            [{xmlel, <<"subscription">>, _attrs, _} = _el
+                             | _els],
+                            List) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">> when __TopXMLNS == <<"urn:xmpp:mucsub:0">> ->
+         decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                                      _els,
+                                      case decode_muc_subscription(__TopXMLNS,
+                                                                   __IgnoreEls,
+                                                                   _el)
+                                          of
+                                        undefined -> List;
+                                        _new_el -> [_new_el | List]
+                                      end);
+      <<"urn:xmpp:mucsub:0">> ->
+         decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                                      _els,
+                                      case
+                                        decode_muc_subscription(<<"urn:xmpp:mucsub:0">>,
+                                                                __IgnoreEls,
+                                                                _el)
+                                          of
+                                        undefined -> List;
+                                        _new_el -> [_new_el | List]
+                                      end);
+      _ ->
+         decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                                      _els, List)
+    end;
+decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                            [_ | _els], List) ->
+    decode_muc_subscriptions_els(__TopXMLNS, __IgnoreEls,
+                                _els, List).
+
+encode_muc_subscriptions({muc_subscriptions, List},
+                        _xmlns_attrs) ->
+    _els =
+       lists:reverse('encode_muc_subscriptions_$list'(List,
+                                                      [])),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"subscriptions">>, _attrs, _els}.
+
+'encode_muc_subscriptions_$list'([], _acc) -> _acc;
+'encode_muc_subscriptions_$list'([List | _els], _acc) ->
+    'encode_muc_subscriptions_$list'(_els,
+                                    [encode_muc_subscription(List, [])
+                                     | _acc]).
+
+decode_muc_subscription(__TopXMLNS, __IgnoreEls,
+                       {xmlel, <<"subscription">>, _attrs, _els}) ->
+    Jid = decode_muc_subscription_attrs(__TopXMLNS, _attrs,
+                                       undefined),
+    Jid.
+
+decode_muc_subscription_attrs(__TopXMLNS,
+                             [{<<"jid">>, _val} | _attrs], _Jid) ->
+    decode_muc_subscription_attrs(__TopXMLNS, _attrs, _val);
+decode_muc_subscription_attrs(__TopXMLNS, [_ | _attrs],
+                             Jid) ->
+    decode_muc_subscription_attrs(__TopXMLNS, _attrs, Jid);
+decode_muc_subscription_attrs(__TopXMLNS, [], Jid) ->
+    decode_muc_subscription_attr_jid(__TopXMLNS, Jid).
+
+encode_muc_subscription(Jid, _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_muc_subscription_attr_jid(Jid,
+                                             _xmlns_attrs),
+    {xmlel, <<"subscription">>, _attrs, _els}.
+
+decode_muc_subscription_attr_jid(__TopXMLNS,
+                                undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"jid">>, <<"subscription">>,
+                  __TopXMLNS}});
+decode_muc_subscription_attr_jid(__TopXMLNS, _val) ->
+    case catch dec_jid(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"jid">>, <<"subscription">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_muc_subscription_attr_jid(_val, _acc) ->
+    [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_x_conference(__TopXMLNS, __IgnoreEls,
+                   {xmlel, <<"x">>, _attrs, _els}) ->
+    {Jid, Password, Reason, Thread, Continue} =
+       decode_x_conference_attrs(__TopXMLNS, _attrs, undefined,
+                                 undefined, undefined, undefined, undefined),
+    {x_conference, Jid, Password, Reason, Continue, Thread}.
+
+decode_x_conference_attrs(__TopXMLNS,
+                         [{<<"jid">>, _val} | _attrs], _Jid, Password, Reason,
+                         Thread, Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, _val,
+                             Password, Reason, Thread, Continue);
+decode_x_conference_attrs(__TopXMLNS,
+                         [{<<"password">>, _val} | _attrs], Jid, _Password,
+                         Reason, Thread, Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, Jid, _val,
+                             Reason, Thread, Continue);
+decode_x_conference_attrs(__TopXMLNS,
+                         [{<<"reason">>, _val} | _attrs], Jid, Password,
+                         _Reason, Thread, Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, Jid,
+                             Password, _val, Thread, Continue);
+decode_x_conference_attrs(__TopXMLNS,
+                         [{<<"thread">>, _val} | _attrs], Jid, Password,
+                         Reason, _Thread, Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, Jid,
+                             Password, Reason, _val, Continue);
+decode_x_conference_attrs(__TopXMLNS,
+                         [{<<"continue">>, _val} | _attrs], Jid, Password,
+                         Reason, Thread, _Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, Jid,
+                             Password, Reason, Thread, _val);
+decode_x_conference_attrs(__TopXMLNS, [_ | _attrs], Jid,
+                         Password, Reason, Thread, Continue) ->
+    decode_x_conference_attrs(__TopXMLNS, _attrs, Jid,
+                             Password, Reason, Thread, Continue);
+decode_x_conference_attrs(__TopXMLNS, [], Jid, Password,
+                         Reason, Thread, Continue) ->
+    {decode_x_conference_attr_jid(__TopXMLNS, Jid),
+     decode_x_conference_attr_password(__TopXMLNS, Password),
+     decode_x_conference_attr_reason(__TopXMLNS, Reason),
+     decode_x_conference_attr_thread(__TopXMLNS, Thread),
+     decode_x_conference_attr_continue(__TopXMLNS,
+                                      Continue)}.
+
+encode_x_conference({x_conference, Jid, Password,
+                    Reason, Continue, Thread},
+                   _xmlns_attrs) ->
+    _els = [],
+    _attrs = encode_x_conference_attr_continue(Continue,
+                                              encode_x_conference_attr_thread(Thread,
+                                                                              encode_x_conference_attr_reason(Reason,
+                                                                                                              encode_x_conference_attr_password(Password,
+                                                                                                                                                encode_x_conference_attr_jid(Jid,
+                                                                                                                                                                             _xmlns_attrs))))),
+    {xmlel, <<"x">>, _attrs, _els}.
+
+decode_x_conference_attr_jid(__TopXMLNS, undefined) ->
+    erlang:error({xmpp_codec,
+                 {missing_attr, <<"jid">>, <<"x">>, __TopXMLNS}});
+decode_x_conference_attr_jid(__TopXMLNS, _val) ->
+    case catch dec_jid(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"jid">>, <<"x">>, __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_x_conference_attr_jid(_val, _acc) ->
+    [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_x_conference_attr_password(__TopXMLNS,
+                                 undefined) ->
+    <<>>;
+decode_x_conference_attr_password(__TopXMLNS, _val) ->
+    _val.
+
+encode_x_conference_attr_password(<<>>, _acc) -> _acc;
+encode_x_conference_attr_password(_val, _acc) ->
+    [{<<"password">>, _val} | _acc].
+
+decode_x_conference_attr_reason(__TopXMLNS,
+                               undefined) ->
+    <<>>;
+decode_x_conference_attr_reason(__TopXMLNS, _val) ->
+    _val.
+
+encode_x_conference_attr_reason(<<>>, _acc) -> _acc;
+encode_x_conference_attr_reason(_val, _acc) ->
+    [{<<"reason">>, _val} | _acc].
+
+decode_x_conference_attr_thread(__TopXMLNS,
+                               undefined) ->
+    <<>>;
+decode_x_conference_attr_thread(__TopXMLNS, _val) ->
+    _val.
+
+encode_x_conference_attr_thread(<<>>, _acc) -> _acc;
+encode_x_conference_attr_thread(_val, _acc) ->
+    [{<<"thread">>, _val} | _acc].
+
+decode_x_conference_attr_continue(__TopXMLNS,
+                                 undefined) ->
+    undefined;
+decode_x_conference_attr_continue(__TopXMLNS, _val) ->
+    case catch dec_bool(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"continue">>, <<"x">>, __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_x_conference_attr_continue(undefined, _acc) ->
+    _acc;
+encode_x_conference_attr_continue(_val, _acc) ->
+    [{<<"continue">>, enc_bool(_val)} | _acc].
+
+decode_muc_unique(__TopXMLNS, __IgnoreEls,
+                 {xmlel, <<"unique">>, _attrs, _els}) ->
+    Name = decode_muc_unique_els(__TopXMLNS, __IgnoreEls,
+                                _els, <<>>),
+    {muc_unique, Name}.
+
+decode_muc_unique_els(__TopXMLNS, __IgnoreEls, [],
+                     Name) ->
+    decode_muc_unique_cdata(__TopXMLNS, Name);
+decode_muc_unique_els(__TopXMLNS, __IgnoreEls,
+                     [{xmlcdata, _data} | _els], Name) ->
+    decode_muc_unique_els(__TopXMLNS, __IgnoreEls, _els,
+                         <<Name/binary, _data/binary>>);
+decode_muc_unique_els(__TopXMLNS, __IgnoreEls,
+                     [_ | _els], Name) ->
+    decode_muc_unique_els(__TopXMLNS, __IgnoreEls, _els,
+                         Name).
+
+encode_muc_unique({muc_unique, Name}, _xmlns_attrs) ->
+    _els = encode_muc_unique_cdata(Name, []),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"unique">>, _attrs, _els}.
+
+decode_muc_unique_cdata(__TopXMLNS, <<>>) -> <<>>;
+decode_muc_unique_cdata(__TopXMLNS, _val) -> _val.
+
+encode_muc_unique_cdata(<<>>, _acc) -> _acc;
+encode_muc_unique_cdata(_val, _acc) ->
+    [{xmlcdata, _val} | _acc].
+
+decode_muc(__TopXMLNS, __IgnoreEls,
+          {xmlel, <<"x">>, _attrs, _els}) ->
+    {Password, History} = decode_muc_els(__TopXMLNS,
+                                        __IgnoreEls, _els, undefined,
+                                        undefined),
+    {muc, History, Password}.
+
+decode_muc_els(__TopXMLNS, __IgnoreEls, [], Password,
+              History) ->
+    {Password, History};
 decode_muc_els(__TopXMLNS, __IgnoreEls,
               [{xmlel, <<"history">>, _attrs, _} = _el | _els],
-              History) ->
+              Password, History) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc">> ->
-         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password,
                         decode_muc_history(__TopXMLNS, __IgnoreEls, _el));
       <<"http://jabber.org/protocol/muc">> ->
-         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password,
                         decode_muc_history(<<"http://jabber.org/protocol/muc">>,
                                            __IgnoreEls, _el));
       _ ->
-         decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History)
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password,
+                        History)
+    end;
+decode_muc_els(__TopXMLNS, __IgnoreEls,
+              [{xmlel, <<"password">>, _attrs, _} = _el | _els],
+              Password, History) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc">> ->
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+                        decode_muc_password(__TopXMLNS, __IgnoreEls, _el),
+                        History);
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+                        decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>,
+                                            __IgnoreEls, _el),
+                        History);
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+                        decode_muc_password(<<"http://jabber.org/protocol/muc#user">>,
+                                            __IgnoreEls, _el),
+                        History);
+      <<"http://jabber.org/protocol/muc">> ->
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els,
+                        decode_muc_password(<<"http://jabber.org/protocol/muc">>,
+                                            __IgnoreEls, _el),
+                        History);
+      _ ->
+         decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password,
+                        History)
     end;
 decode_muc_els(__TopXMLNS, __IgnoreEls, [_ | _els],
-              History) ->
-    decode_muc_els(__TopXMLNS, __IgnoreEls, _els, History).
-
-decode_muc_attrs(__TopXMLNS,
-                [{<<"password">>, _val} | _attrs], _Password) ->
-    decode_muc_attrs(__TopXMLNS, _attrs, _val);
-decode_muc_attrs(__TopXMLNS, [_ | _attrs], Password) ->
-    decode_muc_attrs(__TopXMLNS, _attrs, Password);
-decode_muc_attrs(__TopXMLNS, [], Password) ->
-    decode_muc_attr_password(__TopXMLNS, Password).
+              Password, History) ->
+    decode_muc_els(__TopXMLNS, __IgnoreEls, _els, Password,
+                  History).
 
 encode_muc({muc, History, Password}, _xmlns_attrs) ->
-    _els = lists:reverse('encode_muc_$history'(History,
-                                              [])),
-    _attrs = encode_muc_attr_password(Password,
-                                     _xmlns_attrs),
+    _els = lists:reverse('encode_muc_$password'(Password,
+                                               'encode_muc_$history'(History,
+                                                                     []))),
+    _attrs = _xmlns_attrs,
     {xmlel, <<"x">>, _attrs, _els}.
 
+'encode_muc_$password'(undefined, _acc) -> _acc;
+'encode_muc_$password'(Password, _acc) ->
+    [encode_muc_password(Password, []) | _acc].
+
 'encode_muc_$history'(undefined, _acc) -> _acc;
 'encode_muc_$history'(History, _acc) ->
     [encode_muc_history(History, []) | _acc].
 
-decode_muc_attr_password(__TopXMLNS, undefined) ->
-    undefined;
-decode_muc_attr_password(__TopXMLNS, _val) -> _val.
-
-encode_muc_attr_password(undefined, _acc) -> _acc;
-encode_muc_attr_password(_val, _acc) ->
-    [{<<"password">>, _val} | _acc].
-
 decode_muc_admin(__TopXMLNS, __IgnoreEls,
                 {xmlel, <<"query">>, _attrs, _els}) ->
     Items = decode_muc_admin_els(__TopXMLNS, __IgnoreEls,
@@ -6593,37 +7557,6 @@ encode_muc_admin({muc_admin, Items}, _xmlns_attrs) ->
     'encode_muc_admin_$items'(_els,
                              [encode_muc_admin_item(Items, []) | _acc]).
 
-decode_muc_admin_reason(__TopXMLNS, __IgnoreEls,
-                       {xmlel, <<"reason">>, _attrs, _els}) ->
-    Cdata = decode_muc_admin_reason_els(__TopXMLNS,
-                                       __IgnoreEls, _els, <<>>),
-    Cdata.
-
-decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls, [],
-                           Cdata) ->
-    decode_muc_admin_reason_cdata(__TopXMLNS, Cdata);
-decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
-                           [{xmlcdata, _data} | _els], Cdata) ->
-    decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
-                               _els, <<Cdata/binary, _data/binary>>);
-decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
-                           [_ | _els], Cdata) ->
-    decode_muc_admin_reason_els(__TopXMLNS, __IgnoreEls,
-                               _els, Cdata).
-
-encode_muc_admin_reason(Cdata, _xmlns_attrs) ->
-    _els = encode_muc_admin_reason_cdata(Cdata, []),
-    _attrs = _xmlns_attrs,
-    {xmlel, <<"reason">>, _attrs, _els}.
-
-decode_muc_admin_reason_cdata(__TopXMLNS, <<>>) ->
-    undefined;
-decode_muc_admin_reason_cdata(__TopXMLNS, _val) -> _val.
-
-encode_muc_admin_reason_cdata(undefined, _acc) -> _acc;
-encode_muc_admin_reason_cdata(_val, _acc) ->
-    [{xmlcdata, _val} | _acc].
-
 decode_muc_admin_continue(__TopXMLNS, __IgnoreEls,
                          {xmlel, <<"continue">>, _attrs, _els}) ->
     Thread = decode_muc_admin_continue_attrs(__TopXMLNS,
@@ -6724,7 +7657,7 @@ decode_muc_admin_item(__TopXMLNS, __IgnoreEls,
                      {xmlel, <<"item">>, _attrs, _els}) ->
     {Actor, Continue, Reason} =
        decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
-                                 undefined, undefined, undefined),
+                                 undefined, undefined, <<>>),
     {Affiliation, Role, Jid, Nick} =
        decode_muc_admin_item_attrs(__TopXMLNS, _attrs,
                                    undefined, undefined, undefined, undefined),
@@ -6785,13 +7718,23 @@ decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls,
                 <<"http://jabber.org/protocol/muc#admin">> ->
          decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
                                    Actor, Continue,
-                                   decode_muc_admin_reason(__TopXMLNS,
-                                                           __IgnoreEls, _el));
+                                   decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                     _el));
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                     __IgnoreEls, _el));
       <<"http://jabber.org/protocol/muc#admin">> ->
          decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
                                    Actor, Continue,
-                                   decode_muc_admin_reason(<<"http://jabber.org/protocol/muc#admin">>,
-                                                           __IgnoreEls, _el));
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                     __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                     __IgnoreEls, _el));
       _ ->
          decode_muc_admin_item_els(__TopXMLNS, __IgnoreEls, _els,
                                    Actor, Continue, Reason)
@@ -6857,10 +7800,9 @@ encode_muc_admin_item({muc_item, Actor, Continue,
 'encode_muc_admin_item_$continue'(Continue, _acc) ->
     [encode_muc_admin_continue(Continue, []) | _acc].
 
-'encode_muc_admin_item_$reason'(undefined, _acc) ->
-    _acc;
+'encode_muc_admin_item_$reason'(<<>>, _acc) -> _acc;
 'encode_muc_admin_item_$reason'(Reason, _acc) ->
-    [encode_muc_admin_reason(Reason, []) | _acc].
+    [encode_muc_reason(Reason, []) | _acc].
 
 decode_muc_admin_item_attr_affiliation(__TopXMLNS,
                                       undefined) ->
@@ -6926,306 +7868,446 @@ encode_muc_admin_item_attr_nick(undefined, _acc) ->
 encode_muc_admin_item_attr_nick(_val, _acc) ->
     [{<<"nick">>, _val} | _acc].
 
+decode_muc_owner_item(__TopXMLNS, __IgnoreEls,
+                     {xmlel, <<"item">>, _attrs, _els}) ->
+    {Actor, Continue, Reason} =
+       decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                 undefined, undefined, <<>>),
+    {Affiliation, Role, Jid, Nick} =
+       decode_muc_owner_item_attrs(__TopXMLNS, _attrs,
+                                   undefined, undefined, undefined, undefined),
+    {muc_item, Actor, Continue, Reason, Affiliation, Role,
+     Jid, Nick}.
+
+decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, [],
+                         Actor, Continue, Reason) ->
+    {Actor, Continue, Reason};
+decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls,
+                         [{xmlel, <<"actor">>, _attrs, _} = _el | _els], Actor,
+                         Continue, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   decode_muc_admin_actor(<<"http://jabber.org/protocol/muc#admin">>,
+                                                          __IgnoreEls, _el),
+                                   Continue, Reason);
+      _ ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue, Reason)
+    end;
+decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls,
+                         [{xmlel, <<"continue">>, _attrs, _} = _el | _els],
+                         Actor, Continue, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor,
+                                   decode_muc_admin_continue(<<"http://jabber.org/protocol/muc#admin">>,
+                                                             __IgnoreEls, _el),
+                                   Reason);
+      _ ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue, Reason)
+    end;
+decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls,
+                         [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
+                         Actor, Continue, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                     _el));
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                     __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                     __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue,
+                                   decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                     __IgnoreEls, _el));
+      _ ->
+         decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                   Actor, Continue, Reason)
+    end;
+decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls,
+                         [_ | _els], Actor, Continue, Reason) ->
+    decode_muc_owner_item_els(__TopXMLNS, __IgnoreEls, _els,
+                             Actor, Continue, Reason).
+
+decode_muc_owner_item_attrs(__TopXMLNS,
+                           [{<<"affiliation">>, _val} | _attrs], _Affiliation,
+                           Role, Jid, Nick) ->
+    decode_muc_owner_item_attrs(__TopXMLNS, _attrs, _val,
+                               Role, Jid, Nick);
+decode_muc_owner_item_attrs(__TopXMLNS,
+                           [{<<"role">>, _val} | _attrs], Affiliation, _Role,
+                           Jid, Nick) ->
+    decode_muc_owner_item_attrs(__TopXMLNS, _attrs,
+                               Affiliation, _val, Jid, Nick);
+decode_muc_owner_item_attrs(__TopXMLNS,
+                           [{<<"jid">>, _val} | _attrs], Affiliation, Role,
+                           _Jid, Nick) ->
+    decode_muc_owner_item_attrs(__TopXMLNS, _attrs,
+                               Affiliation, Role, _val, Nick);
+decode_muc_owner_item_attrs(__TopXMLNS,
+                           [{<<"nick">>, _val} | _attrs], Affiliation, Role,
+                           Jid, _Nick) ->
+    decode_muc_owner_item_attrs(__TopXMLNS, _attrs,
+                               Affiliation, Role, Jid, _val);
+decode_muc_owner_item_attrs(__TopXMLNS, [_ | _attrs],
+                           Affiliation, Role, Jid, Nick) ->
+    decode_muc_owner_item_attrs(__TopXMLNS, _attrs,
+                               Affiliation, Role, Jid, Nick);
+decode_muc_owner_item_attrs(__TopXMLNS, [], Affiliation,
+                           Role, Jid, Nick) ->
+    {decode_muc_owner_item_attr_affiliation(__TopXMLNS,
+                                           Affiliation),
+     decode_muc_owner_item_attr_role(__TopXMLNS, Role),
+     decode_muc_owner_item_attr_jid(__TopXMLNS, Jid),
+     decode_muc_owner_item_attr_nick(__TopXMLNS, Nick)}.
+
+encode_muc_owner_item({muc_item, Actor, Continue,
+                      Reason, Affiliation, Role, Jid, Nick},
+                     _xmlns_attrs) ->
+    _els =
+       lists:reverse('encode_muc_owner_item_$actor'(Actor,
+                                                    'encode_muc_owner_item_$continue'(Continue,
+                                                                                      'encode_muc_owner_item_$reason'(Reason,
+                                                                                                                      [])))),
+    _attrs = encode_muc_owner_item_attr_nick(Nick,
+                                            encode_muc_owner_item_attr_jid(Jid,
+                                                                           encode_muc_owner_item_attr_role(Role,
+                                                                                                           encode_muc_owner_item_attr_affiliation(Affiliation,
+                                                                                                                                                  _xmlns_attrs)))),
+    {xmlel, <<"item">>, _attrs, _els}.
+
+'encode_muc_owner_item_$actor'(undefined, _acc) -> _acc;
+'encode_muc_owner_item_$actor'(Actor, _acc) ->
+    [encode_muc_admin_actor(Actor,
+                           [{<<"xmlns">>,
+                             <<"http://jabber.org/protocol/muc#admin">>}])
+     | _acc].
+
+'encode_muc_owner_item_$continue'(undefined, _acc) ->
+    _acc;
+'encode_muc_owner_item_$continue'(Continue, _acc) ->
+    [encode_muc_admin_continue(Continue,
+                              [{<<"xmlns">>,
+                                <<"http://jabber.org/protocol/muc#admin">>}])
+     | _acc].
+
+'encode_muc_owner_item_$reason'(<<>>, _acc) -> _acc;
+'encode_muc_owner_item_$reason'(Reason, _acc) ->
+    [encode_muc_reason(Reason, []) | _acc].
+
+decode_muc_owner_item_attr_affiliation(__TopXMLNS,
+                                      undefined) ->
+    undefined;
+decode_muc_owner_item_attr_affiliation(__TopXMLNS,
+                                      _val) ->
+    case catch dec_enum(_val,
+                       [admin, member, none, outcast, owner])
+       of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"affiliation">>, <<"item">>,
+                        __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_muc_owner_item_attr_affiliation(undefined,
+                                      _acc) ->
+    _acc;
+encode_muc_owner_item_attr_affiliation(_val, _acc) ->
+    [{<<"affiliation">>, enc_enum(_val)} | _acc].
+
+decode_muc_owner_item_attr_role(__TopXMLNS,
+                               undefined) ->
+    undefined;
+decode_muc_owner_item_attr_role(__TopXMLNS, _val) ->
+    case catch dec_enum(_val,
+                       [moderator, none, participant, visitor])
+       of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"role">>, <<"item">>, __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_muc_owner_item_attr_role(undefined, _acc) ->
+    _acc;
+encode_muc_owner_item_attr_role(_val, _acc) ->
+    [{<<"role">>, enc_enum(_val)} | _acc].
+
+decode_muc_owner_item_attr_jid(__TopXMLNS, undefined) ->
+    undefined;
+decode_muc_owner_item_attr_jid(__TopXMLNS, _val) ->
+    case catch dec_jid(_val) of
+      {'EXIT', _} ->
+         erlang:error({xmpp_codec,
+                       {bad_attr_value, <<"jid">>, <<"item">>, __TopXMLNS}});
+      _res -> _res
+    end.
+
+encode_muc_owner_item_attr_jid(undefined, _acc) -> _acc;
+encode_muc_owner_item_attr_jid(_val, _acc) ->
+    [{<<"jid">>, enc_jid(_val)} | _acc].
+
+decode_muc_owner_item_attr_nick(__TopXMLNS,
+                               undefined) ->
+    undefined;
+decode_muc_owner_item_attr_nick(__TopXMLNS, _val) ->
+    _val.
+
+encode_muc_owner_item_attr_nick(undefined, _acc) ->
+    _acc;
+encode_muc_owner_item_attr_nick(_val, _acc) ->
+    [{<<"nick">>, _val} | _acc].
+
 decode_muc_owner(__TopXMLNS, __IgnoreEls,
                 {xmlel, <<"query">>, _attrs, _els}) ->
-    {Config, Destroy} = decode_muc_owner_els(__TopXMLNS,
-                                            __IgnoreEls, _els, undefined,
-                                            undefined),
-    {muc_owner, Destroy, Config}.
+    {Items, Config, Destroy} =
+       decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els, [],
+                            undefined, undefined),
+    {muc_owner, Destroy, Config, Items}.
 
-decode_muc_owner_els(__TopXMLNS, __IgnoreEls, [],
+decode_muc_owner_els(__TopXMLNS, __IgnoreEls, [], Items,
                     Config, Destroy) ->
-    {Config, Destroy};
+    {lists:reverse(Items), Config, Destroy};
 decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
-                    [{xmlel, <<"destroy">>, _attrs, _} = _el | _els],
+                    [{xmlel, <<"destroy">>, _attrs, _} = _el | _els], Items,
                     Config, Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc#owner">> ->
          decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
-                              Config,
-                              decode_muc_owner_destroy(__TopXMLNS, __IgnoreEls,
-                                                       _el));
+                              Items, Config,
+                              decode_muc_destroy(__TopXMLNS, __IgnoreEls,
+                                                 _el));
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                              Items, Config,
+                              decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>,
+                                                 __IgnoreEls, _el));
       <<"http://jabber.org/protocol/muc#owner">> ->
          decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
-                              Config,
-                              decode_muc_owner_destroy(<<"http://jabber.org/protocol/muc#owner">>,
-                                                       __IgnoreEls, _el));
+                              Items, Config,
+                              decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>,
+                                                 __IgnoreEls, _el));
       _ ->
          decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
-                              Config, Destroy)
+                              Items, Config, Destroy)
     end;
 decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
-                    [{xmlel, <<"x">>, _attrs, _} = _el | _els], Config,
-                    Destroy) ->
+                    [{xmlel, <<"x">>, _attrs, _} = _el | _els], Items,
+                    Config, Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"jabber:x:data">> ->
          decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                              Items,
                               decode_xdata(<<"jabber:x:data">>, __IgnoreEls,
                                            _el),
                               Destroy);
       _ ->
          decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
-                              Config, Destroy)
+                              Items, Config, Destroy)
     end;
 decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
-                    [_ | _els], Config, Destroy) ->
-    decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
-                        Config, Destroy).
-
-encode_muc_owner({muc_owner, Destroy, Config},
-                _xmlns_attrs) ->
-    _els = lists:reverse('encode_muc_owner_$config'(Config,
-                                                   'encode_muc_owner_$destroy'(Destroy,
-                                                                               []))),
-    _attrs = _xmlns_attrs,
-    {xmlel, <<"query">>, _attrs, _els}.
-
-'encode_muc_owner_$config'(undefined, _acc) -> _acc;
-'encode_muc_owner_$config'(Config, _acc) ->
-    [encode_xdata(Config,
-                 [{<<"xmlns">>, <<"jabber:x:data">>}])
-     | _acc].
-
-'encode_muc_owner_$destroy'(undefined, _acc) -> _acc;
-'encode_muc_owner_$destroy'(Destroy, _acc) ->
-    [encode_muc_owner_destroy(Destroy, []) | _acc].
-
-decode_muc_owner_destroy(__TopXMLNS, __IgnoreEls,
-                        {xmlel, <<"destroy">>, _attrs, _els}) ->
-    {Password, Reason} =
-       decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                    _els, undefined, undefined),
-    Jid = decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs,
-                                        undefined),
-    {muc_owner_destroy, Jid, Reason, Password}.
-
-decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                            [], Password, Reason) ->
-    {Password, Reason};
-decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                            [{xmlel, <<"password">>, _attrs, _} = _el | _els],
-                            Password, Reason) ->
-    case get_attr(<<"xmlns">>, _attrs) of
-      <<"">>
-         when __TopXMLNS ==
-                <<"http://jabber.org/protocol/muc#owner">> ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els,
-                                      decode_muc_owner_password(__TopXMLNS,
-                                                                __IgnoreEls,
-                                                                _el),
-                                      Reason);
-      <<"http://jabber.org/protocol/muc#owner">> ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els,
-                                      decode_muc_owner_password(<<"http://jabber.org/protocol/muc#owner">>,
-                                                                __IgnoreEls,
-                                                                _el),
-                                      Reason);
-      _ ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els, Password, Reason)
-    end;
-decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                            [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
-                            Password, Reason) ->
+                    [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items,
+                    Config, Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc#owner">> ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els, Password,
-                                      decode_muc_owner_reason(__TopXMLNS,
-                                                              __IgnoreEls,
-                                                              _el));
+         decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                              [decode_muc_owner_item(__TopXMLNS, __IgnoreEls,
+                                                     _el)
+                               | Items],
+                              Config, Destroy);
       <<"http://jabber.org/protocol/muc#owner">> ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els, Password,
-                                      decode_muc_owner_reason(<<"http://jabber.org/protocol/muc#owner">>,
-                                                              __IgnoreEls,
-                                                              _el));
-      _ ->
-         decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                      _els, Password, Reason)
-    end;
-decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                            [_ | _els], Password, Reason) ->
-    decode_muc_owner_destroy_els(__TopXMLNS, __IgnoreEls,
-                                _els, Password, Reason).
-
-decode_muc_owner_destroy_attrs(__TopXMLNS,
-                              [{<<"jid">>, _val} | _attrs], _Jid) ->
-    decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs,
-                                  _val);
-decode_muc_owner_destroy_attrs(__TopXMLNS, [_ | _attrs],
-                              Jid) ->
-    decode_muc_owner_destroy_attrs(__TopXMLNS, _attrs, Jid);
-decode_muc_owner_destroy_attrs(__TopXMLNS, [], Jid) ->
-    decode_muc_owner_destroy_attr_jid(__TopXMLNS, Jid).
-
-encode_muc_owner_destroy({muc_owner_destroy, Jid,
-                         Reason, Password},
-                        _xmlns_attrs) ->
-    _els =
-       lists:reverse('encode_muc_owner_destroy_$password'(Password,
-                                                          'encode_muc_owner_destroy_$reason'(Reason,
-                                                                                             []))),
-    _attrs = encode_muc_owner_destroy_attr_jid(Jid,
-                                              _xmlns_attrs),
-    {xmlel, <<"destroy">>, _attrs, _els}.
-
-'encode_muc_owner_destroy_$password'(undefined, _acc) ->
-    _acc;
-'encode_muc_owner_destroy_$password'(Password, _acc) ->
-    [encode_muc_owner_password(Password, []) | _acc].
-
-'encode_muc_owner_destroy_$reason'(undefined, _acc) ->
-    _acc;
-'encode_muc_owner_destroy_$reason'(Reason, _acc) ->
-    [encode_muc_owner_reason(Reason, []) | _acc].
-
-decode_muc_owner_destroy_attr_jid(__TopXMLNS,
-                                 undefined) ->
-    undefined;
-decode_muc_owner_destroy_attr_jid(__TopXMLNS, _val) ->
-    case catch dec_jid(_val) of
-      {'EXIT', _} ->
-         erlang:error({xmpp_codec,
-                       {bad_attr_value, <<"jid">>, <<"destroy">>,
-                        __TopXMLNS}});
-      _res -> _res
-    end.
-
-encode_muc_owner_destroy_attr_jid(undefined, _acc) ->
-    _acc;
-encode_muc_owner_destroy_attr_jid(_val, _acc) ->
-    [{<<"jid">>, enc_jid(_val)} | _acc].
-
-decode_muc_owner_reason(__TopXMLNS, __IgnoreEls,
-                       {xmlel, <<"reason">>, _attrs, _els}) ->
-    Cdata = decode_muc_owner_reason_els(__TopXMLNS,
-                                       __IgnoreEls, _els, <<>>),
-    Cdata.
-
-decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls, [],
-                           Cdata) ->
-    decode_muc_owner_reason_cdata(__TopXMLNS, Cdata);
-decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
-                           [{xmlcdata, _data} | _els], Cdata) ->
-    decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
-                               _els, <<Cdata/binary, _data/binary>>);
-decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
-                           [_ | _els], Cdata) ->
-    decode_muc_owner_reason_els(__TopXMLNS, __IgnoreEls,
-                               _els, Cdata).
+         decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                              [decode_muc_owner_item(<<"http://jabber.org/protocol/muc#owner">>,
+                                                     __IgnoreEls, _el)
+                               | Items],
+                              Config, Destroy);
+      _ ->
+         decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                              Items, Config, Destroy)
+    end;
+decode_muc_owner_els(__TopXMLNS, __IgnoreEls,
+                    [_ | _els], Items, Config, Destroy) ->
+    decode_muc_owner_els(__TopXMLNS, __IgnoreEls, _els,
+                        Items, Config, Destroy).
 
-encode_muc_owner_reason(Cdata, _xmlns_attrs) ->
-    _els = encode_muc_owner_reason_cdata(Cdata, []),
+encode_muc_owner({muc_owner, Destroy, Config, Items},
+                _xmlns_attrs) ->
+    _els = lists:reverse('encode_muc_owner_$items'(Items,
+                                                  'encode_muc_owner_$config'(Config,
+                                                                             'encode_muc_owner_$destroy'(Destroy,
+                                                                                                         [])))),
     _attrs = _xmlns_attrs,
-    {xmlel, <<"reason">>, _attrs, _els}.
+    {xmlel, <<"query">>, _attrs, _els}.
 
-decode_muc_owner_reason_cdata(__TopXMLNS, <<>>) ->
-    undefined;
-decode_muc_owner_reason_cdata(__TopXMLNS, _val) -> _val.
+'encode_muc_owner_$items'([], _acc) -> _acc;
+'encode_muc_owner_$items'([Items | _els], _acc) ->
+    'encode_muc_owner_$items'(_els,
+                             [encode_muc_owner_item(Items, []) | _acc]).
 
-encode_muc_owner_reason_cdata(undefined, _acc) -> _acc;
-encode_muc_owner_reason_cdata(_val, _acc) ->
-    [{xmlcdata, _val} | _acc].
+'encode_muc_owner_$config'(undefined, _acc) -> _acc;
+'encode_muc_owner_$config'(Config, _acc) ->
+    [encode_xdata(Config,
+                 [{<<"xmlns">>, <<"jabber:x:data">>}])
+     | _acc].
 
-decode_muc_owner_password(__TopXMLNS, __IgnoreEls,
-                         {xmlel, <<"password">>, _attrs, _els}) ->
-    Cdata = decode_muc_owner_password_els(__TopXMLNS,
-                                         __IgnoreEls, _els, <<>>),
+'encode_muc_owner_$destroy'(undefined, _acc) -> _acc;
+'encode_muc_owner_$destroy'(Destroy, _acc) ->
+    [encode_muc_destroy(Destroy, []) | _acc].
+
+decode_muc_password(__TopXMLNS, __IgnoreEls,
+                   {xmlel, <<"password">>, _attrs, _els}) ->
+    Cdata = decode_muc_password_els(__TopXMLNS, __IgnoreEls,
+                                   _els, <<>>),
     Cdata.
 
-decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
-                             [], Cdata) ->
-    decode_muc_owner_password_cdata(__TopXMLNS, Cdata);
-decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
-                             [{xmlcdata, _data} | _els], Cdata) ->
-    decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
-                                 _els, <<Cdata/binary, _data/binary>>);
-decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
-                             [_ | _els], Cdata) ->
-    decode_muc_owner_password_els(__TopXMLNS, __IgnoreEls,
-                                 _els, Cdata).
+decode_muc_password_els(__TopXMLNS, __IgnoreEls, [],
+                       Cdata) ->
+    decode_muc_password_cdata(__TopXMLNS, Cdata);
+decode_muc_password_els(__TopXMLNS, __IgnoreEls,
+                       [{xmlcdata, _data} | _els], Cdata) ->
+    decode_muc_password_els(__TopXMLNS, __IgnoreEls, _els,
+                           <<Cdata/binary, _data/binary>>);
+decode_muc_password_els(__TopXMLNS, __IgnoreEls,
+                       [_ | _els], Cdata) ->
+    decode_muc_password_els(__TopXMLNS, __IgnoreEls, _els,
+                           Cdata).
 
-encode_muc_owner_password(Cdata, _xmlns_attrs) ->
-    _els = encode_muc_owner_password_cdata(Cdata, []),
+encode_muc_password(Cdata, _xmlns_attrs) ->
+    _els = encode_muc_password_cdata(Cdata, []),
     _attrs = _xmlns_attrs,
     {xmlel, <<"password">>, _attrs, _els}.
 
-decode_muc_owner_password_cdata(__TopXMLNS, <<>>) ->
+decode_muc_password_cdata(__TopXMLNS, <<>>) ->
     undefined;
-decode_muc_owner_password_cdata(__TopXMLNS, _val) ->
-    _val.
+decode_muc_password_cdata(__TopXMLNS, _val) -> _val.
 
-encode_muc_owner_password_cdata(undefined, _acc) ->
-    _acc;
-encode_muc_owner_password_cdata(_val, _acc) ->
+encode_muc_password_cdata(undefined, _acc) -> _acc;
+encode_muc_password_cdata(_val, _acc) ->
     [{xmlcdata, _val} | _acc].
 
 decode_muc_user(__TopXMLNS, __IgnoreEls,
                {xmlel, <<"x">>, _attrs, _els}) ->
-    {Status_codes, Items, Invites, Decline, Destroy} =
+    {Status_codes, Items, Invites, Password, Decline,
+     Destroy} =
        decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els, [],
-                           [], [], undefined, undefined),
-    Password = decode_muc_user_attrs(__TopXMLNS, _attrs,
-                                    undefined),
+                           [], [], undefined, undefined, undefined),
     {muc_user, Decline, Destroy, Invites, Items,
      Status_codes, Password}.
 
 decode_muc_user_els(__TopXMLNS, __IgnoreEls, [],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     {lists:reverse(Status_codes), lists:reverse(Items),
-     lists:reverse(Invites), Decline, Destroy};
+     lists:reverse(Invites), Password, Decline, Destroy};
 decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                    [{xmlel, <<"decline">>, _attrs, _} = _el | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites,
+                             Status_codes, Items, Invites, Password,
                              decode_muc_user_decline(__TopXMLNS, __IgnoreEls,
                                                      _el),
                              Destroy);
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites,
+                             Status_codes, Items, Invites, Password,
                              decode_muc_user_decline(<<"http://jabber.org/protocol/muc#user">>,
                                                      __IgnoreEls, _el),
                              Destroy);
       _ ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline, Destroy)
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
     end;
 decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                    [{xmlel, <<"destroy">>, _attrs, _} = _el | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline,
-                             decode_muc_user_destroy(__TopXMLNS, __IgnoreEls,
-                                                     _el));
+                             Status_codes, Items, Invites, Password, Decline,
+                             decode_muc_destroy(__TopXMLNS, __IgnoreEls, _el));
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline,
-                             decode_muc_user_destroy(<<"http://jabber.org/protocol/muc#user">>,
-                                                     __IgnoreEls, _el));
+                             Status_codes, Items, Invites, Password, Decline,
+                             decode_muc_destroy(<<"http://jabber.org/protocol/muc#user">>,
+                                                __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites, Password, Decline,
+                             decode_muc_destroy(<<"http://jabber.org/protocol/muc#owner">>,
+                                                __IgnoreEls, _el));
+      _ ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
+    end;
+decode_muc_user_els(__TopXMLNS, __IgnoreEls,
+                   [{xmlel, <<"password">>, _attrs, _} = _el | _els],
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites,
+                             decode_muc_password(__TopXMLNS, __IgnoreEls, _el),
+                             Decline, Destroy);
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites,
+                             decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>,
+                                                 __IgnoreEls, _el),
+                             Decline, Destroy);
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites,
+                             decode_muc_password(<<"http://jabber.org/protocol/muc#user">>,
+                                                 __IgnoreEls, _el),
+                             Decline, Destroy);
+      <<"http://jabber.org/protocol/muc">> ->
+         decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
+                             Status_codes, Items, Invites,
+                             decode_muc_password(<<"http://jabber.org/protocol/muc">>,
+                                                 __IgnoreEls, _el),
+                             Decline, Destroy);
       _ ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline, Destroy)
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
     end;
 decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                    [{xmlel, <<"invite">>, _attrs, _} = _el | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
@@ -7235,21 +8317,23 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                              [decode_muc_user_invite(__TopXMLNS, __IgnoreEls,
                                                      _el)
                               | Invites],
-                             Decline, Destroy);
+                             Password, Decline, Destroy);
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
                              Status_codes, Items,
                              [decode_muc_user_invite(<<"http://jabber.org/protocol/muc#user">>,
                                                      __IgnoreEls, _el)
                               | Invites],
-                             Decline, Destroy);
+                             Password, Decline, Destroy);
       _ ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline, Destroy)
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
     end;
 decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                    [{xmlel, <<"item">>, _attrs, _} = _el | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
@@ -7259,21 +8343,23 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                              [decode_muc_user_item(__TopXMLNS, __IgnoreEls,
                                                    _el)
                               | Items],
-                             Invites, Decline, Destroy);
+                             Invites, Password, Decline, Destroy);
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
                              Status_codes,
                              [decode_muc_user_item(<<"http://jabber.org/protocol/muc#user">>,
                                                    __IgnoreEls, _el)
                               | Items],
-                             Invites, Decline, Destroy);
+                             Invites, Password, Decline, Destroy);
       _ ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline, Destroy)
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
     end;
 decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                    [{xmlel, <<"status">>, _attrs, _} = _el | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
@@ -7285,7 +8371,7 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                                undefined -> Status_codes;
                                _new_el -> [_new_el | Status_codes]
                              end,
-                             Items, Invites, Decline, Destroy);
+                             Items, Invites, Password, Decline, Destroy);
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
                              case
@@ -7295,24 +8381,18 @@ decode_muc_user_els(__TopXMLNS, __IgnoreEls,
                                undefined -> Status_codes;
                                _new_el -> [_new_el | Status_codes]
                              end,
-                             Items, Invites, Decline, Destroy);
+                             Items, Invites, Password, Decline, Destroy);
       _ ->
          decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                             Status_codes, Items, Invites, Decline, Destroy)
+                             Status_codes, Items, Invites, Password, Decline,
+                             Destroy)
     end;
 decode_muc_user_els(__TopXMLNS, __IgnoreEls, [_ | _els],
-                   Status_codes, Items, Invites, Decline, Destroy) ->
+                   Status_codes, Items, Invites, Password, Decline,
+                   Destroy) ->
     decode_muc_user_els(__TopXMLNS, __IgnoreEls, _els,
-                       Status_codes, Items, Invites, Decline, Destroy).
-
-decode_muc_user_attrs(__TopXMLNS,
-                     [{<<"password">>, _val} | _attrs], _Password) ->
-    decode_muc_user_attrs(__TopXMLNS, _attrs, _val);
-decode_muc_user_attrs(__TopXMLNS, [_ | _attrs],
-                     Password) ->
-    decode_muc_user_attrs(__TopXMLNS, _attrs, Password);
-decode_muc_user_attrs(__TopXMLNS, [], Password) ->
-    decode_muc_user_attr_password(__TopXMLNS, Password).
+                       Status_codes, Items, Invites, Password, Decline,
+                       Destroy).
 
 encode_muc_user({muc_user, Decline, Destroy, Invites,
                 Items, Status_codes, Password},
@@ -7321,11 +8401,11 @@ encode_muc_user({muc_user, Decline, Destroy, Invites,
        lists:reverse('encode_muc_user_$status_codes'(Status_codes,
                                                      'encode_muc_user_$items'(Items,
                                                                               'encode_muc_user_$invites'(Invites,
-                                                                                                         'encode_muc_user_$decline'(Decline,
-                                                                                                                                    'encode_muc_user_$destroy'(Destroy,
-                                                                                                                                                               [])))))),
-    _attrs = encode_muc_user_attr_password(Password,
-                                          _xmlns_attrs),
+                                                                                                         'encode_muc_user_$password'(Password,
+                                                                                                                                     'encode_muc_user_$decline'(Decline,
+                                                                                                                                                                'encode_muc_user_$destroy'(Destroy,
+                                                                                                                                                                                           []))))))),
+    _attrs = _xmlns_attrs,
     {xmlel, <<"x">>, _attrs, _els}.
 
 'encode_muc_user_$status_codes'([], _acc) -> _acc;
@@ -7345,27 +8425,23 @@ encode_muc_user({muc_user, Decline, Destroy, Invites,
     'encode_muc_user_$invites'(_els,
                               [encode_muc_user_invite(Invites, []) | _acc]).
 
+'encode_muc_user_$password'(undefined, _acc) -> _acc;
+'encode_muc_user_$password'(Password, _acc) ->
+    [encode_muc_password(Password, []) | _acc].
+
 'encode_muc_user_$decline'(undefined, _acc) -> _acc;
 'encode_muc_user_$decline'(Decline, _acc) ->
     [encode_muc_user_decline(Decline, []) | _acc].
 
 'encode_muc_user_$destroy'(undefined, _acc) -> _acc;
 'encode_muc_user_$destroy'(Destroy, _acc) ->
-    [encode_muc_user_destroy(Destroy, []) | _acc].
-
-decode_muc_user_attr_password(__TopXMLNS, undefined) ->
-    undefined;
-decode_muc_user_attr_password(__TopXMLNS, _val) -> _val.
-
-encode_muc_user_attr_password(undefined, _acc) -> _acc;
-encode_muc_user_attr_password(_val, _acc) ->
-    [{<<"password">>, _val} | _acc].
+    [encode_muc_destroy(Destroy, []) | _acc].
 
 decode_muc_user_item(__TopXMLNS, __IgnoreEls,
                     {xmlel, <<"item">>, _attrs, _els}) ->
     {Actor, Continue, Reason} =
        decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
-                                undefined, undefined, undefined),
+                                undefined, undefined, <<>>),
     {Affiliation, Role, Jid, Nick} =
        decode_muc_user_item_attrs(__TopXMLNS, _attrs,
                                   undefined, undefined, undefined, undefined),
@@ -7426,13 +8502,23 @@ decode_muc_user_item_els(__TopXMLNS, __IgnoreEls,
                 <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
                                   Actor, Continue,
-                                  decode_muc_user_reason(__TopXMLNS,
-                                                         __IgnoreEls, _el));
+                                  decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                    _el));
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
                                   Actor, Continue,
-                                  decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
-                                                         __IgnoreEls, _el));
+                                  decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                    __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                  Actor, Continue,
+                                  decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                    __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
+                                  Actor, Continue,
+                                  decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                    __IgnoreEls, _el));
       _ ->
          decode_muc_user_item_els(__TopXMLNS, __IgnoreEls, _els,
                                   Actor, Continue, Reason)
@@ -7498,9 +8584,9 @@ encode_muc_user_item({muc_item, Actor, Continue, Reason,
 'encode_muc_user_item_$continue'(Continue, _acc) ->
     [encode_muc_user_continue(Continue, []) | _acc].
 
-'encode_muc_user_item_$reason'(undefined, _acc) -> _acc;
+'encode_muc_user_item_$reason'(<<>>, _acc) -> _acc;
 'encode_muc_user_item_$reason'(Reason, _acc) ->
-    [encode_muc_user_reason(Reason, []) | _acc].
+    [encode_muc_reason(Reason, []) | _acc].
 
 decode_muc_user_item_attr_affiliation(__TopXMLNS,
                                      undefined) ->
@@ -7695,39 +8781,72 @@ encode_muc_user_actor_attr_nick(_val, _acc) ->
 
 decode_muc_user_invite(__TopXMLNS, __IgnoreEls,
                       {xmlel, <<"invite">>, _attrs, _els}) ->
-    Reason = decode_muc_user_invite_els(__TopXMLNS,
-                                       __IgnoreEls, _els, undefined),
+    {Continue, Reason} =
+       decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                  _els, undefined, <<>>),
     {To, From} = decode_muc_user_invite_attrs(__TopXMLNS,
                                              _attrs, undefined, undefined),
-    {muc_invite, Reason, From, To}.
+    {muc_invite, Reason, From, To, Continue}.
 
 decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls, [],
-                          Reason) ->
-    Reason;
+                          Continue, Reason) ->
+    {Continue, Reason};
 decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
                           [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
-                          Reason) ->
+                          Continue, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                    _els, Continue,
+                                    decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                      _el));
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                    _els, Continue,
+                                    decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                      __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                    _els, Continue,
+                                    decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                      __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                    _els, Continue,
+                                    decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                      __IgnoreEls, _el));
+      _ ->
+         decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                                    _els, Continue, Reason)
+    end;
+decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
+                          [{xmlel, <<"continue">>, _attrs, _} = _el | _els],
+                          Continue, Reason) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
                 <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
                                     _els,
-                                    decode_muc_user_reason(__TopXMLNS,
-                                                           __IgnoreEls, _el));
+                                    decode_muc_user_continue(__TopXMLNS,
+                                                             __IgnoreEls, _el),
+                                    Reason);
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
                                     _els,
-                                    decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
-                                                           __IgnoreEls, _el));
+                                    decode_muc_user_continue(<<"http://jabber.org/protocol/muc#user">>,
+                                                             __IgnoreEls, _el),
+                                    Reason);
       _ ->
          decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
-                                    _els, Reason)
+                                    _els, Continue, Reason)
     end;
 decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
-                          [_ | _els], Reason) ->
+                          [_ | _els], Continue, Reason) ->
     decode_muc_user_invite_els(__TopXMLNS, __IgnoreEls,
-                              _els, Reason).
+                              _els, Continue, Reason).
 
 decode_muc_user_invite_attrs(__TopXMLNS,
                             [{<<"to">>, _val} | _attrs], _To, From) ->
@@ -7746,20 +8865,26 @@ decode_muc_user_invite_attrs(__TopXMLNS, [], To,
     {decode_muc_user_invite_attr_to(__TopXMLNS, To),
      decode_muc_user_invite_attr_from(__TopXMLNS, From)}.
 
-encode_muc_user_invite({muc_invite, Reason, From, To},
+encode_muc_user_invite({muc_invite, Reason, From, To,
+                       Continue},
                       _xmlns_attrs) ->
     _els =
-       lists:reverse('encode_muc_user_invite_$reason'(Reason,
-                                                      [])),
+       lists:reverse('encode_muc_user_invite_$continue'(Continue,
+                                                        'encode_muc_user_invite_$reason'(Reason,
+                                                                                         []))),
     _attrs = encode_muc_user_invite_attr_from(From,
                                              encode_muc_user_invite_attr_to(To,
                                                                             _xmlns_attrs)),
     {xmlel, <<"invite">>, _attrs, _els}.
 
-'encode_muc_user_invite_$reason'(undefined, _acc) ->
+'encode_muc_user_invite_$continue'(undefined, _acc) ->
     _acc;
+'encode_muc_user_invite_$continue'(Continue, _acc) ->
+    [encode_muc_user_continue(Continue, []) | _acc].
+
+'encode_muc_user_invite_$reason'(<<>>, _acc) -> _acc;
 'encode_muc_user_invite_$reason'(Reason, _acc) ->
-    [encode_muc_user_reason(Reason, []) | _acc].
+    [encode_muc_reason(Reason, []) | _acc].
 
 decode_muc_user_invite_attr_to(__TopXMLNS, undefined) ->
     undefined;
@@ -7792,69 +8917,125 @@ encode_muc_user_invite_attr_from(undefined, _acc) ->
 encode_muc_user_invite_attr_from(_val, _acc) ->
     [{<<"from">>, enc_jid(_val)} | _acc].
 
-decode_muc_user_destroy(__TopXMLNS, __IgnoreEls,
-                       {xmlel, <<"destroy">>, _attrs, _els}) ->
-    Reason = decode_muc_user_destroy_els(__TopXMLNS,
-                                        __IgnoreEls, _els, undefined),
-    Jid = decode_muc_user_destroy_attrs(__TopXMLNS, _attrs,
-                                       undefined),
-    {muc_user_destroy, Reason, Jid}.
+decode_muc_destroy(__TopXMLNS, __IgnoreEls,
+                  {xmlel, <<"destroy">>, _attrs, _els}) ->
+    {Password, Reason} = decode_muc_destroy_els(__TopXMLNS,
+                                               __IgnoreEls, _els, undefined,
+                                               <<>>),
+    {Jid, Xmlns} = decode_muc_destroy_attrs(__TopXMLNS,
+                                           _attrs, undefined, undefined),
+    {muc_destroy, Xmlns, Jid, Reason, Password}.
 
-decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls, [],
-                           Reason) ->
-    Reason;
-decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                           [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
-                           Reason) ->
+decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, [],
+                      Password, Reason) ->
+    {Password, Reason};
+decode_muc_destroy_els(__TopXMLNS, __IgnoreEls,
+                      [{xmlel, <<"reason">>, _attrs, _} = _el | _els],
+                      Password, Reason) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
-                <<"http://jabber.org/protocol/muc#user">> ->
-         decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                                     _els,
-                                     decode_muc_user_reason(__TopXMLNS,
-                                                            __IgnoreEls, _el));
+                <<"http://jabber.org/protocol/muc#user">>;
+              __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password,
+                                decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                  _el));
       <<"http://jabber.org/protocol/muc#user">> ->
-         decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                                     _els,
-                                     decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
-                                                            __IgnoreEls, _el));
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password,
+                                decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                  __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password,
+                                decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                  __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password,
+                                decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                  __IgnoreEls, _el));
       _ ->
-         decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                                     _els, Reason)
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password, Reason)
     end;
-decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                           [_ | _els], Reason) ->
-    decode_muc_user_destroy_els(__TopXMLNS, __IgnoreEls,
-                               _els, Reason).
-
-decode_muc_user_destroy_attrs(__TopXMLNS,
-                             [{<<"jid">>, _val} | _attrs], _Jid) ->
-    decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, _val);
-decode_muc_user_destroy_attrs(__TopXMLNS, [_ | _attrs],
-                             Jid) ->
-    decode_muc_user_destroy_attrs(__TopXMLNS, _attrs, Jid);
-decode_muc_user_destroy_attrs(__TopXMLNS, [], Jid) ->
-    decode_muc_user_destroy_attr_jid(__TopXMLNS, Jid).
-
-encode_muc_user_destroy({muc_user_destroy, Reason, Jid},
-                       _xmlns_attrs) ->
+decode_muc_destroy_els(__TopXMLNS, __IgnoreEls,
+                      [{xmlel, <<"password">>, _attrs, _} = _el | _els],
+                      Password, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"">>
+         when __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#user">>;
+              __TopXMLNS ==
+                <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                decode_muc_password(__TopXMLNS, __IgnoreEls,
+                                                    _el),
+                                Reason);
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                decode_muc_password(<<"http://jabber.org/protocol/muc#owner">>,
+                                                    __IgnoreEls, _el),
+                                Reason);
+      <<"http://jabber.org/protocol/muc#user">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                decode_muc_password(<<"http://jabber.org/protocol/muc#user">>,
+                                                    __IgnoreEls, _el),
+                                Reason);
+      <<"http://jabber.org/protocol/muc">> ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                decode_muc_password(<<"http://jabber.org/protocol/muc">>,
+                                                    __IgnoreEls, _el),
+                                Reason);
+      _ ->
+         decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                                Password, Reason)
+    end;
+decode_muc_destroy_els(__TopXMLNS, __IgnoreEls,
+                      [_ | _els], Password, Reason) ->
+    decode_muc_destroy_els(__TopXMLNS, __IgnoreEls, _els,
+                          Password, Reason).
+
+decode_muc_destroy_attrs(__TopXMLNS,
+                        [{<<"jid">>, _val} | _attrs], _Jid, Xmlns) ->
+    decode_muc_destroy_attrs(__TopXMLNS, _attrs, _val,
+                            Xmlns);
+decode_muc_destroy_attrs(__TopXMLNS,
+                        [{<<"xmlns">>, _val} | _attrs], Jid, _Xmlns) ->
+    decode_muc_destroy_attrs(__TopXMLNS, _attrs, Jid, _val);
+decode_muc_destroy_attrs(__TopXMLNS, [_ | _attrs], Jid,
+                        Xmlns) ->
+    decode_muc_destroy_attrs(__TopXMLNS, _attrs, Jid,
+                            Xmlns);
+decode_muc_destroy_attrs(__TopXMLNS, [], Jid, Xmlns) ->
+    {decode_muc_destroy_attr_jid(__TopXMLNS, Jid),
+     decode_muc_destroy_attr_xmlns(__TopXMLNS, Xmlns)}.
+
+encode_muc_destroy({muc_destroy, Xmlns, Jid, Reason,
+                   Password},
+                  _xmlns_attrs) ->
     _els =
-       lists:reverse('encode_muc_user_destroy_$reason'(Reason,
-                                                       [])),
-    _attrs = encode_muc_user_destroy_attr_jid(Jid,
-                                             _xmlns_attrs),
+       lists:reverse('encode_muc_destroy_$password'(Password,
+                                                    'encode_muc_destroy_$reason'(Reason,
+                                                                                 []))),
+    _attrs = encode_muc_destroy_attr_xmlns(Xmlns,
+                                          encode_muc_destroy_attr_jid(Jid,
+                                                                      _xmlns_attrs)),
     {xmlel, <<"destroy">>, _attrs, _els}.
 
-'encode_muc_user_destroy_$reason'(undefined, _acc) ->
-    _acc;
-'encode_muc_user_destroy_$reason'(Reason, _acc) ->
-    [encode_muc_user_reason(Reason, []) | _acc].
+'encode_muc_destroy_$password'(undefined, _acc) -> _acc;
+'encode_muc_destroy_$password'(Password, _acc) ->
+    [encode_muc_password(Password, []) | _acc].
 
-decode_muc_user_destroy_attr_jid(__TopXMLNS,
-                                undefined) ->
+'encode_muc_destroy_$reason'(<<>>, _acc) -> _acc;
+'encode_muc_destroy_$reason'(Reason, _acc) ->
+    [encode_muc_reason(Reason, []) | _acc].
+
+decode_muc_destroy_attr_jid(__TopXMLNS, undefined) ->
     undefined;
-decode_muc_user_destroy_attr_jid(__TopXMLNS, _val) ->
+decode_muc_destroy_attr_jid(__TopXMLNS, _val) ->
     case catch dec_jid(_val) of
       {'EXIT', _} ->
          erlang:error({xmpp_codec,
@@ -7863,15 +9044,22 @@ decode_muc_user_destroy_attr_jid(__TopXMLNS, _val) ->
       _res -> _res
     end.
 
-encode_muc_user_destroy_attr_jid(undefined, _acc) ->
-    _acc;
-encode_muc_user_destroy_attr_jid(_val, _acc) ->
+encode_muc_destroy_attr_jid(undefined, _acc) -> _acc;
+encode_muc_destroy_attr_jid(_val, _acc) ->
     [{<<"jid">>, enc_jid(_val)} | _acc].
 
+decode_muc_destroy_attr_xmlns(__TopXMLNS, undefined) ->
+    undefined;
+decode_muc_destroy_attr_xmlns(__TopXMLNS, _val) -> _val.
+
+encode_muc_destroy_attr_xmlns(undefined, _acc) -> _acc;
+encode_muc_destroy_attr_xmlns(_val, _acc) ->
+    [{<<"xmlns">>, _val} | _acc].
+
 decode_muc_user_decline(__TopXMLNS, __IgnoreEls,
                        {xmlel, <<"decline">>, _attrs, _els}) ->
     Reason = decode_muc_user_decline_els(__TopXMLNS,
-                                        __IgnoreEls, _els, undefined),
+                                        __IgnoreEls, _els, <<>>),
     {To, From} = decode_muc_user_decline_attrs(__TopXMLNS,
                                               _attrs, undefined, undefined),
     {muc_decline, Reason, From, To}.
@@ -7888,13 +9076,23 @@ decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
                 <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
                                      _els,
-                                     decode_muc_user_reason(__TopXMLNS,
-                                                            __IgnoreEls, _el));
+                                     decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                                                       _el));
       <<"http://jabber.org/protocol/muc#user">> ->
          decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
                                      _els,
-                                     decode_muc_user_reason(<<"http://jabber.org/protocol/muc#user">>,
-                                                            __IgnoreEls, _el));
+                                     decode_muc_reason(<<"http://jabber.org/protocol/muc#user">>,
+                                                       __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#admin">> ->
+         decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
+                                     _els,
+                                     decode_muc_reason(<<"http://jabber.org/protocol/muc#admin">>,
+                                                       __IgnoreEls, _el));
+      <<"http://jabber.org/protocol/muc#owner">> ->
+         decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
+                                     _els,
+                                     decode_muc_reason(<<"http://jabber.org/protocol/muc#owner">>,
+                                                       __IgnoreEls, _el));
       _ ->
          decode_muc_user_decline_els(__TopXMLNS, __IgnoreEls,
                                      _els, Reason)
@@ -7931,10 +9129,9 @@ encode_muc_user_decline({muc_decline, Reason, From, To},
                                                                               _xmlns_attrs)),
     {xmlel, <<"decline">>, _attrs, _els}.
 
-'encode_muc_user_decline_$reason'(undefined, _acc) ->
-    _acc;
+'encode_muc_user_decline_$reason'(<<>>, _acc) -> _acc;
 'encode_muc_user_decline_$reason'(Reason, _acc) ->
-    [encode_muc_user_reason(Reason, []) | _acc].
+    [encode_muc_reason(Reason, []) | _acc].
 
 decode_muc_user_decline_attr_to(__TopXMLNS,
                                undefined) ->
@@ -7969,35 +9166,34 @@ encode_muc_user_decline_attr_from(undefined, _acc) ->
 encode_muc_user_decline_attr_from(_val, _acc) ->
     [{<<"from">>, enc_jid(_val)} | _acc].
 
-decode_muc_user_reason(__TopXMLNS, __IgnoreEls,
-                      {xmlel, <<"reason">>, _attrs, _els}) ->
-    Cdata = decode_muc_user_reason_els(__TopXMLNS,
-                                      __IgnoreEls, _els, <<>>),
+decode_muc_reason(__TopXMLNS, __IgnoreEls,
+                 {xmlel, <<"reason">>, _attrs, _els}) ->
+    Cdata = decode_muc_reason_els(__TopXMLNS, __IgnoreEls,
+                                 _els, <<>>),
     Cdata.
 
-decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls, [],
-                          Cdata) ->
-    decode_muc_user_reason_cdata(__TopXMLNS, Cdata);
-decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
-                          [{xmlcdata, _data} | _els], Cdata) ->
-    decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
-                              _els, <<Cdata/binary, _data/binary>>);
-decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
-                          [_ | _els], Cdata) ->
-    decode_muc_user_reason_els(__TopXMLNS, __IgnoreEls,
-                              _els, Cdata).
+decode_muc_reason_els(__TopXMLNS, __IgnoreEls, [],
+                     Cdata) ->
+    decode_muc_reason_cdata(__TopXMLNS, Cdata);
+decode_muc_reason_els(__TopXMLNS, __IgnoreEls,
+                     [{xmlcdata, _data} | _els], Cdata) ->
+    decode_muc_reason_els(__TopXMLNS, __IgnoreEls, _els,
+                         <<Cdata/binary, _data/binary>>);
+decode_muc_reason_els(__TopXMLNS, __IgnoreEls,
+                     [_ | _els], Cdata) ->
+    decode_muc_reason_els(__TopXMLNS, __IgnoreEls, _els,
+                         Cdata).
 
-encode_muc_user_reason(Cdata, _xmlns_attrs) ->
-    _els = encode_muc_user_reason_cdata(Cdata, []),
+encode_muc_reason(Cdata, _xmlns_attrs) ->
+    _els = encode_muc_reason_cdata(Cdata, []),
     _attrs = _xmlns_attrs,
     {xmlel, <<"reason">>, _attrs, _els}.
 
-decode_muc_user_reason_cdata(__TopXMLNS, <<>>) ->
-    undefined;
-decode_muc_user_reason_cdata(__TopXMLNS, _val) -> _val.
+decode_muc_reason_cdata(__TopXMLNS, <<>>) -> undefined;
+decode_muc_reason_cdata(__TopXMLNS, _val) -> _val.
 
-encode_muc_user_reason_cdata(undefined, _acc) -> _acc;
-encode_muc_user_reason_cdata(_val, _acc) ->
+encode_muc_reason_cdata(undefined, _acc) -> _acc;
+encode_muc_reason_cdata(_val, _acc) ->
     [{xmlcdata, _val} | _acc].
 
 decode_muc_history(__TopXMLNS, __IgnoreEls,
@@ -10554,23 +11750,15 @@ decode_xdata_field_els(__TopXMLNS, __IgnoreEls,
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">> when __TopXMLNS == <<"jabber:x:data">> ->
          decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
-                                case decode_xdata_field_option(__TopXMLNS,
-                                                               __IgnoreEls,
-                                                               _el)
-                                    of
-                                  undefined -> Options;
-                                  _new_el -> [_new_el | Options]
-                                end,
+                                [decode_xdata_field_option(__TopXMLNS,
+                                                           __IgnoreEls, _el)
+                                 | Options],
                                 Values, Desc, Required);
       <<"jabber:x:data">> ->
          decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
-                                case
-                                  decode_xdata_field_option(<<"jabber:x:data">>,
-                                                            __IgnoreEls, _el)
-                                    of
-                                  undefined -> Options;
-                                  _new_el -> [_new_el | Options]
-                                end,
+                                [decode_xdata_field_option(<<"jabber:x:data">>,
+                                                           __IgnoreEls, _el)
+                                 | Options],
                                 Values, Desc, Required);
       _ ->
          decode_xdata_field_els(__TopXMLNS, __IgnoreEls, _els,
@@ -10675,7 +11863,9 @@ decode_xdata_field_option(__TopXMLNS, __IgnoreEls,
                          {xmlel, <<"option">>, _attrs, _els}) ->
     Value = decode_xdata_field_option_els(__TopXMLNS,
                                          __IgnoreEls, _els, error),
-    Value.
+    Label = decode_xdata_field_option_attrs(__TopXMLNS,
+                                           _attrs, undefined),
+    {xdata_option, Label, Value}.
 
 decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
                              [], Value) ->
@@ -10712,16 +11902,42 @@ decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
     decode_xdata_field_option_els(__TopXMLNS, __IgnoreEls,
                                  _els, Value).
 
-encode_xdata_field_option(Value, _xmlns_attrs) ->
+decode_xdata_field_option_attrs(__TopXMLNS,
+                               [{<<"label">>, _val} | _attrs], _Label) ->
+    decode_xdata_field_option_attrs(__TopXMLNS, _attrs,
+                                   _val);
+decode_xdata_field_option_attrs(__TopXMLNS,
+                               [_ | _attrs], Label) ->
+    decode_xdata_field_option_attrs(__TopXMLNS, _attrs,
+                                   Label);
+decode_xdata_field_option_attrs(__TopXMLNS, [],
+                               Label) ->
+    decode_xdata_field_option_attr_label(__TopXMLNS, Label).
+
+encode_xdata_field_option({xdata_option, Label, Value},
+                         _xmlns_attrs) ->
     _els =
        lists:reverse('encode_xdata_field_option_$value'(Value,
                                                         [])),
-    _attrs = _xmlns_attrs,
+    _attrs = encode_xdata_field_option_attr_label(Label,
+                                                 _xmlns_attrs),
     {xmlel, <<"option">>, _attrs, _els}.
 
 'encode_xdata_field_option_$value'(Value, _acc) ->
     [encode_xdata_field_value(Value, []) | _acc].
 
+decode_xdata_field_option_attr_label(__TopXMLNS,
+                                    undefined) ->
+    undefined;
+decode_xdata_field_option_attr_label(__TopXMLNS,
+                                    _val) ->
+    _val.
+
+encode_xdata_field_option_attr_label(undefined, _acc) ->
+    _acc;
+encode_xdata_field_option_attr_label(_val, _acc) ->
+    [{<<"label">>, _val} | _acc].
+
 decode_xdata_field_value(__TopXMLNS, __IgnoreEls,
                         {xmlel, <<"value">>, _attrs, _els}) ->
     Cdata = decode_xdata_field_value_els(__TopXMLNS,
@@ -19881,6 +21097,19 @@ decode_error_els(__TopXMLNS, __IgnoreEls,
          decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
                           Reason)
     end;
+decode_error_els(__TopXMLNS, __IgnoreEls,
+                [{xmlel, <<"payment-required">>, _attrs, _} = _el
+                 | _els],
+                Text, Reason) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"urn:ietf:params:xml:ns:xmpp-stanzas">> ->
+         decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
+                          decode_error_payment_required(<<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
+                                                        __IgnoreEls, _el));
+      _ ->
+         decode_error_els(__TopXMLNS, __IgnoreEls, _els, Text,
+                          Reason)
+    end;
 decode_error_els(__TopXMLNS, __IgnoreEls,
                 [{xmlel, <<"policy-violation">>, _attrs, _} = _el
                  | _els],
@@ -20132,6 +21361,12 @@ encode_error({error, Type, Code, By, Reason, Text},
                                 [{<<"xmlns">>,
                                   <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
      | _acc];
+'encode_error_$reason'('payment-required' = Reason,
+                      _acc) ->
+    [encode_error_payment_required(Reason,
+                                  [{<<"xmlns">>,
+                                    <<"urn:ietf:params:xml:ns:xmpp-stanzas">>}])
+     | _acc];
 'encode_error_$reason'('policy-violation' = Reason,
                       _acc) ->
     [encode_error_policy_violation(Reason,
@@ -20438,6 +21673,16 @@ encode_error_policy_violation('policy-violation',
     _attrs = _xmlns_attrs,
     {xmlel, <<"policy-violation">>, _attrs, _els}.
 
+decode_error_payment_required(__TopXMLNS, __IgnoreEls,
+                             {xmlel, <<"payment-required">>, _attrs, _els}) ->
+    'payment-required'.
+
+encode_error_payment_required('payment-required',
+                             _xmlns_attrs) ->
+    _els = [],
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"payment-required">>, _attrs, _els}.
+
 decode_error_not_authorized(__TopXMLNS, __IgnoreEls,
                            {xmlel, <<"not-authorized">>, _attrs, _els}) ->
     'not-authorized'.
@@ -22069,17 +23314,18 @@ encode_private({private, __Xmls}, _xmlns_attrs) ->
 
 decode_disco_items(__TopXMLNS, __IgnoreEls,
                   {xmlel, <<"query">>, _attrs, _els}) ->
-    Items = decode_disco_items_els(__TopXMLNS, __IgnoreEls,
-                                  _els, []),
+    {Items, Rsm} = decode_disco_items_els(__TopXMLNS,
+                                         __IgnoreEls, _els, [], undefined),
     Node = decode_disco_items_attrs(__TopXMLNS, _attrs,
                                    undefined),
-    {disco_items, Node, Items}.
+    {disco_items, Node, Items, Rsm}.
 
 decode_disco_items_els(__TopXMLNS, __IgnoreEls, [],
-                      Items) ->
-    lists:reverse(Items);
+                      Items, Rsm) ->
+    {lists:reverse(Items), Rsm};
 decode_disco_items_els(__TopXMLNS, __IgnoreEls,
-                      [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items) ->
+                      [{xmlel, <<"item">>, _attrs, _} = _el | _els], Items,
+                      Rsm) ->
     case get_attr(<<"xmlns">>, _attrs) of
       <<"">>
          when __TopXMLNS ==
@@ -22087,20 +23333,35 @@ decode_disco_items_els(__TopXMLNS, __IgnoreEls,
          decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
                                 [decode_disco_item(__TopXMLNS, __IgnoreEls,
                                                    _el)
-                                 | Items]);
+                                 | Items],
+                                Rsm);
       <<"http://jabber.org/protocol/disco#items">> ->
          decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
                                 [decode_disco_item(<<"http://jabber.org/protocol/disco#items">>,
                                                    __IgnoreEls, _el)
-                                 | Items]);
+                                 | Items],
+                                Rsm);
+      _ ->
+         decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
+                                Items, Rsm)
+    end;
+decode_disco_items_els(__TopXMLNS, __IgnoreEls,
+                      [{xmlel, <<"set">>, _attrs, _} = _el | _els], Items,
+                      Rsm) ->
+    case get_attr(<<"xmlns">>, _attrs) of
+      <<"http://jabber.org/protocol/rsm">> ->
+         decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
+                                Items,
+                                decode_rsm_set(<<"http://jabber.org/protocol/rsm">>,
+                                               __IgnoreEls, _el));
       _ ->
          decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
-                                Items)
+                                Items, Rsm)
     end;
 decode_disco_items_els(__TopXMLNS, __IgnoreEls,
-                      [_ | _els], Items) ->
+                      [_ | _els], Items, Rsm) ->
     decode_disco_items_els(__TopXMLNS, __IgnoreEls, _els,
-                          Items).
+                          Items, Rsm).
 
 decode_disco_items_attrs(__TopXMLNS,
                         [{<<"node">>, _val} | _attrs], _Node) ->
@@ -22111,10 +23372,11 @@ decode_disco_items_attrs(__TopXMLNS, [_ | _attrs],
 decode_disco_items_attrs(__TopXMLNS, [], Node) ->
     decode_disco_items_attr_node(__TopXMLNS, Node).
 
-encode_disco_items({disco_items, Node, Items},
+encode_disco_items({disco_items, Node, Items, Rsm},
                   _xmlns_attrs) ->
     _els = lists:reverse('encode_disco_items_$items'(Items,
-                                                    [])),
+                                                    'encode_disco_items_$rsm'(Rsm,
+                                                                              []))),
     _attrs = encode_disco_items_attr_node(Node,
                                          _xmlns_attrs),
     {xmlel, <<"query">>, _attrs, _els}.
@@ -22124,6 +23386,12 @@ encode_disco_items({disco_items, Node, Items},
     'encode_disco_items_$items'(_els,
                                [encode_disco_item(Items, []) | _acc]).
 
+'encode_disco_items_$rsm'(undefined, _acc) -> _acc;
+'encode_disco_items_$rsm'(Rsm, _acc) ->
+    [encode_rsm_set(Rsm,
+                   [{<<"xmlns">>, <<"http://jabber.org/protocol/rsm">>}])
+     | _acc].
+
 decode_disco_items_attr_node(__TopXMLNS, undefined) ->
     undefined;
 decode_disco_items_attr_node(__TopXMLNS, _val) -> _val.
index 42b251fc1ba3e43b7f3ca1bc93d3c8c5130ff177..83f1f4adc42a8a8ebce3829a95b2903c71400032 100644 (file)
@@ -10,7 +10,8 @@
 
 %% API
 -export([add_delay_info/3, add_delay_info/4, unwrap_carbon/1,
-        is_standalone_chat_state/1]).
+        is_standalone_chat_state/1, get_xdata_values/2,
+        has_xdata_var/2]).
 
 -include("xmpp.hrl").
 
@@ -76,6 +77,17 @@ is_standalone_chat_state(Stanza) ->
            false
     end.
 
+-spec get_xdata_values(binary(), xdata()) -> [binary()].
+get_xdata_values(Var, #xdata{fields = Fields}) ->
+    case lists:keyfind(Var, #xdata_field.var, Fields) of
+       #xdata_field{values = Vals} -> Vals;
+       false -> []
+    end.
+
+-spec has_xdata_var(binary(), xdata()) -> boolean().
+has_xdata_var(Var, #xdata{fields = Fields}) ->
+    lists:keymember(Var, #xdata_field.var, Fields).
+
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
index 6c74a7ab15b80d864d8c98a9a61c7056579968a7..fdf16ed37e49a426b5aa884adb15f3f69d4bd98e 100644 (file)
 -xml(disco_items,
      #elem{name = <<"query">>,
            xmlns = <<"http://jabber.org/protocol/disco#items">>,
-           result = {disco_items, '$node', '$items'},
+           result = {disco_items, '$node', '$items', '$rsm'},
            attrs = [#attr{name = <<"node">>}],
            refs = [#ref{name = disco_item,
-                        label = '$items'}]}).
+                        label = '$items'},
+                  #ref{name = rsm_set, min = 0, max = 1,
+                       label = '$rsm'}]}).
 
 -xml(private,
      #elem{name = <<"query">>,
      #elem{name = <<"not-authorized">>,
            xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
            result = 'not-authorized'}).
+-xml(error_payment_required,
+     #elem{name = <<"payment-required">>,
+           xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
+           result = 'payment-required'}).
 -xml(error_policy_violation,
      #elem{name = <<"policy-violation">>,
            xmlns = <<"urn:ietf:params:xml:ns:xmpp-stanzas">>,
                         min = 0, max = 1, label = '$reason'},
                    #ref{name = error_not_authorized,
                         min = 0, max = 1, label = '$reason'},
+                  #ref{name = error_payment_required,
+                        min = 0, max = 1, label = '$reason'},
                    #ref{name = error_policy_violation,
                         min = 0, max = 1, label = '$reason'},
                    #ref{name = error_recipient_unavailable,
 -xml(xdata_field_option,
      #elem{name = <<"option">>,
            xmlns = <<"jabber:x:data">>,
-           result = '$value',
+           result = {xdata_option, '$label', '$value'},
+          attrs = [#attr{name = <<"label">>}],
            refs = [#ref{name = xdata_field_value,
                         label = '$value',
                         min = 1, max = 1}]}).
                           dec = {dec_utc, []},
                           enc = {enc_utc, []}}]}).
 
--xml(muc_user_reason,
+-xml(muc_reason,
      #elem{name = <<"reason">>,
-           xmlns = <<"http://jabber.org/protocol/muc#user">>,
+           xmlns = [<<"http://jabber.org/protocol/muc#user">>,
+                   <<"http://jabber.org/protocol/muc#admin">>,
+                   <<"http://jabber.org/protocol/muc#owner">>],
            result = '$cdata'}).
 
 -xml(muc_user_decline,
                     #attr{name = <<"from">>,
                           dec = {dec_jid, []},
                           enc = {enc_jid, []}}],
-           refs = [#ref{name = muc_user_reason, min = 0,
+           refs = [#ref{name = muc_reason, min = 0,
+                       default = <<"">>,
                         max = 1, label = '$reason'}]}).
 
--xml(muc_user_destroy,
+-xml(muc_destroy,
      #elem{name = <<"destroy">>,
-           xmlns = <<"http://jabber.org/protocol/muc#user">>,
-           result = {muc_user_destroy, '$reason', '$jid'},
-           attrs = [#attr{name = <<"jid">>,
+          xmlns = [<<"http://jabber.org/protocol/muc#user">>,
+                   <<"http://jabber.org/protocol/muc#owner">>],
+          result = {muc_destroy, '$xmlns', '$jid', '$reason', '$password'},
+          attrs = [#attr{name = <<"jid">>,
                           dec = {dec_jid, []},
-                          enc = {enc_jid, []}}],
-           refs = [#ref{name = muc_user_reason, min = 0,
-                        max = 1, label = '$reason'}]}).
+                          enc = {enc_jid, []}},
+                   #attr{name = <<"xmlns">>}],
+          refs = [#ref{name = muc_reason, min = 0,
+                       default = <<"">>,
+                        max = 1, label = '$reason'},
+                  #ref{name = muc_password, min = 0, max = 1,
+                       label = '$password'}]}).
 
 -xml(muc_user_invite,
      #elem{name = <<"invite">>,
            xmlns = <<"http://jabber.org/protocol/muc#user">>,
-           result = {muc_invite, '$reason', '$from', '$to'},
+           result = {muc_invite, '$reason', '$from', '$to', '$continue'},
            attrs = [#attr{name = <<"to">>,
                           dec = {dec_jid, []},
                           enc = {enc_jid, []}},
                     #attr{name = <<"from">>,
                           dec = {dec_jid, []},
                           enc = {enc_jid, []}}],
-           refs = [#ref{name = muc_user_reason, min = 0,
-                        max = 1, label = '$reason'}]}).
+           refs = [#ref{name = muc_reason, min = 0, default = <<"">>,
+                        max = 1, label = '$reason'},
+                  #ref{name = muc_user_continue, min = 0, max = 1,
+                       label = '$continue'}]}).
 
 -xml(muc_user_actor,
      #elem{name = <<"actor">>,
                         min = 0, max = 1, label = '$actor'},
                    #ref{name = muc_user_continue,
                         min = 0, max = 1, label = '$continue'},
-                   #ref{name = muc_user_reason,
+                   #ref{name = muc_reason, default = <<"">>,
                         min = 0, max = 1, label = '$reason'}],
            attrs = [#attr{name = <<"affiliation">>,
                           dec = {dec_enum, [[admin, member, none,
            xmlns = <<"http://jabber.org/protocol/muc#user">>,
            result = {muc_user, '$decline', '$destroy', '$invites',
                      '$items', '$status_codes', '$password'},
-           attrs = [#attr{name = <<"password">>}],
            refs = [#ref{name = muc_user_decline, min = 0,
                         max = 1, label = '$decline'},
-                   #ref{name = muc_user_destroy, min = 0, max = 1,
+                   #ref{name = muc_destroy, min = 0, max = 1,
                         label = '$destroy'},
+                  #ref{name = muc_password, min = 0, max = 1,
+                       label = '$password'},
                    #ref{name = muc_user_invite, label = '$invites'},
                    #ref{name = muc_user_item, label = '$items'},
                    #ref{name = muc_user_status, label = '$status_codes'}]}).
 
--xml(muc_owner_password,
+-xml(muc_password,
      #elem{name = <<"password">>,
-           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
-           result = '$cdata'}).
-
--xml(muc_owner_reason,
-     #elem{name = <<"reason">>,
-           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
+           xmlns = [<<"http://jabber.org/protocol/muc#owner">>,
+                   <<"http://jabber.org/protocol/muc#user">>,
+                   <<"http://jabber.org/protocol/muc">>],
            result = '$cdata'}).
 
--xml(muc_owner_destroy,
-     #elem{name = <<"destroy">>,
-           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
-           result = {muc_owner_destroy, '$jid', '$reason', '$password'},
-           attrs = [#attr{name = <<"jid">>,
-                          dec = {dec_jid, []},
-                          enc = {enc_jid, []}}],
-           refs = [#ref{name = muc_owner_password, min = 0, max = 1,
-                        label = '$password'},
-                   #ref{name = muc_owner_reason, min = 0, max = 1,
-                        label = '$reason'}]}).
-
 -xml(muc_owner,
      #elem{name = <<"query">>,
            xmlns = <<"http://jabber.org/protocol/muc#owner">>,
-           result = {muc_owner, '$destroy', '$config'},
-           refs = [#ref{name = muc_owner_destroy, min = 0, max = 1,
-                        label = '$destroy'},
-                   #ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
+           result = {muc_owner, '$destroy', '$config', '$items'},
+           refs = [#ref{name = muc_destroy, min = 0, max = 1,
+                       label = '$destroy'},
+                   #ref{name = xdata, min = 0, max = 1,
+                       label = '$config'},
+                  #ref{name = muc_owner_item, label = '$items'}]}).
+
+-xml(muc_owner_item,
+     #elem{name = <<"item">>,
+           xmlns = <<"http://jabber.org/protocol/muc#owner">>,
+           result = {muc_item, '$actor', '$continue', '$reason',
+                     '$affiliation', '$role', '$jid', '$nick'},
+           refs = [#ref{name = muc_admin_actor,
+                        min = 0, max = 1, label = '$actor'},
+                   #ref{name = muc_admin_continue,
+                        min = 0, max = 1, label = '$continue'},
+                   #ref{name = muc_reason, default = <<"">>,
+                        min = 0, max = 1, label = '$reason'}],
+           attrs = [#attr{name = <<"affiliation">>,
+                          dec = {dec_enum, [[admin, member, none,
+                                             outcast, owner]]},
+                          enc = {enc_enum, []}},
+                    #attr{name = <<"role">>,
+                          dec = {dec_enum, [[moderator, none,
+                                             participant, visitor]]},
+                          enc = {enc_enum, []}},
+                    #attr{name = <<"jid">>,
+                          dec = {dec_jid, []},
+                          enc = {enc_jid, []}},
+                    #attr{name = <<"nick">>}]}).
 
 -xml(muc_admin_item,
      #elem{name = <<"item">>,
                         min = 0, max = 1, label = '$actor'},
                    #ref{name = muc_admin_continue,
                         min = 0, max = 1, label = '$continue'},
-                   #ref{name = muc_admin_reason,
+                   #ref{name = muc_reason, default = <<"">>,
                         min = 0, max = 1, label = '$reason'}],
            attrs = [#attr{name = <<"affiliation">>,
                           dec = {dec_enum, [[admin, member, none,
            result = '$thread',
            attrs = [#attr{name = <<"thread">>}]}).
 
--xml(muc_admin_reason,
-     #elem{name = <<"reason">>,
-           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
-           result = '$cdata'}).
-
 -xml(muc_admin,
      #elem{name = <<"query">>,
           xmlns = <<"http://jabber.org/protocol/muc#admin">>,
      #elem{name = <<"x">>,
            xmlns = <<"http://jabber.org/protocol/muc">>,
            result = {muc, '$history', '$password'},
-           attrs = [#attr{name = <<"password">>}],
            refs = [#ref{name = muc_history, min = 0, max = 1,
-                        label = '$history'}]}).
+                        label = '$history'},
+                  #ref{name = muc_password, min = 0, max = 1,
+                       label = '$password'}]}).
+
+-xml(muc_unique,
+     #elem{name = <<"unique">>,
+          xmlns = <<"http://jabber.org/protocol/muc#unique">>,
+          result = {muc_unique, '$name'},
+          cdata = #cdata{default = <<"">>,
+                         label = '$name'}}).
+
+-xml(x_conference,
+     #elem{name = <<"x">>,
+          xmlns = <<"jabber:x:conference">>,
+          result = {x_conference, '$jid', '$password', '$reason',
+                    '$continue', '$thread'},
+          attrs = [#attr{name = <<"jid">>,
+                         required = true,
+                         dec = {dec_jid, []},
+                          enc = {enc_jid, []}},
+                   #attr{name = <<"password">>, default = <<"">>},
+                   #attr{name = <<"reason">>, default = <<"">>},
+                   #attr{name = <<"thread">>, default = <<"">>},
+                   #attr{name = <<"continue">>,
+                         dec = {dec_bool, []},
+                         enc = {enc_bool, []}}]}).
+
+-xml(muc_subscription,
+     #elem{name = <<"subscription">>,
+          xmlns = <<"urn:xmpp:mucsub:0">>,
+          result = '$jid',
+           attrs = [#attr{name = <<"jid">>,
+                          required = true,
+                          dec = {dec_jid, []},
+                          enc = {enc_jid, []}}]}).
+
+-xml(muc_subscriptions,
+     #elem{name = <<"subscriptions">>,
+          xmlns = <<"urn:xmpp:mucsub:0">>,
+          result = {muc_subscriptions, '$list'},
+          refs = [#ref{name = muc_subscription, label = '$list'}]}).
+
+-xml(muc_subscribe_event,
+     #elem{name = <<"event">>,
+          xmlns = <<"urn:xmpp:mucsub:0">>,
+          result = '$node',
+          attrs = [#attr{name = <<"node">>, required = true}]}).
+
+-xml(muc_subscribe,
+     #elem{name = <<"subscribe">>,
+          xmlns = <<"urn:xmpp:mucsub:0">>,
+          result = {muc_subscribe, '$nick', '$events'},
+          attrs = [#attr{name = <<"nick">>, required = true}],
+          refs = [#ref{name = muc_subscribe_event, label = '$events'}]}).
+
+-xml(muc_unsubscribe,
+     #elem{name = <<"unsubscribe">>,
+          xmlns = <<"urn:xmpp:mucsub:0">>,
+          result = {muc_unsubscribe}}).
 
 -xml(rsm_after,
      #elem{name = <<"after">>,
 -xml(rsm_before,
      #elem{name = <<"before">>,
            xmlns = <<"http://jabber.org/protocol/rsm">>,
-          cdata = #cdata{default = none},
+          cdata = #cdata{default = <<"">>},
            result = '$cdata'}).
 
 -xml(rsm_last,
                           dec = {dec_jid, []},
                           enc = {enc_jid, []}}}).
 
+-xml(mam_withtext,
+     #elem{name = <<"withtext">>,
+          xmlns = <<"urn:xmpp:mam:tmp">>,
+          result = '$cdata',
+          cdata = #cdata{required = true}}).
+
 -xml(mam_query,
      #elem{name = <<"query">>,
            xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
            result = {mam_query, '$xmlns', '$id', '$start', '$end', '$with',
-                    '$rsm', '$xdata'},
+                    '$withtext', '$rsm', '$xdata'},
            attrs = [#attr{name = <<"queryid">>, label = '$id'},
                    #attr{name = <<"xmlns">>}],
            refs = [#ref{name = mam_start, min = 0, max = 1, label = '$start'},
                    #ref{name = mam_end, min = 0, max = 1, label = '$end'},
                    #ref{name = mam_with, min = 0, max = 1, label = '$with'},
+                  #ref{name = mam_withtext, min = 0, max = 1, label = '$withtext'},
                    #ref{name = rsm_set, min = 0, max = 1, label = '$rsm'},
                   #ref{name = xdata, min = 0, max = 1, label = '$xdata'}]}).
 
 
 -xml(mam_jid,
      #elem{name = <<"jid">>,
-           xmlns = <<"urn:xmpp:mam:tmp">>,
+           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
            result = '$cdata',
            cdata = #cdata{required = true,
                           dec = {dec_jid, []},
 
 -xml(mam_never,
      #elem{name = <<"never">>,
-           xmlns = <<"urn:xmpp:mam:tmp">>,
+           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
            result = '$jids',
-           refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
+           refs = [#ref{name = mam_jid, label = '$jids'}]}).
 
 -xml(mam_always,
      #elem{name = <<"always">>,
-           xmlns = <<"urn:xmpp:mam:tmp">>,
+           xmlns = [<<"urn:xmpp:mam:0">>, <<"urn:xmpp:mam:1">>, <<"urn:xmpp:mam:tmp">>],
            result = '$jids',
-           refs = [#ref{name = mam_jid, label = '$jids', default = []}]}).
+           refs = [#ref{name = mam_jid, label = '$jids'}]}).
 
 -xml(mam_prefs,
      #elem{name = <<"prefs">>,
                           enc = {enc_enum, []}},
                    #attr{name = <<"xmlns">>}],
            refs = [#ref{name = mam_always, label = '$always',
-                        min = 0, max = 1, default = []},
+                        min = 0, max = 1},
                    #ref{name = mam_never, label = '$never',
-                        min = 0, max = 1, default = []}]}).
+                        min = 0, max = 1}]}).
 
 -xml(mam_fin,
      #elem{name = <<"fin">>,
                    #attr{name = <<"nick">>,
                          label = '$nick'}]}).
 
--record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' |
-                      'store' | 'no-permanent-store'}).
+-record(hint, {type :: 'no-copy' | 'no-store' | 'no-storage' | 'store' |
+                      'no-permanent-store' | 'no-permanent-storage'}).
 -type hint() :: #hint{}.
 
 -xml(hint_no_copy,
           xmlns = <<"urn:xmpp:hints">>,
           result = {hint, 'no-permanent-store'}}).
 
+-xml(hint_no_permanent_storage,
+     #elem{name = <<"no-permanent-storage">>,
+          xmlns = <<"urn:xmpp:hints">>,
+          result = {hint, 'no-permanent-storage'}}).
+
 -xml(search_instructions,
      #elem{name = <<"instructions">>,
            xmlns = <<"jabber:iq:search">>,
                          dec = {dec_int, [0, infinity]},
                           enc = {enc_int, []}}]}).
 
+-xml(nick,
+     #elem{name = <<"nick">>,
+          xmlns = <<"http://jabber.org/protocol/nick">>,
+          result = {nick, '$name'},
+          cdata = #cdata{label = '$name',
+                         required = true}}).
+
+-xml(address,
+     #elem{name = <<"address">>,
+          xmlns = <<"http://jabber.org/protocol/address">>,
+          result = {address, '$type', '$jid', '$desc', '$node', '$delivered'},
+          attrs = [#attr{name = <<"type">>,
+                         required = true,
+                         dec = {dec_enum, [[bcc, cc, noreply, ofrom,
+                                            replyroom, replyto, to]]},
+                         enc = {enc_enum, []}},
+                   #attr{name = <<"jid">>,
+                         enc = {enc_jid, []},
+                         dec = {dec_jid, []}},
+                   #attr{name = <<"desc">>},
+                   #attr{name = <<"node">>},
+                   #attr{name = <<"delivered">>,
+                         enc = {enc_bool, []},
+                         dec = {dec_bool, []}}]}).
+
+-xml(addresses,
+     #elem{name = <<"addresses">>,
+          xmlns = <<"http://jabber.org/protocol/address">>,
+          result = {addresses, '$list'},
+          %% TODO: 'min' should be '1', but this is not implemented
+          refs = [#ref{name = address, label = '$list'}]}).
+
+-xml(stanza_id,
+     #elem{name = <<"stanza-id">>,
+          xmlns = <<"urn:xmpp:sid:0">>,
+          result = {stanza_id, '$by', '$id'},
+          attrs = [#attr{name = <<"id">>, required = true},
+                   #attr{name = <<"by">>, required = true,
+                         enc = {enc_jid, []},
+                         dec = {dec_jid, []}}]}).
+
+-xml(client_id,
+     #elem{name = <<"client-id">>,
+          xmlns = <<"urn:xmpp:sid:0">>,
+          result = {client_id, '$id'},
+          attrs = [#attr{name = <<"id">>, required = true}]}).
+
 dec_tzo(Val) ->
     [H1, M1] = str:tokens(Val, <<":">>),
     H = jlib:binary_to_integer(H1),