%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2001-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% %%---------------------------------------------------------------------- %% Purpose: Simple example of an MGC %% %% Example usage: %% %% cd megaco/examples/simple %% erl -pa ../../../megaco/ebin -s megaco_filter -s megaco %% megaco_simple_mgc:start(). %%---------------------------------------------------------------------- -module(megaco_simple_mgc). -behaviour(megaco_user). -export([ start_batch/0, start_batch/1, init_batch/3, start/0, start/2, start/4, stop/0, stop/1 ]). -export([ handle_connect/2, handle_disconnect/3, handle_syntax_error/3, handle_message_error/3, handle_trans_request/3, handle_trans_long_request/3, handle_trans_reply/4, handle_trans_ack/4, handle_unexpected_trans/3, handle_trans_request_abort/4 ]). -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). %%---------------------------------------------------------------------- %% Starting the MGC %%---------------------------------------------------------------------- start() -> start(false, false). start(Trace, Debug) -> start({deviceName, "controller"}, [], Trace, Debug). start(Mid, Config, Trace, Debug) -> put(debug, Debug), d("start -> entry with" "~n Mid: ~p" "~n Config: ~p" "~n Trace: ~p", [Mid, Config, Trace]), init_inline_trace(Trace), case megaco:start_user(Mid, [{user_mod, ?MODULE} | Config]) of ok -> d("start -> user started"), case catch do_start(Mid) of {'EXIT', Reason} -> d("start -> exited: ~n~p",[Reason]), {error, Reason}; Other -> d("start -> Other: ~n~p",[Other]), Other end; {error, Reason} -> d("start -> user start failed: ~n~p", [Reason]), {error, {start_user, Reason}} end. %% ----------------------------------------------------------------------- init_inline_trace(true) -> megaco:enable_trace(max, io); init_inline_trace(_) -> ok. %% ----------------------------------------------------------------------- do_start(Mid) -> d("do_start -> entry"), RecHandle = megaco:user_info(Mid, receive_handle), d("do_start -> RecHandle: ~n~p",[RecHandle]), TextMod = megaco_pretty_text_encoder, TextTcp = RecHandle#megaco_receive_handle{encoding_mod = TextMod, encoding_config = [], send_mod = megaco_tcp}, d("do_start -> TextTcp: ~n~p",[TextTcp]), TextUdp = TextTcp#megaco_receive_handle{send_mod = megaco_udp}, d("do_start -> TextUdp: ~n~p",[TextUdp]), BinMod = megaco_binary_encoder, BinTcp = RecHandle#megaco_receive_handle{encoding_mod = BinMod, encoding_config = [], send_mod = megaco_tcp}, d("do_start -> BinTcp: ~n~p",[BinTcp]), BinUdp = BinTcp#megaco_receive_handle{send_mod = megaco_udp}, d("do_start -> BinUdp: ~n~p",[BinUdp]), ListenTo = [{?megaco_ip_port_text, TextTcp}, {?megaco_ip_port_text, TextUdp}, {?megaco_ip_port_binary, BinTcp}, {?megaco_ip_port_binary, BinUdp} ], d("do_start -> start transports"), Transports = [{start_transport(Port, RH), Port, RH} || {Port, RH} <- ListenTo], d("do_start -> Transports: ~n~p",[Transports]), {ok, Transports}. start_transport(MgcPort, RecHandle) -> case RecHandle#megaco_receive_handle.send_mod of megaco_tcp -> start_tcp(MgcPort, RecHandle); megaco_udp -> start_udp(MgcPort, RecHandle); SendMod -> {error, {bad_send_mod, SendMod}} end. start_udp(MgcPort, RecHandle) -> d("start_udp -> entry with" "~n MgcPort: ~p" "~n RecHandle: ~p", [MgcPort, RecHandle]), case megaco_udp:start_transport() of {ok, SupPid} -> Options = [{port, MgcPort}, {receive_handle, RecHandle}], case megaco_udp:open(SupPid, Options) of {ok, _SendHandle, _ControlPid} -> ok; {error, Reason} -> {error, {megaco_udp_open, Reason}} end; {error, Reason} -> {error, {megaco_udp_start_transport, Reason}} end. start_tcp(MgcPort, RecHandle) -> d("start_tcp -> entry with" "~n MgcPort: ~p" "~n RecHandle: ~p", [MgcPort, RecHandle]), case megaco_tcp:start_transport() of {ok, SupPid} -> d("start_tcp -> transport started: " "~n SupPid: ~p", [SupPid]), Options = [{port, MgcPort}, {receive_handle, RecHandle}], case megaco_tcp:listen(SupPid, Options) of ok -> d("start_tcp -> listen ok"), ok; {error, Reason} -> d("start_tcp -> listen failed: " "~n Reason: ~p", [Reason]), {error, {megaco_tcp_listen, Reason}} end; {error, Reason} -> d("start_tcp -> transport start failed: " "~n Reason: ~p", [Reason]), {error, {megaco_tcp_start_transport, Reason}} end. %%---------------------------------------------------------------------- %% Stopping the MGC %%---------------------------------------------------------------------- stop() -> [{Mid, stop(Mid)} || Mid <- megaco:system_info(users)]. stop(Mid) -> d("stop -> entry with~n Mid: ~p", [Mid]), Disco = fun(CH) -> d("stop -> CH: ~p", [CH]), Reason = stopped_by_user, Pid = megaco:conn_info(CH, control_pid), SendMod = megaco:conn_info(CH, send_mod), SendHandle = megaco:conn_info(CH, send_handle), d("stop -> disconnect", []), megaco:disconnect(CH, Reason), d("stop -> cancel", []), megaco:cancel(CH, Reason), % see handle_disconnect d("stop -> close transport" "~n SendMod: ~p" "~n SendHandle: ~p", [SendMod, SendHandle]), case SendMod of megaco_tcp -> megaco_tcp:close(SendHandle); megaco_udp -> megaco_udp:close(SendHandle); SendMod -> exit(Pid, Reason) end end, Conns = megaco:user_info(Mid, connections), d("stop -> Conns: ~p", [Conns]), Disconns = lists:map(Disco, Conns), d("stop -> Disconns: ~p", [Disconns]), megaco:stop_user(Mid), case whereis(?MODULE) of undefined -> ignore; Pid -> d("stop -> Pid: ~p", [Pid]), unlink(Pid), exit(Pid, shutdown) end, ok. %%---------------------------------------------------------------------- %% Invoked when a new connection is established %%---------------------------------------------------------------------- handle_connect(ConnHandle, ProtocolVersion) -> d("handle_connect -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "", [ConnHandle, ProtocolVersion]), ok. %%---------------------------------------------------------------------- %% Invoked when a connection is teared down %%---------------------------------------------------------------------- handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> d("handle_disconnect -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n Reason: ~p" "", [ConnHandle, ProtocolVersion, Reason]), info_msg("handle_disconnect - cancel outstanding messages~n"), megaco:cancel(ConnHandle, Reason), % Cancel the outstanding messages ok. %%---------------------------------------------------------------------- %% Invoked when a received message had syntax errors %%---------------------------------------------------------------------- handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor) -> d("handle_syntax_error -> entry with" "~n ReceiveHandle: ~p" "~n ProtocolVersion: ~p" "~n ErrorDescriptor: ~p" "", [ReceiveHandle, ProtocolVersion, ErrorDescriptor]), reply. %%---------------------------------------------------------------------- %% Invoked when a received message contained no transactions %%---------------------------------------------------------------------- handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor) -> d("handle_message_error -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n ErrorDescriptor: ~p" "", [ConnHandle, ProtocolVersion, ErrorDescriptor]), no_reply. %%---------------------------------------------------------------------- %% Invoked for each transaction request %%---------------------------------------------------------------------- handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> d("handle_trans_request -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n ActionRequests: ~p" "", [ConnHandle, ProtocolVersion, ActionRequests]), ED = #'ErrorDescriptor'{errorCode = ?megaco_not_implemented, errorText = "Only single service change on null context handled"}, case ActionRequests of [AR] -> ContextId = AR#'ActionRequest'.contextId, case AR#'ActionRequest'.commandRequests of [CR] when ContextId =:= ?megaco_null_context_id -> case CR#'CommandRequest'.command of {serviceChangeReq, Req} -> Rep = service_change(ConnHandle, ProtocolVersion, Req), {discard_ack, [#'ActionReply'{contextId = ContextId, commandReply = [{serviceChangeReply, Rep}]}]}; _ -> {discard_ack, ED} end; _ -> {discard_ack, ED} end; _ -> {discard_ack, ED} end. service_change(ConnHandle, _ProtocolVersion, SCR) -> SCP = SCR#'ServiceChangeRequest'.serviceChangeParms, #'ServiceChangeParm'{serviceChangeAddress = Address, serviceChangeProfile = Profile} = SCP, TermId = SCR#'ServiceChangeRequest'.terminationID, if TermId == [?megaco_root_termination_id] -> MyMid = ConnHandle#megaco_conn_handle.local_mid, Res = {serviceChangeResParms, #'ServiceChangeResParm'{serviceChangeMgcId = MyMid, serviceChangeAddress = Address, serviceChangeProfile = Profile}}, #'ServiceChangeReply'{terminationID = TermId, serviceChangeResult = Res}; true -> Res = {errorDescriptor, #'ErrorDescriptor'{errorCode = ?megaco_not_implemented, errorText = "Only handled for root"}}, #'ServiceChangeReply'{terminationID = TermId, serviceChangeResult = Res} end. %%---------------------------------------------------------------------- %% Optionally invoked for a time consuming transaction request %%---------------------------------------------------------------------- handle_trans_long_request(_ConnHandle, _ProtocolVersion, _ReqData) -> ED = #'ErrorDescriptor'{errorCode = ?megaco_not_implemented, errorText = "Long transaction requests not handled"}, {discard_ack, ED}. %%---------------------------------------------------------------------- %% Optionally invoked for a transaction reply %%---------------------------------------------------------------------- handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData) -> d("handle_trans_eply -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n ActualReply: ~p" "~n ReplyData: ~p" "", [ConnHandle, ProtocolVersion, ActualReply, ReplyData]), ok. %%---------------------------------------------------------------------- %% Optionally invoked for a transaction acknowledgement %%---------------------------------------------------------------------- handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> d("handle_trans_ack -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n ckStatus: ~p" "~n AckData: ~p" "", [ConnHandle, ProtocolVersion, AckStatus, AckData]), ok. %%---------------------------------------------------------------------- %% Invoked when an unexpected message has been received %%---------------------------------------------------------------------- handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> d("handle_unexpected_trans -> entry with" "~n ConnHandle: ~p" "~n ProtocolVersion: ~p" "~n Trans: ~p" "", [ConnHandle, ProtocolVersion, Trans]), ok. %%---------------------------------------------------------------------- %% Invoked when an unexpected message has been received %%---------------------------------------------------------------------- handle_trans_request_abort(_ConnHandle, _ProtocolVersion, _TransId, _Pid) -> ok. %%---------------------------------------------------------------------- %% To be used at command line: erl -s ?MODULE start_batch %%---------------------------------------------------------------------- start_batch() -> start_batch([false]). start_batch(Args0) -> Defs = [{trace,false}, {debug, false}], Args = parse_args(Args0, Defs), Trace = get_arg(trace, Args), Debug = get_arg(debug, Args), Pid = spawn(?MODULE, init_batch, [self(), Trace, Debug]), receive {init_batch, Pid, Res} -> io:format("~p(~p): ~p~n", [?MODULE, ?LINE, Res]), Res end. init_batch(ReplyTo, Trace, Debug) -> register(?MODULE, self()), Res = start(Trace, Debug), ReplyTo ! {init_batch, self(), Res}, receive after infinity -> Res end. parse_args([], Acc) -> Acc; parse_args([Arg|Args], Acc) when is_atom(Arg) -> case string:tokens(atom_to_list(Arg),"{},") of ["trace",Trace] -> parse_args(Args, parse_args(trace, list_to_atom(Trace), Acc)); ["debug",Debug] -> parse_args(Args, parse_args(debug, list_to_atom(Debug), Acc)); _Invalid -> parse_args(Args, Acc) end. parse_args(Key, Val, Args) -> Entry = {Key, Val}, case lists:keyreplace(Key, 1, Args, {Key, Val}) of Args -> [Entry|Args]; Args2 -> Args2 end. get_arg(Key, Args) -> {value, {Key, Val}} = lists:keysearch(Key, 1, Args), Val. %%---------------------------------------------------------------------- %% DEBUGGING %%---------------------------------------------------------------------- info_msg(F) -> info_msg(F, []). info_msg(F, A) -> io:format("~p MGC: " ++ F ++ "~n", [self()|A]). d(F) -> d(F, []). d(F,A) -> d(get(debug),F,A). d(true,F,A) -> io:format("SIMPLE_MGC: " ++ F ++ "~n", A); d(_, _F, _A) -> ok.