1
0
Fork 0
mirror of https://github.com/processone/ejabberd synced 2025-10-03 01:39:35 +02:00

mod_pubsub_serverinfo: initial import as found on ejabberd-contrib

This commit is contained in:
Stefan Strigler 2025-07-05 14:19:20 +02:00
parent 6b47d3eb0d
commit 740b0c7dd7
5 changed files with 1127 additions and 0 deletions

View file

@ -0,0 +1,17 @@
%% Created automatically by XML generator (fxml_gen.erl)
%% Source: pubsub_serverinfo_codec.spec
-record(pubsub_serverinfo_remote_domain, {name = <<>> :: binary(),
type = [] :: ['bidi' | 'incoming' | 'outgoing']}).
-type pubsub_serverinfo_remote_domain() :: #pubsub_serverinfo_remote_domain{}.
-record(pubsub_serverinfo_domain, {name = <<>> :: binary(),
remote_domain :: 'undefined' | [#pubsub_serverinfo_remote_domain{}]}).
-type pubsub_serverinfo_domain() :: #pubsub_serverinfo_domain{}.
-record(pubsub_serverinfo, {domain = [] :: [#pubsub_serverinfo_domain{}]}).
-type pubsub_serverinfo() :: #pubsub_serverinfo{}.
-type pubsub_serverinfo_codec() :: pubsub_serverinfo() |
pubsub_serverinfo_domain() |
pubsub_serverinfo_remote_domain().

View file

@ -0,0 +1,52 @@
-xml(pubsub_serverinfo,
#elem{name = <<"serverinfo">>,
xmlns = <<"urn:xmpp:serverinfo:0">>,
module = pubsub_serverinfo_codec,
result = {pubsub_serverinfo, '$domain'},
refs = [#ref{name = pubsub_serverinfo_domain,
label = '$domain',
min = 0}]}).
-xml(pubsub_serverinfo_domain,
#elem{name = <<"domain">>,
xmlns = <<"urn:xmpp:serverinfo:0">>,
module = pubsub_serverinfo_codec,
result = {pubsub_serverinfo_domain, '$name', '$remote_domain'},
attrs = [#attr{name = <<"name">>,
label = '$name',
required = true}],
refs = [#ref{name = pubsub_serverinfo_federation,
label = '$remote_domain',
min = 0, max = 1}]}).
-xml(pubsub_serverinfo_federation,
#elem{name = <<"federation">>,
xmlns = <<"urn:xmpp:serverinfo:0">>,
module = pubsub_serverinfo_codec,
result = '$remote_domain',
refs = [#ref{name = pubsub_serverinfo_remote_domain,
label = '$remote_domain',
min = 0}]}).
-xml(pubsub_serverinfo_remote_domain,
#elem{name = <<"remote-domain">>,
xmlns = <<"urn:xmpp:serverinfo:0">>,
module = pubsub_serverinfo_codec,
result = {pubsub_serverinfo_remote_domain, '$name', '$type'},
attrs = [#attr{name = <<"name">>,
label = '$name',
required = true}],
refs = [#ref{name = pubsub_serverinfo_connection,
label = '$type',
min = 0}]}).
-xml(pubsub_serverinfo_connection,
#elem{name = <<"connection">>,
xmlns = <<"urn:xmpp:serverinfo:0">>,
module = pubsub_serverinfo_codec,
result = '$type',
attrs = [#attr{name = <<"type">>,
label = '$type',
required = true,
enc = {enc_enum, []},
dec = {dec_enum, [[incoming, outgoing, bidi]]}}]}).

View file

@ -0,0 +1,312 @@
%%%----------------------------------------------------------------------
%%% File : mod_pubsub_serverinfo.erl
%%% Author : Stefan Strigler <stefan@strigler.de>
%%% Purpose : Exposes server information over Pub/Sub
%%% Created : 26 Dec 2023 by Guus der Kinderen <guus.der.kinderen@gmail.com>
%%%
%%%
%%% ejabberd, Copyright (C) 2023 - 2025 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_pubsub_serverinfo).
-author('stefan@strigler.de').
-behaviour(gen_mod).
-behaviour(gen_server).
-include("pubsub_serverinfo_codec.hrl").
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
%% gen_mod callbacks.
-export([start/2, stop/1, depends/2, mod_options/1, mod_opt_type/1, get_local_features/5, mod_doc/0]).
-export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]).
-export([in_auth_result/3, out_auth_result/2, get_info/5]).
-define(NS_URN_SERVERINFO, <<"urn:xmpp:serverinfo:0">>).
-define(PUBLIC_HOSTS_URL, <<"https://data.xmpp.net/providers/v2/providers-Ds.json">>).
-record(state, {host, pubsub_host, node, monitors = #{}, timer = undefined, public_hosts = []}).
start(Host, Opts) ->
case pubsub_host(Host, Opts) of
{error, _Reason} = Error ->
Error;
PubsubHost ->
xmpp:register_codec(pubsub_serverinfo_codec),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:add(s2s_out_auth_result, Host, ?MODULE, out_auth_result, 50),
ejabberd_hooks:add(s2s_in_auth_result, Host, ?MODULE, in_auth_result, 50),
gen_mod:start_child(?MODULE, Host, PubsubHost)
end.
stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 50),
ejabberd_hooks:delete(s2s_out_auth_result, Host, ?MODULE, out_auth_result, 50),
ejabberd_hooks:delete(s2s_in_auth_result, Host, ?MODULE, in_auth_result, 50),
gen_mod:stop_child(?MODULE, Host).
init([Host, PubsubHost]) ->
TRef = timer:send_interval(timer:minutes(5), self(), update_pubsub),
Monitors = init_monitors(Host),
PublicHosts = fetch_public_hosts(),
State = #state{host = Host,
pubsub_host = PubsubHost,
node = <<"serverinfo">>,
timer = TRef,
monitors = Monitors,
public_hosts = PublicHosts},
self() ! update_pubsub,
{ok, State}.
-spec init_monitors(binary()) -> map().
init_monitors(Host) ->
lists:foldl(
fun(Domain, Monitors) ->
RefIn = make_ref(), % just dummies
RefOut = make_ref(),
maps:merge(#{RefIn => {incoming, {Host, Domain, true}},
RefOut => {outgoing, {Host, Domain, true}}},
Monitors)
end,
#{},
ejabberd_option:hosts() -- [Host]).
-spec fetch_public_hosts() -> list().
fetch_public_hosts() ->
try
{ok, {{_, 200, _}, _Headers, Body}} = httpc:request(get, {?PUBLIC_HOSTS_URL, []}, [{timeout, 1000}], [{body_format, binary}]),
case misc:json_decode(Body) of
PublicHosts when is_list(PublicHosts) -> PublicHosts;
Other ->
?WARNING_MSG("Parsed JSON for public hosts was not a list: ~p", [Other]),
[]
end
catch E:R ->
?WARNING_MSG("Failed fetching public hosts (~p): ~p", [E, R]),
[]
end.
handle_cast({Event, Domain, Pid}, #state{host = Host, monitors = Mons} = State)
when Event == register_in; Event == register_out ->
Ref = monitor(process, Pid),
IsPublic = check_if_public(Domain, State),
NewMons = maps:put(Ref, {event_to_dir(Event), {Host, Domain, IsPublic}}, Mons),
{noreply, State#state{monitors = NewMons}};
handle_cast(_, State) ->
{noreply, State}.
event_to_dir(register_in) -> incoming;
event_to_dir(register_out) -> outgoing.
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_info({iq_reply, IQReply, {LServer, RServer}},
#state{monitors = Mons} = State) ->
case IQReply of
#iq{type = result, sub_els = [El]} ->
case xmpp:decode(El) of
#disco_info{features = Features} ->
case lists:member(?NS_URN_SERVERINFO, Features) of
true ->
NewMons = maps:fold(fun(Ref, {Dir, {LServer0, RServer0, _}}, Acc)
when LServer == LServer0, RServer == RServer0 ->
maps:put(Ref, {Dir, {LServer, RServer, true}}, Acc);
(Ref, Other, Acc) ->
maps:put(Ref, Other, Acc)
end,
#{},
Mons),
{noreply, State#state{monitors = NewMons}};
_ ->
{noreply, State}
end;
_ ->
{noreply, State}
end;
_ ->
{noreply, State}
end;
handle_info(update_pubsub, State) ->
update_pubsub(State),
{noreply, State};
handle_info({'DOWN', Mon, process, _Pid, _Info}, #state{monitors = Mons} = State) ->
{noreply, State#state{monitors = maps:remove(Mon, Mons)}};
handle_info(_Request, State) ->
{noreply, State}.
terminate(_Reason, #state{monitors = Mons, timer = Timer}) ->
case is_reference(Timer) of
true ->
case erlang:cancel_timer(Timer) of
false ->
receive {timeout, Timer, _} -> ok
after 0 -> ok
end;
_ ->
ok
end;
_ ->
ok
end,
maps:fold(
fun(Mon, _, _) ->
demonitor(Mon)
end, ok, Mons).
depends(_Host, _Opts) ->
[{mod_pubsub, hard}].
mod_options(_Host) ->
[{pubsub_host, undefined}].
mod_opt_type(pubsub_host) ->
econf:either(undefined, econf:host()).
mod_doc() -> #{}.
in_auth_result(#{server_host := Host, remote_server := RServer} = State, true, _Server) ->
gen_server:cast(gen_mod:get_module_proc(Host, ?MODULE), {register_in, RServer, self()}),
State;
in_auth_result(State, _, _) ->
State.
out_auth_result(#{server_host := Host, remote_server := RServer} = State, true) ->
gen_server:cast(gen_mod:get_module_proc(Host, ?MODULE), {register_out, RServer, self()}),
State;
out_auth_result(State, _) ->
State.
check_if_public(Domain, State) ->
maybe_send_disco_info(is_public(Domain, State) orelse is_monitored(Domain, State), Domain, State).
is_public(Domain, #state{public_hosts = PublicHosts}) ->
lists:member(Domain, PublicHosts).
is_monitored(Domain, #state{host = Host, monitors = Mons}) ->
maps:size(
maps:filter(
fun(_Ref, {_Dir, {LServer, RServer, IsPublic}})
when LServer == Host, RServer == Domain -> IsPublic;
(_Ref, _Other) -> false
end,
Mons)) =/= 0.
maybe_send_disco_info(true, _Domain, _State) -> true;
maybe_send_disco_info(false, Domain, #state{host = Host}) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
IQ = #iq{type = get, from = jid:make(Host),
to = jid:make(Domain), sub_els = [#disco_info{}]},
ejabberd_router:route_iq(IQ, {Host, Domain}, Proc),
false.
update_pubsub(#state{host = Host, pubsub_host = PubsubHost, node = Node, monitors = Mons}) ->
Map = maps:fold(
fun(_, {Dir, {MyDomain, Target, IsPublic}}, Acc) ->
maps:update_with(MyDomain,
fun(Acc2) ->
maps:update_with(Target,
fun({Types, _}) -> {Types#{Dir => true}, IsPublic} end,
{#{Dir => true}, IsPublic}, Acc2)
end, #{Target => {#{Dir => true}, IsPublic}}, Acc)
end, #{}, Mons),
Domains = maps:fold(
fun(MyDomain, Targets, Acc) ->
Remote = maps:fold(
fun(Remote, {Types, true}, Acc2) ->
[#pubsub_serverinfo_remote_domain{name = Remote, type = maps:keys(Types)} | Acc2];
(_HiddenRemote, {Types, false}, Acc2) ->
[#pubsub_serverinfo_remote_domain{type = maps:keys(Types)} | Acc2]
end, [], Targets),
[#pubsub_serverinfo_domain{name = MyDomain, remote_domain = Remote} | Acc]
end, [], Map),
PubOpts = [{persist_items, true}, {max_items, 1}, {access_model, open}],
?DEBUG("Publishing serverinfo pubsub item on ~s: ~p", [PubsubHost, Domains]),
mod_pubsub:publish_item(
PubsubHost, Host, Node, jid:make(Host),
<<"current">>, [xmpp:encode(#pubsub_serverinfo{domain = Domains})], PubOpts, all).
get_local_features({error, _} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_local_features(Acc, _From, _To, Node, _Lang) when Node == <<>> ->
case Acc of
{result, Features} ->
{result, [?NS_URN_SERVERINFO | Features]};
empty -> {result, [?NS_URN_SERVERINFO]}
end;
get_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
get_info(Acc, Host, Mod, Node, Lang) when (Mod == undefined orelse Mod == mod_disco), Node == <<"">> ->
case mod_disco:get_info(Acc, Host, Mod, Node, Lang) of
[#xdata{fields = Fields} = XD | Rest] ->
PubsubHost = pubsub_host(Host),
NodeField = #xdata_field{var = <<"serverinfo-pubsub-node">>,
values = [<<"xmpp:", PubsubHost/binary, "?;node=serverinfo">>]},
{stop, [XD#xdata{fields = Fields ++ [NodeField]} | Rest]};
_ ->
Acc
end;
get_info(Acc, Host, Mod, Node, _Lang) when Node == <<"">>, is_atom(Mod) ->
PubsubHost = pubsub_host(Host),
[#xdata{type = result,
fields = [
#xdata_field{type = hidden,
var = <<"FORM_TYPE">>,
values = [?NS_SERVERINFO]},
#xdata_field{var = <<"serverinfo-pubsub-node">>,
values = [<<"xmpp:", PubsubHost/binary, "?;node=serverinfo">>]}]} | Acc];
get_info(Acc, _Host, _Mod, _Node, _Lang) ->
Acc.
pubsub_host(Host) ->
case pubsub_host(Host, gen_mod:get_module_opts(Host, ?MODULE)) of
{error, _Reason} = Error ->
throw(Error);
PubsubHost ->
PubsubHost
end.
pubsub_host(Host, Opts) ->
case gen_mod:get_opt(pubsub_host, Opts) of
undefined ->
PubsubHost = hd(get_mod_pubsub_hosts(Host)),
?INFO_MSG("No pubsub_host in configuration for ~p, choosing ~s", [?MODULE, PubsubHost]),
PubsubHost;
PubsubHost ->
case check_pubsub_host_exists(Host, PubsubHost) of
true ->
PubsubHost;
false ->
{error, {pubsub_host_does_not_exist, PubsubHost}}
end
end.
check_pubsub_host_exists(Host, PubsubHost) ->
lists:member(PubsubHost, get_mod_pubsub_hosts(Host)).
get_mod_pubsub_hosts(Host) ->
case gen_mod:get_module_opt(Host, mod_pubsub, hosts) of
[] ->
[gen_mod:get_module_opt(Host, mod_pubsub, host)];
PubsubHosts ->
PubsubHosts
end.

View file

@ -0,0 +1,734 @@
%% Created automatically by XML generator (fxml_gen.erl)
%% Source: pubsub_serverinfo_codec.spec
-module(pubsub_serverinfo_codec).
-compile(export_all).
decode(El) -> decode(El, <<>>, []).
decode(El, Opts) -> decode(El, <<>>, Opts).
decode({xmlel, Name, Attrs, _} = El, TopXMLNS, Opts) ->
XMLNS = get_attr(<<"xmlns">>, Attrs, TopXMLNS),
case get_mod(Name, XMLNS) of
undefined when XMLNS == <<>> ->
erlang:error({pubsub_serverinfo_codec,
{missing_tag_xmlns, Name}});
undefined ->
erlang:error({pubsub_serverinfo_codec,
{unknown_tag, Name, XMLNS}});
Mod -> Mod:do_decode(Name, XMLNS, El, Opts)
end.
encode(El) -> encode(El, <<>>).
encode({xmlel, _, _, _} = El, _) -> El;
encode({xmlcdata, _} = CData, _) -> CData;
encode(El, TopXMLNS) ->
Mod = get_mod(El),
Mod:do_encode(El, TopXMLNS).
get_name(El) ->
Mod = get_mod(El),
Mod:do_get_name(El).
get_ns(El) ->
Mod = get_mod(El),
Mod:do_get_ns(El).
is_known_tag({xmlel, Name, Attrs, _}, TopXMLNS) ->
XMLNS = get_attr(<<"xmlns">>, Attrs, TopXMLNS),
get_mod(Name, XMLNS) /= undefined.
get_els(Term) ->
Mod = get_mod(Term),
Mod:get_els(Term).
set_els(Term, Els) ->
Mod = get_mod(Term),
Mod:set_els(Term, Els).
do_decode(<<"connection">>, <<"urn:xmpp:serverinfo:0">>,
El, Opts) ->
decode_pubsub_serverinfo_connection(<<"urn:xmpp:serverinfo:0">>,
Opts,
El);
do_decode(<<"remote-domain">>,
<<"urn:xmpp:serverinfo:0">>, El, Opts) ->
decode_pubsub_serverinfo_remote_domain(<<"urn:xmpp:serverinfo:0">>,
Opts,
El);
do_decode(<<"federation">>, <<"urn:xmpp:serverinfo:0">>,
El, Opts) ->
decode_pubsub_serverinfo_federation(<<"urn:xmpp:serverinfo:0">>,
Opts,
El);
do_decode(<<"domain">>, <<"urn:xmpp:serverinfo:0">>, El,
Opts) ->
decode_pubsub_serverinfo_domain(<<"urn:xmpp:serverinfo:0">>,
Opts,
El);
do_decode(<<"serverinfo">>, <<"urn:xmpp:serverinfo:0">>,
El, Opts) ->
decode_pubsub_serverinfo(<<"urn:xmpp:serverinfo:0">>,
Opts,
El);
do_decode(Name, <<>>, _, _) ->
erlang:error({pubsub_serverinfo_codec,
{missing_tag_xmlns, Name}});
do_decode(Name, XMLNS, _, _) ->
erlang:error({pubsub_serverinfo_codec,
{unknown_tag, Name, XMLNS}}).
tags() ->
[{<<"connection">>, <<"urn:xmpp:serverinfo:0">>},
{<<"remote-domain">>, <<"urn:xmpp:serverinfo:0">>},
{<<"federation">>, <<"urn:xmpp:serverinfo:0">>},
{<<"domain">>, <<"urn:xmpp:serverinfo:0">>},
{<<"serverinfo">>, <<"urn:xmpp:serverinfo:0">>}].
do_encode({pubsub_serverinfo, _} = Serverinfo,
TopXMLNS) ->
encode_pubsub_serverinfo(Serverinfo, TopXMLNS);
do_encode({pubsub_serverinfo_domain, _, _} = Domain,
TopXMLNS) ->
encode_pubsub_serverinfo_domain(Domain, TopXMLNS);
do_encode({pubsub_serverinfo_remote_domain, _, _} =
Remote_domain,
TopXMLNS) ->
encode_pubsub_serverinfo_remote_domain(Remote_domain,
TopXMLNS).
do_get_name({pubsub_serverinfo, _}) -> <<"serverinfo">>;
do_get_name({pubsub_serverinfo_domain, _, _}) ->
<<"domain">>;
do_get_name({pubsub_serverinfo_remote_domain, _, _}) ->
<<"remote-domain">>.
do_get_ns({pubsub_serverinfo, _}) ->
<<"urn:xmpp:serverinfo:0">>;
do_get_ns({pubsub_serverinfo_domain, _, _}) ->
<<"urn:xmpp:serverinfo:0">>;
do_get_ns({pubsub_serverinfo_remote_domain, _, _}) ->
<<"urn:xmpp:serverinfo:0">>.
register_module(Mod) ->
register_module(Mod, pubsub_serverinfo_codec_external).
unregister_module(Mod) ->
unregister_module(Mod,
pubsub_serverinfo_codec_external).
format_error({bad_attr_value, Attr, Tag, XMLNS}) ->
<<"Bad value of attribute '", Attr/binary, "' in tag <",
Tag/binary, "/> qualified by namespace '", XMLNS/binary,
"'">>;
format_error({bad_cdata_value, <<>>, Tag, XMLNS}) ->
<<"Bad value of cdata in tag <", Tag/binary,
"/> qualified by namespace '", XMLNS/binary, "'">>;
format_error({missing_tag, Tag, XMLNS}) ->
<<"Missing tag <", Tag/binary,
"/> qualified by namespace '", XMLNS/binary, "'">>;
format_error({missing_attr, Attr, Tag, XMLNS}) ->
<<"Missing attribute '", Attr/binary, "' in tag <",
Tag/binary, "/> qualified by namespace '", XMLNS/binary,
"'">>;
format_error({missing_cdata, <<>>, Tag, XMLNS}) ->
<<"Missing cdata in tag <", Tag/binary,
"/> qualified by namespace '", XMLNS/binary, "'">>;
format_error({unknown_tag, Tag, XMLNS}) ->
<<"Unknown tag <", Tag/binary,
"/> qualified by namespace '", XMLNS/binary, "'">>;
format_error({missing_tag_xmlns, Tag}) ->
<<"Missing namespace for tag <", Tag/binary, "/>">>.
io_format_error({bad_attr_value, Attr, Tag, XMLNS}) ->
{<<"Bad value of attribute '~s' in tag <~s/> "
"qualified by namespace '~s'">>,
[Attr, Tag, XMLNS]};
io_format_error({bad_cdata_value, <<>>, Tag, XMLNS}) ->
{<<"Bad value of cdata in tag <~s/> qualified "
"by namespace '~s'">>,
[Tag, XMLNS]};
io_format_error({missing_tag, Tag, XMLNS}) ->
{<<"Missing tag <~s/> qualified by namespace "
"'~s'">>,
[Tag, XMLNS]};
io_format_error({missing_attr, Attr, Tag, XMLNS}) ->
{<<"Missing attribute '~s' in tag <~s/> "
"qualified by namespace '~s'">>,
[Attr, Tag, XMLNS]};
io_format_error({missing_cdata, <<>>, Tag, XMLNS}) ->
{<<"Missing cdata in tag <~s/> qualified "
"by namespace '~s'">>,
[Tag, XMLNS]};
io_format_error({unknown_tag, Tag, XMLNS}) ->
{<<"Unknown tag <~s/> qualified by namespace "
"'~s'">>,
[Tag, XMLNS]};
io_format_error({missing_tag_xmlns, Tag}) ->
{<<"Missing namespace for tag <~s/>">>, [Tag]}.
get_attr(Attr, Attrs, Default) ->
case lists:keyfind(Attr, 1, Attrs) of
{_, Val} -> Val;
false -> Default
end.
enc_xmlns_attrs(XMLNS, XMLNS) -> [];
enc_xmlns_attrs(XMLNS, _) -> [{<<"xmlns">>, XMLNS}].
choose_top_xmlns(<<>>, NSList, TopXMLNS) ->
case lists:member(TopXMLNS, NSList) of
true -> TopXMLNS;
false -> hd(NSList)
end;
choose_top_xmlns(XMLNS, _, _) -> XMLNS.
register_module(Mod, ResolverMod) ->
MD5Sum = try Mod:module_info(md5) of
Val -> Val
catch
error:badarg ->
{ok, {Mod, Val}} = beam_lib:md5(code:which(Mod)),
Val
end,
case orddict:find(Mod, ResolverMod:modules()) of
{ok, MD5Sum} -> ok;
_ ->
Mods = orddict:store(Mod,
MD5Sum,
ResolverMod:modules()),
recompile_resolver(Mods, ResolverMod)
end.
unregister_module(Mod, ResolverMod) ->
case orddict:find(Mod, ResolverMod:modules()) of
{ok, _} ->
Mods = orddict:erase(Mod, ResolverMod:modules()),
recompile_resolver(Mods, ResolverMod);
error -> ok
end.
recompile_resolver(Mods, ResolverMod) ->
Tags = lists:flatmap(fun (M) ->
[{Name, XMLNS, M} || {Name, XMLNS} <- M:tags()]
end,
orddict:fetch_keys(Mods)),
Records = lists:flatmap(fun (M) ->
[{RecName, RecSize, M}
|| {RecName, RecSize} <- M:records()]
end,
orddict:fetch_keys(Mods)),
Lookup1 = string:join(lists:map(fun ({RecName,
RecSize,
M}) ->
io_lib:format("lookup({~s}) -> '~s'",
[string:join([io_lib:format("'~s'",
[RecName])
| ["_"
|| _
<- lists:seq(1,
RecSize)]],
","),
M])
end,
Records)
++
["lookup(Term) -> erlang:error(badarg, "
"[Term])."],
";" ++ io_lib:nl()),
Lookup2 = string:join(lists:map(fun ({Name,
XMLNS,
M}) ->
io_lib:format("lookup(~w, ~w) -> '~s'",
[Name, XMLNS, M])
end,
Tags)
++ ["lookup(_, _) -> undefined."],
";" ++ io_lib:nl()),
Modules = io_lib:format("modules() -> [~s].",
[string:join([io_lib:format("{'~s', ~w}", [M, S])
|| {M, S} <- Mods],
",")]),
Module = io_lib:format("-module(~s).", [ResolverMod]),
Compile = "-compile(export_all).",
Forms = lists:map(fun (Expr) ->
{ok, Tokens, _} =
erl_scan:string(lists:flatten(Expr)),
{ok, Form} = erl_parse:parse_form(Tokens),
Form
end,
[Module, Compile, Modules, Lookup1, Lookup2]),
{ok, Code} = case compile:forms(Forms, []) of
{ok, ResolverMod, Bin} -> {ok, Bin};
{ok, ResolverMod, Bin, _Warnings} -> {ok, Bin};
Error -> Error
end,
{module, ResolverMod} = code:load_binary(ResolverMod,
"nofile",
Code),
ok.
dec_enum(Val, Enums) ->
AtomVal = erlang:binary_to_existing_atom(Val, utf8),
case lists:member(AtomVal, Enums) of
true -> AtomVal
end.
enc_enum(Atom) -> erlang:atom_to_binary(Atom, utf8).
pp(pubsub_serverinfo, 1) -> [domain];
pp(pubsub_serverinfo_domain, 2) ->
[name, remote_domain];
pp(pubsub_serverinfo_remote_domain, 2) -> [name, type];
pp(xmlel, 3) -> [name, attrs, children];
pp(Name, Arity) ->
try get_mod(erlang:make_tuple(Arity + 1,
undefined,
[{1, Name}]))
of
Mod -> Mod:pp(Name, Arity)
catch
error:badarg -> no
end.
records() ->
[{pubsub_serverinfo, 1},
{pubsub_serverinfo_domain, 2},
{pubsub_serverinfo_remote_domain, 2}].
get_mod(<<"serverinfo">>,
<<"urn:xmpp:serverinfo:0">>) ->
pubsub_serverinfo_codec;
get_mod(<<"remote-domain">>,
<<"urn:xmpp:serverinfo:0">>) ->
pubsub_serverinfo_codec;
get_mod(<<"federation">>,
<<"urn:xmpp:serverinfo:0">>) ->
pubsub_serverinfo_codec;
get_mod(<<"domain">>, <<"urn:xmpp:serverinfo:0">>) ->
pubsub_serverinfo_codec;
get_mod(<<"connection">>,
<<"urn:xmpp:serverinfo:0">>) ->
pubsub_serverinfo_codec;
get_mod(Name, XMLNS) ->
pubsub_serverinfo_codec_external:lookup(Name, XMLNS).
get_mod({pubsub_serverinfo, _}) ->
pubsub_serverinfo_codec;
get_mod({pubsub_serverinfo_domain, _, _}) ->
pubsub_serverinfo_codec;
get_mod({pubsub_serverinfo_remote_domain, _, _}) ->
pubsub_serverinfo_codec;
get_mod(Record) ->
pubsub_serverinfo_codec_external:lookup(Record).
decode_pubsub_serverinfo_connection(__TopXMLNS, __Opts,
{xmlel, <<"connection">>, _attrs, _els}) ->
Type =
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
_attrs,
undefined),
Type.
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
[{<<"type">>, _val} | _attrs],
_Type) ->
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
_attrs,
_val);
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
[_ | _attrs], Type) ->
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
_attrs,
Type);
decode_pubsub_serverinfo_connection_attrs(__TopXMLNS,
[], Type) ->
decode_pubsub_serverinfo_connection_attr_type(__TopXMLNS,
Type).
encode_pubsub_serverinfo_connection(Type, __TopXMLNS) ->
__NewTopXMLNS =
pubsub_serverinfo_codec:choose_top_xmlns(<<"urn:xmpp:serverinfo:0">>,
[],
__TopXMLNS),
_els = [],
_attrs =
encode_pubsub_serverinfo_connection_attr_type(Type,
pubsub_serverinfo_codec:enc_xmlns_attrs(__NewTopXMLNS,
__TopXMLNS)),
{xmlel, <<"connection">>, _attrs, _els}.
decode_pubsub_serverinfo_connection_attr_type(__TopXMLNS,
undefined) ->
erlang:error({pubsub_serverinfo_codec,
{missing_attr,
<<"type">>,
<<"connection">>,
__TopXMLNS}});
decode_pubsub_serverinfo_connection_attr_type(__TopXMLNS,
_val) ->
case catch dec_enum(_val, [incoming, outgoing, bidi]) of
{'EXIT', _} ->
erlang:error({pubsub_serverinfo_codec,
{bad_attr_value,
<<"type">>,
<<"connection">>,
__TopXMLNS}});
_res -> _res
end.
encode_pubsub_serverinfo_connection_attr_type(_val,
_acc) ->
[{<<"type">>, enc_enum(_val)} | _acc].
decode_pubsub_serverinfo_remote_domain(__TopXMLNS,
__Opts,
{xmlel,
<<"remote-domain">>,
_attrs,
_els}) ->
Type =
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts,
_els,
[]),
Name =
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
_attrs,
undefined),
{pubsub_serverinfo_remote_domain, Name, Type}.
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts, [], Type) ->
lists:reverse(Type);
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts,
[{xmlel,
<<"connection">>,
_attrs,
_} =
_el
| _els],
Type) ->
case pubsub_serverinfo_codec:get_attr(<<"xmlns">>,
_attrs,
__TopXMLNS)
of
<<"urn:xmpp:serverinfo:0">> ->
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts,
_els,
[decode_pubsub_serverinfo_connection(<<"urn:xmpp:serverinfo:0">>,
__Opts,
_el)
| Type]);
_ ->
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts,
_els,
Type)
end;
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts, [_ | _els], Type) ->
decode_pubsub_serverinfo_remote_domain_els(__TopXMLNS,
__Opts,
_els,
Type).
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
[{<<"name">>, _val} | _attrs],
_Name) ->
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
_attrs,
_val);
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
[_ | _attrs], Name) ->
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
_attrs,
Name);
decode_pubsub_serverinfo_remote_domain_attrs(__TopXMLNS,
[], Name) ->
decode_pubsub_serverinfo_remote_domain_attr_name(__TopXMLNS,
Name).
encode_pubsub_serverinfo_remote_domain({pubsub_serverinfo_remote_domain,
Name,
Type},
__TopXMLNS) ->
__NewTopXMLNS =
pubsub_serverinfo_codec:choose_top_xmlns(<<"urn:xmpp:serverinfo:0">>,
[],
__TopXMLNS),
_els =
lists:reverse('encode_pubsub_serverinfo_remote_domain_$type'(Type,
__NewTopXMLNS,
[])),
_attrs =
encode_pubsub_serverinfo_remote_domain_attr_name(Name,
pubsub_serverinfo_codec:enc_xmlns_attrs(__NewTopXMLNS,
__TopXMLNS)),
{xmlel, <<"remote-domain">>, _attrs, _els}.
'encode_pubsub_serverinfo_remote_domain_$type'([],
__TopXMLNS, _acc) ->
_acc;
'encode_pubsub_serverinfo_remote_domain_$type'([Type
| _els],
__TopXMLNS, _acc) ->
'encode_pubsub_serverinfo_remote_domain_$type'(_els,
__TopXMLNS,
[encode_pubsub_serverinfo_connection(Type,
__TopXMLNS)
| _acc]).
decode_pubsub_serverinfo_remote_domain_attr_name(__TopXMLNS,
undefined) ->
erlang:error({pubsub_serverinfo_codec,
{missing_attr,
<<"name">>,
<<"remote-domain">>,
__TopXMLNS}});
decode_pubsub_serverinfo_remote_domain_attr_name(__TopXMLNS,
_val) ->
_val.
encode_pubsub_serverinfo_remote_domain_attr_name(_val,
_acc) ->
[{<<"name">>, _val} | _acc].
decode_pubsub_serverinfo_federation(__TopXMLNS, __Opts,
{xmlel, <<"federation">>, _attrs, _els}) ->
Remote_domain =
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts,
_els,
[]),
Remote_domain.
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts, [], Remote_domain) ->
lists:reverse(Remote_domain);
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts,
[{xmlel,
<<"remote-domain">>,
_attrs,
_} =
_el
| _els],
Remote_domain) ->
case pubsub_serverinfo_codec:get_attr(<<"xmlns">>,
_attrs,
__TopXMLNS)
of
<<"urn:xmpp:serverinfo:0">> ->
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts,
_els,
[decode_pubsub_serverinfo_remote_domain(<<"urn:xmpp:serverinfo:0">>,
__Opts,
_el)
| Remote_domain]);
_ ->
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts,
_els,
Remote_domain)
end;
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts, [_ | _els], Remote_domain) ->
decode_pubsub_serverinfo_federation_els(__TopXMLNS,
__Opts,
_els,
Remote_domain).
encode_pubsub_serverinfo_federation(Remote_domain,
__TopXMLNS) ->
__NewTopXMLNS =
pubsub_serverinfo_codec:choose_top_xmlns(<<"urn:xmpp:serverinfo:0">>,
[],
__TopXMLNS),
_els =
lists:reverse('encode_pubsub_serverinfo_federation_$remote_domain'(Remote_domain,
__NewTopXMLNS,
[])),
_attrs =
pubsub_serverinfo_codec:enc_xmlns_attrs(__NewTopXMLNS,
__TopXMLNS),
{xmlel, <<"federation">>, _attrs, _els}.
'encode_pubsub_serverinfo_federation_$remote_domain'([],
__TopXMLNS, _acc) ->
_acc;
'encode_pubsub_serverinfo_federation_$remote_domain'([Remote_domain
| _els],
__TopXMLNS, _acc) ->
'encode_pubsub_serverinfo_federation_$remote_domain'(_els,
__TopXMLNS,
[encode_pubsub_serverinfo_remote_domain(Remote_domain,
__TopXMLNS)
| _acc]).
decode_pubsub_serverinfo_domain(__TopXMLNS, __Opts,
{xmlel, <<"domain">>, _attrs, _els}) ->
Remote_domain =
decode_pubsub_serverinfo_domain_els(__TopXMLNS,
__Opts,
_els,
undefined),
Name = decode_pubsub_serverinfo_domain_attrs(__TopXMLNS,
_attrs,
undefined),
{pubsub_serverinfo_domain, Name, Remote_domain}.
decode_pubsub_serverinfo_domain_els(__TopXMLNS, __Opts,
[], Remote_domain) ->
Remote_domain;
decode_pubsub_serverinfo_domain_els(__TopXMLNS, __Opts,
[{xmlel, <<"federation">>, _attrs, _} = _el
| _els],
Remote_domain) ->
case pubsub_serverinfo_codec:get_attr(<<"xmlns">>,
_attrs,
__TopXMLNS)
of
<<"urn:xmpp:serverinfo:0">> ->
decode_pubsub_serverinfo_domain_els(__TopXMLNS,
__Opts,
_els,
decode_pubsub_serverinfo_federation(<<"urn:xmpp:serverinfo:0">>,
__Opts,
_el));
_ ->
decode_pubsub_serverinfo_domain_els(__TopXMLNS,
__Opts,
_els,
Remote_domain)
end;
decode_pubsub_serverinfo_domain_els(__TopXMLNS, __Opts,
[_ | _els], Remote_domain) ->
decode_pubsub_serverinfo_domain_els(__TopXMLNS,
__Opts,
_els,
Remote_domain).
decode_pubsub_serverinfo_domain_attrs(__TopXMLNS,
[{<<"name">>, _val} | _attrs], _Name) ->
decode_pubsub_serverinfo_domain_attrs(__TopXMLNS,
_attrs,
_val);
decode_pubsub_serverinfo_domain_attrs(__TopXMLNS,
[_ | _attrs], Name) ->
decode_pubsub_serverinfo_domain_attrs(__TopXMLNS,
_attrs,
Name);
decode_pubsub_serverinfo_domain_attrs(__TopXMLNS, [],
Name) ->
decode_pubsub_serverinfo_domain_attr_name(__TopXMLNS,
Name).
encode_pubsub_serverinfo_domain({pubsub_serverinfo_domain,
Name,
Remote_domain},
__TopXMLNS) ->
__NewTopXMLNS =
pubsub_serverinfo_codec:choose_top_xmlns(<<"urn:xmpp:serverinfo:0">>,
[],
__TopXMLNS),
_els =
lists:reverse('encode_pubsub_serverinfo_domain_$remote_domain'(Remote_domain,
__NewTopXMLNS,
[])),
_attrs = encode_pubsub_serverinfo_domain_attr_name(Name,
pubsub_serverinfo_codec:enc_xmlns_attrs(__NewTopXMLNS,
__TopXMLNS)),
{xmlel, <<"domain">>, _attrs, _els}.
'encode_pubsub_serverinfo_domain_$remote_domain'(undefined,
__TopXMLNS, _acc) ->
_acc;
'encode_pubsub_serverinfo_domain_$remote_domain'(Remote_domain,
__TopXMLNS, _acc) ->
[encode_pubsub_serverinfo_federation(Remote_domain,
__TopXMLNS)
| _acc].
decode_pubsub_serverinfo_domain_attr_name(__TopXMLNS,
undefined) ->
erlang:error({pubsub_serverinfo_codec,
{missing_attr, <<"name">>, <<"domain">>, __TopXMLNS}});
decode_pubsub_serverinfo_domain_attr_name(__TopXMLNS,
_val) ->
_val.
encode_pubsub_serverinfo_domain_attr_name(_val, _acc) ->
[{<<"name">>, _val} | _acc].
decode_pubsub_serverinfo(__TopXMLNS, __Opts,
{xmlel, <<"serverinfo">>, _attrs, _els}) ->
Domain = decode_pubsub_serverinfo_els(__TopXMLNS,
__Opts,
_els,
[]),
{pubsub_serverinfo, Domain}.
decode_pubsub_serverinfo_els(__TopXMLNS, __Opts, [],
Domain) ->
lists:reverse(Domain);
decode_pubsub_serverinfo_els(__TopXMLNS, __Opts,
[{xmlel, <<"domain">>, _attrs, _} = _el | _els],
Domain) ->
case pubsub_serverinfo_codec:get_attr(<<"xmlns">>,
_attrs,
__TopXMLNS)
of
<<"urn:xmpp:serverinfo:0">> ->
decode_pubsub_serverinfo_els(__TopXMLNS,
__Opts,
_els,
[decode_pubsub_serverinfo_domain(<<"urn:xmpp:serverinfo:0">>,
__Opts,
_el)
| Domain]);
_ ->
decode_pubsub_serverinfo_els(__TopXMLNS,
__Opts,
_els,
Domain)
end;
decode_pubsub_serverinfo_els(__TopXMLNS, __Opts,
[_ | _els], Domain) ->
decode_pubsub_serverinfo_els(__TopXMLNS,
__Opts,
_els,
Domain).
encode_pubsub_serverinfo({pubsub_serverinfo, Domain},
__TopXMLNS) ->
__NewTopXMLNS =
pubsub_serverinfo_codec:choose_top_xmlns(<<"urn:xmpp:serverinfo:0">>,
[],
__TopXMLNS),
_els =
lists:reverse('encode_pubsub_serverinfo_$domain'(Domain,
__NewTopXMLNS,
[])),
_attrs =
pubsub_serverinfo_codec:enc_xmlns_attrs(__NewTopXMLNS,
__TopXMLNS),
{xmlel, <<"serverinfo">>, _attrs, _els}.
'encode_pubsub_serverinfo_$domain'([], __TopXMLNS,
_acc) ->
_acc;
'encode_pubsub_serverinfo_$domain'([Domain | _els],
__TopXMLNS, _acc) ->
'encode_pubsub_serverinfo_$domain'(_els,
__TopXMLNS,
[encode_pubsub_serverinfo_domain(Domain,
__TopXMLNS)
| _acc]).

View file

@ -0,0 +1,12 @@
%% Created automatically by XML generator (fxml_gen.erl)
%% Source: pubsub_serverinfo_codec.spec
-module(pubsub_serverinfo_codec_external).
-compile(export_all).
modules() -> [].
lookup(_, _) -> undefined.
lookup(Term) -> erlang:error(badarg, [Term]).