%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2001-2016. 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 MG
%%
%% Example usage:
%%
%% cd megaco/examples/simple
%% erl -pa ../../../megaco/ebin -s megaco_filter -s megaco
%% megaco_simple_mg:start().
%%----------------------------------------------------------------------
-module(megaco_simple_mg).
-behaviour(megaco_user).
-export([
start_batch/0, start_batch/1, init_batch/4,
start/0, start/3,
start/4, %% ????????????????????????
stop/0, stop/1,
start_tcp_text/2, start_tcp_binary/2,
start_udp_text/2, start_udp_binary/2
]).
-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").
%%----------------------------------------------------------------------
%% To be used at command line: erl -s ?MODULE start_batch
%%----------------------------------------------------------------------
start_batch() ->
start_batch([]).
start_batch(Args0) when is_list(Args0) ->
{ok, LocalHost} = inet:gethostname(),
Defs = [{mgc_host, LocalHost}, {trace,false}, {debug, false}],
Args = parse_args(Args0, Defs),
MgcHost = get_arg(mgc_host, Args),
Trace = get_arg(trace, Args),
Debug = get_arg(debug, Args),
Pid = spawn(?MODULE, init_batch, [self(), MgcHost, Trace, Debug]),
receive
{init_batch, Pid, Res} ->
io:format("~p(~p): ~p~n", [?MODULE, ?LINE, Res]),
Res
end.
parse_args([], Acc) ->
Acc;
parse_args([Arg|Args], Acc) when is_atom(Arg) ->
case string:tokens(atom_to_list(Arg),"{},") of
["mgc_host", Host] when is_list(Host) ->
parse_args(Args, parse_args(mgc_host, Host, Acc));
["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.
init_batch(ReplyTo, MgcHost, Trace, Debug) ->
register(?MODULE, self()),
Res = start(MgcHost, Trace, Debug),
ReplyTo ! {init_batch, self(), Res},
receive
after infinity -> Res
end.
%%----------------------------------------------------------------------
%% Starting the MG
%%----------------------------------------------------------------------
%% -----------------------------------------------------------------------
init_inline_trace(true) ->
megaco:enable_trace(max, io);
init_inline_trace(_) ->
ok.
%% -----------------------------------------------------------------------
start() ->
{ok, LocalHost} = inet:gethostname(),
start(LocalHost, false, false).
%% Used when calling from the erlang shell:
start(MgcHost, Trace, Debug)
when is_atom(MgcHost) andalso is_atom(Trace) andalso is_atom(Debug) ->
start(atom_to_list(MgcHost), Trace, Debug);
start(MgcHost, Trace, Debug)
when is_list(MgcHost) andalso is_atom(Trace) andalso is_atom(Debug) ->
put(debug, Debug),
d("start -> entry with"
"~n MgcHost: ~s"
"~n Trace: ~p", [MgcHost, Trace]),
init_inline_trace(Trace),
Starters = [fun start_tcp_text/2,
fun start_tcp_binary/2,
fun start_udp_text/2,
fun start_udp_binary/2],
[Fun(MgcHost, []) || Fun <- Starters].
start_tcp_text(MgcHost, Default) ->
d("start_tcp_text -> entry with"
"~n MgcHost: ~p", [MgcHost]),
Config = [{encoding_mod, megaco_pretty_text_encoder},
{encoding_config, []},
{send_mod, megaco_tcp} | Default],
Mid = {deviceName, "gateway_tt"},
{Mid, start(MgcHost, ?megaco_ip_port_text, Mid, Config)}.
start_tcp_binary(MgcHost, Default) ->
d("start_tcp_binary -> entry with"
"~n MgcHost: ~p", [MgcHost]),
Config = [{encoding_mod, megaco_binary_encoder},
{encoding_config, []},
{send_mod, megaco_tcp} | Default],
Mid = {deviceName, "gateway_tb"},
{Mid, start(MgcHost, ?megaco_ip_port_binary, Mid, Config)}.
start_udp_text(MgcHost, Default) ->
d("start_udp_text -> entry with"
"~n MgcHost: ~p", [MgcHost]),
Config = [{encoding_mod, megaco_pretty_text_encoder},
{encoding_config, []},
{send_mod, megaco_udp} | Default],
Mid = {deviceName, "gateway_ut"},
{Mid, start(MgcHost, ?megaco_ip_port_text, Mid, Config)}.
start_udp_binary(MgcHost, Default) ->
d("start_udp_binary -> entry with"
"~n MgcHost: ~p", [MgcHost]),
Config = [{encoding_mod, megaco_binary_encoder},
{encoding_config, []},
{send_mod, megaco_udp} | Default],
Mid = {deviceName, "gateway_ub"},
{Mid, start(MgcHost, ?megaco_ip_port_binary, Mid, Config)}.
start(MgcHost, MgcPort, Mid, Config) ->
case megaco:start_user(Mid, [{user_mod, ?MODULE} | Config]) of
ok ->
case start_transport(MgcHost, MgcPort, Mid) of
{ok, ConnHandle} ->
service_change(ConnHandle);
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, {start_user, Reason}}
end.
start_transport(MgcHost, MgcPort, Mid) ->
RecHandle = megaco:user_info(Mid, receive_handle),
case RecHandle#megaco_receive_handle.send_mod of
megaco_tcp -> start_tcp(MgcHost, MgcPort, RecHandle);
megaco_udp -> start_udp(MgcHost, MgcPort, RecHandle);
SendMod -> {error, {bad_send_mod, SendMod}}
end.
start_tcp(MgcHost, MgcPort, RecHandle) ->
d("start_tcp -> start transport"),
case megaco_tcp:start_transport() of
{ok, Pid} ->
d("start_tcp -> transport started: ~p", [Pid]),
Options = [{host, MgcHost},
{port, MgcPort},
{receive_handle, RecHandle}],
case megaco_tcp:connect(Pid, Options) of
{ok, SendHandle, ControlPid} ->
d("start_tcp -> connected: ~p", [ControlPid]),
MgcMid = preliminary_mid,
megaco:connect(RecHandle, MgcMid, SendHandle, ControlPid);
{error, Reason} ->
d("start_tcp -> connection failed: ~p", [Reason]),
{error, {megaco_tcp_connect, Reason}}
end;
{error, Reason} ->
d("start_tcp -> failed starting transport: ~p", [Reason]),
{error, {megaco_tcp_start_transport, Reason}}
end.
start_udp(MgcHost, MgcPort, RecHandle) ->
d("start_udp -> start transport"),
case megaco_udp:start_transport() of
{ok, SupPid} ->
d("start_udp -> transport started: ~p", [SupPid]),
Options = [{port, 0}, {receive_handle, RecHandle}],
case megaco_udp:open(SupPid, Options) of
{ok, Handle, ControlPid} ->
d("start_udp -> port opened: ~p", [ControlPid]),
%% Socket = megaco_udp:socket(Handle),
%% MgPort = inet:port(Socket), BUGBUG BUGBUG
MgcMid = preliminary_mid,
SendHandle = megaco_udp:create_send_handle(Handle,
MgcHost, % BUGBUG BUGBUG
MgcPort),
megaco:connect(RecHandle, MgcMid, SendHandle, ControlPid);
{error, Reason} ->
d("start_udp -> failed open port: ~p", [Reason]),
{error, {megaco_udp_open, Reason}}
end;
{error, Reason} ->
d("start_udp -> failed starting transport: ~p", [Reason]),
{error, {megaco_udp_start_transport, Reason}}
end.
service_change(ConnHandle) ->
service_change(ConnHandle, restart, ?megaco_cold_boot).
service_change(ConnHandle, Method, Reason) ->
SCP = #'ServiceChangeParm'{serviceChangeMethod = Method,
serviceChangeReason = [Reason]},
TermId = [?megaco_root_termination_id],
SCR = #'ServiceChangeRequest'{terminationID = TermId,
serviceChangeParms = SCP},
CR = #'CommandRequest'{command = {serviceChangeReq, SCR}},
AR = #'ActionRequest'{contextId = ?megaco_null_context_id,
commandRequests = [CR]},
megaco:call(ConnHandle, [AR], []).
%%----------------------------------------------------------------------
%% Stopping the MG
%%----------------------------------------------------------------------
stop() ->
[{Mid, stop(Mid)} || Mid <- megaco:system_info(users)].
stop(Mid) ->
Reason = stopped_by_user,
Disco = fun(CH) ->
Pid = megaco:conn_info(CH, control_pid),
megaco:disconnect(CH, Reason),
megaco:cancel(CH, Reason),
exit(Pid, Reason)
end,
lists:map(Disco, megaco:user_info(Mid, connections)),
megaco:stop_user(Mid).
%%----------------------------------------------------------------------
%% 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]),
megaco:cancel(ConnHandle, Reason), % Cancel the outstanding messages
d("handle_disconnect -> done", []),
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 = "Transaction requests not handled"},
{discard_ack, ED}.
%%----------------------------------------------------------------------
%% Optionally invoked for a time consuming transaction request
%%----------------------------------------------------------------------
handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) ->
d("handle_trans_long_request -> entry with"
"~n ConnHandle: ~p"
"~n ProtocolVersion: ~p"
"~n ReqData: ~p", [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_reply -> 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 AckStatus: ~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 AckStatus: ~p"
"~n AckData: ~p",
[ConnHandle, ProtocolVersion, Trans]),
ok.
%%----------------------------------------------------------------------
%% Invoked when an unexpected message has been received
%%----------------------------------------------------------------------
handle_trans_request_abort(ConnHandle, ProtocolVersion, TransId, Pid) ->
d("handle_trans_request_abort -> entry with"
"~n ConnHandle: ~p"
"~n ProtocolVersion: ~p"
"~n TransId: ~p"
"~n Pid: ~p",
[ConnHandle, ProtocolVersion, TransId, Pid]),
ok.
%%----------------------------------------------------------------------
%% DEBUGGING
%%----------------------------------------------------------------------
d(F) ->
d(F, []).
d(F,A) ->
d(get(debug),F,A).
d(true,F,A) ->
io:format("SIMPLE_MG: " ++ F ++ "~n", A);
d(_, _F, _A) ->
ok.