1%%
2%% Copyright (c) 2013 Grasshopper
3%%
4%% Permission is hereby granted, free of charge, to any person obtaining a copy
5%% of this software and associated documentation files (the "Software"), to deal
6%% in the Software without restriction, including without limitation the rights
7%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8%% copies of the Software, and to permit persons to whom the Software is
9%% furnished to do so, subject to the following conditions:
10%%
11%% The above copyright notice and this permission notice shall be included in
12%% all copies or substantial portions of the Software.
13%%
14%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20%% THE SOFTWARE.
21%%
22%% Contributors:
23%%   Chris Rienzo <chris.rienzo@grasshopper.com>
24%%
25%% Maintainer: Chris Rienzo <chris.rienzo@grasshopper.com>
26%%
27%% mod_rayo_gateway.erl -- ejabberd Rayo gateway module
28%%
29-module(mod_rayo_gateway).
30
31-include("ejabberd.hrl").
32-include("jlib.hrl").
33
34-behavior(gen_server).
35-behavior(gen_mod).
36
37%%  JID mappings
38%%
39%%  Entity          Internal JID                     Mapped JID
40%%  ======          ===============                  ===============
41%%  Client          user@domain/resource             gateway@internal_domain/gw-resource
42%%  Node            node_domain                      external_domain
43%%  Call            uuid@node_domain                 node_domain|uuid@external_domain
44%%  Call Resource   uuid@node_domain/resource        node_domain|uuid@external_domain/resource
45%%  Mixer           name@node_domain                 node_domain|name@external_domain
46%%  Mixer Resource  name@node_domain/resource        node_domain|name@external_domain/resource
47
48%% TODO don't allow nodes to act as clients
49%% TODO don't allow clients to act as nodes
50
51-export([
52	start_link/2,
53	start/2,
54	stop/1,
55	init/1,
56	handle_call/3,
57	handle_cast/2,
58	handle_info/2,
59	terminate/2,
60	code_change/3,
61	route_internal/3,
62	route_external/3
63]).
64
65-define(PROCNAME, ejabberd_mod_rayo_gateway).
66-define(NS_RAYO, "urn:xmpp:rayo:1").
67-define(NS_PING, "urn:xmpp:ping").
68
69-record(rayo_config, {name, value}).
70-record(rayo_clients, {jid, status}).
71-record(rayo_nodes, {jid, status}).
72-record(rayo_entities, {external_jid, internal_jid, dcp_jid, type}).
73
74start_link(Host, Opts) ->
75	Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
76	gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
77
78% Start the module process
79start(Host, Opts) ->
80	Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
81	ChildSpec = {Proc,
82		{?MODULE, start_link, [Host, Opts]},
83		temporary,
84		1000,
85		worker,
86		[?MODULE]},
87	supervisor:start_child(ejabberd_sup, ChildSpec).
88
89% Shutdown the module
90stop(Host) ->
91	Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
92	gen_server:call(Proc, stop),
93	supervisor:terminate_child(ejabberd_sup, Proc),
94	supervisor:delete_child(ejabberd_sup, Proc).
95
96% Initialize the module
97init([Host, Opts]) ->
98	?DEBUG("MOD_RAYO_GATEWAY: Starting", []),
99
100	mnesia:delete_table(rayo_clients),
101	mnesia:create_table(rayo_clients, [{attributes, record_info(fields, rayo_clients)}]),
102	mnesia:delete_table(rayo_nodes),
103	mnesia:create_table(rayo_nodes, [{attributes, record_info(fields, rayo_nodes)}]),
104	mnesia:delete_table(rayo_entities),
105	mnesia:create_table(rayo_entities, [{attributes, record_info(fields, rayo_entities)}, {index, [internal_jid]}]),
106	mnesia:delete_table(rayo_config),
107	mnesia:create_table(rayo_config, [{attributes, record_info(fields, rayo_config)}]),
108
109	{A1,A2,A3} = now(),
110	random:seed(A1, A2, A3),
111
112	% create virtual domains
113	InternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo-int.@HOST@"),
114	ExternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo.@HOST@"),
115	{ok, Hostname} = inet:gethostname(),
116	InternalClient = "gateway@" ++ InternalDomain ++ "/" ++ Hostname ++ "-" ++ integer_to_list(random:uniform(65535)),
117	?DEBUG("MOD_RAYO_GATEWAY: InternalDomain = ~p, ExternalDomain = ~p, InternalClient = ~p", [InternalDomain, ExternalDomain, InternalClient]),
118	mnesia:transaction(
119		fun() ->
120			mnesia:write(#rayo_config{name = "internal_domain", value = InternalDomain}),
121			mnesia:write(#rayo_config{name = "internal_client", value = InternalClient}),
122			mnesia:write(#rayo_config{name = "external_domain", value = ExternalDomain})
123		end
124	),
125
126	% set up routes to virtual domains
127	ejabberd_router:register_route(InternalDomain, {apply, ?MODULE, route_internal}),
128	ejabberd_router:register_route(ExternalDomain, {apply, ?MODULE, route_external}),
129	{ok, Host}.
130
131handle_call(stop, _From, Host) ->
132	{stop, normal, ok, Host}.
133
134handle_cast(_Msg, Host) ->
135	{noreply, Host}.
136
137handle_info(_Msg, Host) ->
138	{noreply, Host}.
139
140terminate(_Reason, Host) ->
141	ejabberd_router:unregister_route(Host),
142	ok.
143
144code_change(_OldVsn, Host, _Extra) ->
145	{ok, Host}.
146
147register_rayo_node(Jid) ->
148	Write = fun() ->
149		mnesia:write(#rayo_nodes{jid = Jid, status = "online" })
150	end,
151	Result = mnesia:transaction(Write),
152	?DEBUG("MOD_RAYO_GATEWAY: register node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, num_rayo_nodes()]),
153	case num_clients() >= 1 of
154		true ->
155			ejabberd_router:route(internal_client(), Jid, online_presence());
156		_ ->
157			ok
158	end,
159	ok.
160
161% TODO call this when s2s connection is dropped
162unregister_rayo_node(Jid) ->
163	Delete = fun() ->
164		mnesia:delete({rayo_nodes, Jid})
165	end,
166	Result = mnesia:transaction(Delete),
167	Size = mnesia:table_info(rayo_nodes, size),
168	?DEBUG("MOD_RAYO_GATEWAY: unregister node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, Size]),
169	ok.
170
171% Add client
172register_rayo_client(Jid) ->
173	Write = fun() ->
174		mnesia:write(#rayo_clients{jid = Jid, status = "online" })
175	end,
176	Result = mnesia:transaction(Write),
177	Size = num_clients(),
178	?DEBUG("MOD_RAYO_GATEWAY: register client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]),
179	case Size of
180		1 ->
181			route_to_list(internal_client(), all_rayo_nodes(), online_presence());
182		_ ->
183			ok
184	end,
185	ok.
186
187% Remove client
188% TODO call this when c2s connection is dropped
189unregister_rayo_client(Jid) ->
190	Delete = fun() ->
191		mnesia:delete({rayo_clients, Jid})
192	end,
193	Result = mnesia:transaction(Delete),
194	Size = num_clients(),
195	?DEBUG("MOD_RAYO_GATEWAY: unregister client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]),
196	case Size of
197		0 ->
198			route_to_list(internal_client(), all_rayo_nodes(), offline_presence());
199		_ ->
200			ok
201	end,
202	ok.
203
204% Add node entity
205register_rayo_node_entity(ExtJid, IntJid, DcpJid, Type) ->
206	Write = fun() ->
207		mnesia:write(#rayo_entities{external_jid = ExtJid, internal_jid = IntJid, dcp_jid = DcpJid, type = Type})
208	end,
209	Result = mnesia:transaction(Write),
210	Size = mnesia:table_info(rayo_entities, size),
211	?DEBUG("MOD_RAYO_GATEWAY: register entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]),
212	ok.
213
214% Remove node entity
215unregister_rayo_node_entity(ExtJid) ->
216	Delete = fun() ->
217		mnesia:delete({rayo_entities, ExtJid})
218	end,
219	Result = mnesia:transaction(Delete),
220	Size = mnesia:table_info(rayo_entities, size),
221	?DEBUG("MOD_RAYO_GATEWAY: unregister entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]),
222	ok.
223
224% find node entity given enitity's (or its component's) internal JID
225find_rayo_node_entity_by_int_jid(IntJid) ->
226	% remove resource from JID to find component's parent call/mixer
227	case mnesia:dirty_index_read(rayo_entities, jlib:jid_remove_resource(IntJid), #rayo_entities.internal_jid) of
228		[Entity | _] ->
229			Entity;
230		_ ->
231			none
232	end.
233
234% find node entity given enitity's (or its component's) external JID
235find_rayo_node_entity_by_ext_jid(ExtJid) ->
236	% remove resource from JID to find component's parent call/mixer
237	case mnesia:dirty_read(rayo_entities, jlib:jid_remove_resource(ExtJid)) of
238		[Entity | _] ->
239			Entity;
240		_ ->
241			none
242	end.
243
244% find entity Definitive Controlling Party JID given entity external JID
245find_rayo_node_entity_dcp_by_ext_jid(ExtJid) ->
246	case find_rayo_node_entity_by_ext_jid(ExtJid) of
247		{rayo_entities, _, _, DcpJid, _} ->
248			DcpJid;
249		_ ->
250			none
251	end.
252
253% find entity Definitive Controlling Party JID given entity internal JID
254find_rayo_node_entity_dcp_by_int_jid(IntJid) ->
255	case find_rayo_node_entity_by_int_jid(IntJid) of
256		{rayo_entities, _, _, DcpJid, _} ->
257			DcpJid;
258		_ ->
259			none
260	end.
261
262% create External JID from Internal JID
263% intnode@intdomain/resource -> intdomain-intnode@extdomain/resource
264create_external_jid({jid, Node, Domain, Resource, _, _, _}) ->
265	jlib:make_jid(Domain ++ "|" ++ Node, jlib:jid_to_string(external_domain()), Resource).
266
267% create Internal JID from External JID
268% intdomain-intnode@extdomain/resource -> intnode@intdomain/resource
269create_internal_jid({jid, Node, _Domain, Resource, _, _, _}) ->
270	% TODO use rayo_entities to lookup node... it's safer
271	Idx = string:str(Node, "|"),
272	case Idx > 0 of
273		true ->
274			jlib:make_jid(string:substr(Node, Idx + 1), string:substr(Node, 1, Idx - 1), Resource);
275		false ->
276			none
277	end.
278
279% Take control of entity
280% Return {true, internal entity JID} if successful
281set_entity_dcp(PcpJid, EntityJid) ->
282	SetDcp = fun() ->
283		case mnesia:wread(rayo_entities, EntityJid) of
284			[{rayo_entities, EntityJid, InternalJid, none, Type}] ->
285				% take control
286				case mnesia:write(#rayo_entities{external_jid = EntityJid, internal_jid = InternalJid, dcp_jid = PcpJid, type = Type}) of
287					ok ->
288						{true, InternalJid};
289					Else ->
290						{error, Else}
291				end;
292			_ ->
293				{false, []}
294		end
295	end,
296	{_, Result} = mnesia:transaction(SetDcp),
297	Result.
298
299% Check if PCP has control of entity
300% Return {true, internal entity JID} if true
301is_entity_dcp(PcpJid, EntityJid) ->
302	% quick check first
303	case mnesia:dirty_read(rayo_entities, EntityJid) of
304		[{rayo_entities, EntityJid, _, none, _}] ->
305			% take control
306			set_entity_dcp(PcpJid, EntityJid);
307		[{rayo_entities, EntityJid, InternalJid, PcpJid, _}] ->
308			{true, InternalJid};
309		[{rayo_entities, EntityJid, InternalJid, _, _}] ->
310			{false, InternalJid};
311		[] ->
312			?DEBUG("MOD_RAYO_GATEWAY: no match for EntityJid ~p", [EntityJid]),
313			{false, none}
314	end.
315
316% Handle presence to external domain
317route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
318	?DEBUG("MOD_RAYO_GATEWAY: got client presence ~n~p", [Presence]),
319	route_client_presence(From, To, Presence),
320	ok;
321
322% Handle presence to external domain resource
323route_external(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
324	?DEBUG("MOD_RAYO_GATEWAY: got client presence to mixer ~n~p", [Presence]),
325	% TODO check if actually being sent to mixer...
326	route_client_presence_to_mixer(From, To, Presence),
327	ok;
328
329% Handle <message> to external domain
330route_external(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) ->
331	% ignore
332	?DEBUG("MOD_RAYO_GATEWAY: got client message ~n~p", [Message]),
333	ok;
334
335% Handle <iq> to external domain
336route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
337	?DEBUG("MOD_RAYO_GATEWAY: got client iq to gateway ~n~p", [IQ]),
338	case get_attribute_as_list(IQ, "type", "") of
339		"get" ->
340			case get_element(IQ, ?NS_PING, "ping") of
341				undefined ->
342					route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
343				_ ->
344					route_result_reply(To, From, IQ)
345			end;
346		"set" ->
347			case get_element(IQ, ?NS_RAYO, "dial") of
348				undefined->
349					route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
350				_ ->
351					route_dial_call(To, From, IQ)
352			end;
353		"" ->
354			route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
355	end,
356	ok;
357
358% Handle <iq> to external domain resource
359route_external(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
360	?DEBUG("MOD_RAYO_GATEWAY: got client iq ~n~p", [IQ]),
361	case is_entity_dcp(From, To) of
362		{true, _} ->
363			IntFrom = internal_client(),
364			IntTo = create_internal_jid(To),
365			route_iq_request(IntFrom, IntTo, IQ, fun(IQReply) -> route_iq_response(From, To, IQ, IQReply) end);
366		{false, _} ->
367			route_error_reply(To, From, IQ, ?ERR_CONFLICT);
368		_ ->
369			route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
370	end,
371	ok.
372
373% Handle <presence> to internal domain
374route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
375	?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]),
376	route_server_presence(From, To, Presence),
377	ok;
378
379% Handle <presence> to internal domain resource
380route_internal(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
381	?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]),
382	case To =:= internal_client() of
383		true ->
384			route_server_presence(From, To, Presence);
385		false ->
386			% TODO implement
387			ok
388	end,
389	ok;
390
391% Handle <message> to internal domain
392route_internal(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) ->
393	?DEBUG("MOD_RAYO_GATEWAY: got node message ~n~p", [Message]),
394	% ignore
395	ok;
396
397% Handle <iq> to internal domain.
398route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
399	?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]),
400	case get_attribute_as_list(IQ, "type", "") of
401		"get" ->
402			case get_element(IQ, ?NS_PING, "ping") of
403				undefined ->
404					route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
405				_ ->
406					route_result_reply(To, From, IQ)
407			end;
408		"result" ->
409			ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
410		"error" ->
411			ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
412		"" ->
413			% don't allow get/set from nodes
414			route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
415	end,
416	ok;
417
418% Handle <iq> to internal domain resource.
419route_internal(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
420	?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]),
421	case get_attribute_as_list(IQ, "type", "") of
422		"result" ->
423			ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
424		"error" ->
425			ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
426		_ ->
427			% Don't allow get/set from nodes
428			route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
429	end,
430	ok.
431
432% Process presence message from rayo node
433route_rayo_node_presence(From, _To, Presence) ->
434	case get_attribute_as_list(Presence, "type", "") of
435		"" ->
436			case get_element(Presence, "show") of
437				undefined ->
438					?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []);
439				Show ->
440					case get_cdata_as_list(Show) of
441						"chat" ->
442							register_rayo_node(From);
443						"dnd" ->
444							unregister_rayo_node(From);
445						"xa" ->
446							unregister_rayo_node(From);
447						"" ->
448							unregister_rayo_node(From)
449					end
450			end;
451		"unavailable" ->
452			%TODO broadcast end instead?
453			unregister_rayo_node(From)
454	end,
455	ok.
456
457% Process presence from call
458route_call_presence(From, _To, Presence) ->
459	%TODO join/unjoin mixer events
460	case get_attribute_as_list(Presence, "type", "") of
461		"" ->
462			case get_element(Presence, ?NS_RAYO, "offer") of
463				undefined ->
464					route_rayo_entity_stanza(From, Presence);
465				_ ->
466					route_offer_call(From, Presence)
467			end;
468		"unavailable" ->
469			case get_element(Presence, ?NS_RAYO, "end") of
470				undefined ->
471					route_rayo_entity_stanza(From, Presence);
472				_ ->
473					route_rayo_entity_stanza(From, Presence),
474					unregister_rayo_node_entity(create_external_jid(From))
475			end
476	end,
477	ok.
478
479% presence from node
480route_server_presence({jid, [], _Domain, [], [], _LDomain, []} = From, To, Presence) ->
481	route_rayo_node_presence(From, To, Presence),
482	ok;
483
484% presence from call/mixer
485route_server_presence(From, To, Presence) ->
486	% TODO mixer
487	route_call_presence(From, To, Presence),
488	ok.
489
490% presence from Rayo Client
491route_client_presence(From, _To, Presence) ->
492	case get_attribute_as_list(Presence, "type", "") of
493		"" ->
494			case get_element(Presence, "show") of
495				undefined ->
496					?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []);
497				Show ->
498					case get_cdata_as_list(Show) of
499						"chat" ->
500							register_rayo_client(From);
501						"dnd" ->
502							unregister_rayo_client(From);
503						_ ->
504							unregister_rayo_client(From)
505					end
506			end;
507		"unavailable" ->
508			unregister_rayo_client(From);
509		_ ->
510			ok
511	end,
512	ok.
513
514% route client directed presence to mixer
515route_client_presence_to_mixer(_From, _To, _Presence) ->
516	% TODO
517	ok.
518
519% Handle offer to client
520route_offer_call(From, Offer) ->
521	% Any clients available?
522	case pick_client() of
523		none ->
524			% TODO reject?
525			ok;
526		ClientDcp ->
527			% Remember call
528			ExtFrom = create_external_jid(From),
529			register_rayo_node_entity(ExtFrom, From, ClientDcp, call),
530			ejabberd_router:route(ExtFrom, ClientDcp, Offer)
531	end,
532	ok.
533
534% convert URI to a JID
535uri_to_jid(Uri) ->
536	JidString = case string:str(Uri, "xmpp:") of
537		1 ->
538			string:substr(Uri, 6);
539		_ ->
540			Uri
541	end,
542	jlib:string_to_jid(JidString).
543
544% convert internal IQ reply to an external reply
545create_external_iq_reply(OrigIQ, {xmlelement, _, _, Els} = IQReply) ->
546	IQId = get_attribute_as_list(OrigIQ, "id", ""),
547	IQType = get_attribute_as_list(IQReply, "type", ""),
548	{xmlelement, "iq", [{"id", IQId}, {"type", IQType}], Els}.
549
550% Process dial response
551route_dial_call_response(OrigFrom, OrigTo, OrigIQ, timeout) ->
552	% TODO retry on different node?
553	route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout);
554
555route_dial_call_response(OrigFrom, OrigTo, OrigIQ, IQReply) ->
556	?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]),
557	IQReplyPacket = jlib:iq_to_xml(IQReply),
558	case get_element(IQReplyPacket, "error") of
559		undefined ->
560			case get_element(IQReplyPacket, "ref") of
561				undefined ->
562					ok;
563				Ref ->
564					IntJid = uri_to_jid(get_attribute_as_list(Ref, "uri", "")),
565					register_rayo_node_entity(create_external_jid(IntJid), IntJid, OrigFrom, call)
566			end;
567		_ ->
568			ok
569	end,
570	ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, IQReplyPacket)),
571	ok.
572
573% Forward dial to node
574route_dial_call(From, To, Dial) ->
575	% any nodes available?
576	case num_rayo_nodes() > 0 of
577		true ->
578			IntFrom = internal_client(),
579			case pick_rayo_node() of
580				none ->
581					route_error_reply(To, From, Dial, ?ERR_SERVICE_UNAVAILABLE);
582				NodeJid ->
583					route_iq_request(IntFrom, NodeJid, Dial, fun(IQReply) -> route_dial_call_response(From, To, Dial, IQReply) end)
584			end;
585		_ ->
586			route_error_reply(To, From, Dial, ?ERR_RESOURCE_CONSTRAINT)
587	end.
588
589% return configuration value given name
590config_value(Name) ->
591	case catch mnesia:dirty_read(rayo_config, Name) of
592		[{rayo_config, Name, Value}] ->
593			Value;
594		_ ->
595			""
596	end.
597
598% return internal client name
599internal_client() ->
600	jlib:string_to_jid(config_value("internal_client")).
601
602% return internal domain name
603internal_domain() ->
604	jlib:string_to_jid(config_value("internal_domain")).
605
606% return external domain name
607external_domain() ->
608	jlib:string_to_jid(config_value("external_domain")).
609
610% return number of registered clients
611num_clients() ->
612	mnesia:table_info(rayo_clients, size).
613
614% return all registered client JIDs
615all_clients() ->
616	case mnesia:transaction(fun() -> mnesia:all_keys(rayo_clients) end) of
617		{atomic, Keys} ->
618			Keys;
619		_ ->
620			[]
621	end.
622
623% pick a registered client
624pick_client() ->
625	% pick at random for now...
626	case all_clients() of
627		[] ->
628			none;
629		AllClients ->
630			lists:nth(random:uniform(length(AllClients)), AllClients)
631	end.
632
633% pick a registered node
634pick_rayo_node() ->
635	% pick at random for now...
636	case all_rayo_nodes() of
637		[] ->
638			none;
639		AllNodes ->
640			lists:nth(random:uniform(length(AllNodes)), AllNodes)
641	end.
642
643% return number of registered rayo nodes
644num_rayo_nodes() ->
645	mnesia:table_info(rayo_nodes, size).
646
647% return all rayo node JIDs
648all_rayo_nodes() ->
649	case mnesia:transaction(fun() -> mnesia:all_keys(rayo_nodes) end) of
650		{atomic, Keys} ->
651			Keys;
652		_ ->
653			[]
654	end.
655
656presence(Status) ->
657	{xmlelement, "presence", [], [
658		{xmlelement, "show", [], [
659			{xmlcdata, Status}
660		]}
661	]}.
662
663online_presence() ->
664	presence(<<"chat">>).
665
666offline_presence() ->
667	presence(<<"dnd">>).
668
669route_to_list(From, ToList, Stanza) ->
670	lists:map(fun(To) -> ejabberd_router:route(From, To, Stanza) end, ToList),
671	ok.
672
673% route stanza from entity
674route_rayo_entity_stanza(From, Stanza) ->
675	case find_rayo_node_entity_dcp_by_int_jid(From) of
676		none ->
677			?DEBUG("MOD_RAYO_GATEWAY: Failed to find DCP for ~p", [From]),
678			ok;
679		DcpJid ->
680 			ejabberd_router:route(create_external_jid(From), DcpJid, Stanza)
681 	end,
682	ok.
683
684% route IQ response from node to client
685route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout) ->
686	route_error_reply(OrigTo, OrigFrom, OrigIQ, ?ERR_REMOTE_SERVER_TIMEOUT),
687	ok;
688
689route_iq_response(OrigFrom, OrigTo, OrigIQ, IQReply) ->
690	?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]),
691	ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, jlib:iq_to_xml(IQReply))),
692	ok.
693
694% route IQ from client to node
695route_iq_request(From, To, {xmlelement, "iq", _Atts, Els}, ResponseCallback) ->
696	ejabberd_local:route_iq(From, To, #iq{type = set, sub_el = Els}, ResponseCallback),
697	ok.
698
699% route IQ error given request
700route_error_reply(From, To, IQ, Reason) ->
701	ejabberd_router:route(From, To, jlib:make_error_reply(IQ, Reason)),
702	ok.
703
704% route IQ result given request
705route_result_reply(From, To, IQ) ->
706	ejabberd_router:route(From, To, jlib:make_result_iq_reply(IQ)),
707	ok.
708
709% XML parsing helpers
710
711get_element(Element, Name) ->
712	case xml:get_subtag(Element, Name) of
713		false ->
714			undefined;
715		Subtag ->
716			Subtag
717	end.
718
719get_element(Element, NS, Name) ->
720	case get_element(Element, Name) of
721		undefined ->
722			undefined;
723		Subtag ->
724			case get_attribute_as_list(Subtag, "xmlns", "") of
725				"" ->
726					undefined;
727				NS ->
728					Subtag
729			end
730	end.
731
732get_cdata_as_list(undefined) ->
733	"";
734
735get_cdata_as_list(Element) ->
736	xml:get_tag_cdata(Element).
737
738get_element_cdata_as_list(Element, Name) ->
739	get_cdata_as_list(get_element(Element, Name)).
740
741get_element_cdata_as_list(Element, NS, Name) ->
742	get_cdata_as_list(get_element(Element, NS, Name)).
743
744get_element_attribute_as_list(Element, Name, AttrName, Default) ->
745	get_attribute_as_list(get_element(Element, Name), AttrName, Default).
746
747get_element_attribute_as_list(Element, NS, Name, AttrName, Default) ->
748	get_attribute_as_list(get_element(Element, NS, Name), AttrName, Default).
749
750get_attribute_as_list(undefined, _Name, _Default) ->
751	undefined;
752
753get_attribute_as_list(Element, Name, Default) ->
754	case xml:get_tag_attr_s(Name, Element) of
755		"" ->
756			Default;
757		Attr ->
758			Attr
759	end.
760