%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2007-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: megaco_tcp sequence generator for the megaco test suite
%%----------------------------------------------------------------------
-module(megaco_test_tcp_generator).
-behaviour(megaco_test_generator).
-compile({no_auto_import,[error/1]}).
%% API
-export([
start_link/1, start_link/2,
stop/1,
exec/2, exec/3
]).
%% genarator behaviour callback exports
-export([
init/1,
handle_parse/2,
handle_exec/2,
terminate/2
]).
-record(state,
{
listen, % Listen socket
connection, % Connection socket
encode, % Encode fun
decode, % Decode fun
result = [] % Accumulated results from exec
}).
%%----------------------------------------------------------------------
%% API
%%----------------------------------------------------------------------
start_link(Name) ->
megaco_test_generator:start_link(?MODULE, [], Name).
start_link(Name, Node) ->
megaco_test_generator:start_link(?MODULE, [], Name, Node).
stop(Server) ->
megaco_test_generator:stop(Server).
exec(Server, Instructions) when is_list(Instructions) ->
megaco_test_generator:exec(Server, Instructions).
exec(Server, Instructions, Timeout) when is_list(Instructions) ->
megaco_test_generator:exec(Server, Instructions, Timeout).
%%----------------------------------------------------------------------
%% generator callback functions
%%----------------------------------------------------------------------
init([]) ->
{ok, #state{}}.
%% ----- instruction parser -----
handle_parse({debug, Debug} = Instruction, State)
when (Debug == true) orelse (Debug == false) ->
{ok, Instruction, State};
handle_parse({decode, Decode} = Instruction, State)
when is_function(Decode) ->
{ok, Instruction, State};
handle_parse({encode, Encode} = Instruction, State)
when is_function(Encode) ->
{ok, Instruction, State};
handle_parse(disconnect = Instruction, State) ->
{ok, Instruction, State};
handle_parse({listen, Port} = Instruction, State)
when is_integer(Port) andalso (Port >= 0) ->
{ok, Instruction, State};
handle_parse({expect_accept, any} = _Instruction, State) ->
{ok, {expect_accept, {any, infinity}}, State};
handle_parse({expect_accept, {any, To}} = Instruction, State)
when (is_integer(To) andalso (To >= 0)) orelse (To == infinity) ->
{ok, Instruction, State};
handle_parse({expect_accept, {Host, To}} = _Instruction, State)
when (is_integer(To) andalso (To >= 0)) orelse (To == infinity) ->
case inet:getaddr(Host, inet) of
{ok, Addr} ->
Instruction = {expect_accept, {Addr, To}},
{ok, Instruction, State};
{error, Reason} ->
{error, {bad_host, Host, Reason}}
end;
handle_parse({expect_accept, Host} = _Instruction, State) ->
case inet:getaddr(Host, inet) of
{ok, Addr} ->
Instruction = {expect_accept, {Addr, infinity}},
{ok, Instruction, State};
{error, Reason} ->
{error, {bad_host, Host, Reason}}
end;
handle_parse({active, NewState} = Instruction, State)
when (NewState == true) orelse
(NewState == false) orelse
(NewState == once) ->
{ok, Instruction, State};
handle_parse({connect, Port} = _Instruction, State)
when is_integer(Port) andalso (Port >= 0) ->
Host =
case inet:gethostname() of
{ok, Hostname} ->
Hostname;
{error, Reason1} ->
error({failed_geting_own_hostname, Reason1})
end,
case inet:getaddr(Host, inet) of
{ok, Address} ->
Instruction = {connect, {Address, Port, infinity}},
{ok, Instruction, State};
{error, Reason2} ->
{error, {bad_host, Host, Reason2}}
end;
handle_parse({connect, {Port, To}} = _Instruction, State)
when (is_integer(Port) andalso
(Port >= 0)) andalso ((is_integer(To) andalso (To >= 0)) orelse
(To == infinity)) ->
Host =
case inet:gethostname() of
{ok, Hostname} ->
Hostname;
{error, Reason1} ->
error({failed_geting_own_hostname, Reason1})
end,
case inet:getaddr(Host, inet) of
{ok, Address} ->
Instruction = {connect, {Address, Port, To}},
{ok, Instruction, State};
{error, Reason2} ->
{error, {bad_host, Host, Reason2}}
end;
handle_parse({connect, {Host, Port}} = _Instruction, State)
when (is_integer(Port) andalso (Port >= 0)) ->
case inet:getaddr(Host, inet) of
{ok, Address} ->
Instruction = {connect, {Address, Port, infinity}},
{ok, Instruction, State};
{error, Reason} ->
{error, {bad_host, Host, Reason}}
end;
handle_parse({connect, {Host, Port, To}} = _Instruction, State)
when (is_integer(Port) andalso
(Port >= 0)) andalso ((is_integer(To) andalso (To >= 0)) orelse
(To == infinity)) ->
case inet:getaddr(Host, inet) of
{ok, Address} ->
Instruction = {connect, {Address, Port, To}},
{ok, Instruction, State};
{error, Reason} ->
{error, {bad_host, Host, Reason}}
end;
handle_parse({sleep, To} = Instruction, State)
when is_integer(To) andalso (To > 0) ->
{ok, Instruction, State};
handle_parse({expect_nothing, To} = Instruction, State)
when is_integer(To) andalso (To > 0) ->
{ok, Instruction, State};
handle_parse({expect_closed, To} = Instruction, State)
when is_integer(To) andalso (To > 0) ->
{ok, Instruction, State};
handle_parse({expect_receive, Desc, Verify} = _Instruction, State)
when is_list(Desc) andalso is_function(Verify) ->
Instruction = {expect_receive, Desc, {Verify, infinity}},
{ok, Instruction, State};
handle_parse({expect_receive, Desc, {Verify, To}} = Instruction, State)
when is_list(Desc) andalso
is_function(Verify) andalso
((is_integer(To) andalso (To >= 0)) orelse (To == infinity)) ->
{ok, Instruction, State};
handle_parse({send, Desc, Msg} = Instruction, State)
when is_list(Desc) andalso (is_tuple(Msg) orelse is_binary(Msg)) ->
{ok, Instruction, State};
handle_parse({trigger, Desc, Trigger} = Instruction, State)
when is_list(Desc) andalso is_function(Trigger) ->
{ok, Instruction, State};
handle_parse(Instruction, _State) ->
{error, {unknown_instruction, Instruction}}.
%% ----- instruction exececutor -----
handle_exec({debug, Debug},
State) ->
p("debug: ~p", [Debug]),
put(debug, Debug),
{ok, State};
handle_exec({encode, Encode},
State) ->
p("encode: ~p", [Encode]),
{ok, State#state{encode = Encode}};
handle_exec({decode, Decode},
State) ->
p("Decode: ~p", [Decode]),
{ok, State#state{decode = Decode}};
handle_exec(disconnect,
#state{listen = Listen,
connection = Sock,
result = Res} = State) ->
p("disconnect"),
(catch gen_tcp:close(Sock)),
(catch gen_tcp:close(Listen)),
{ok, State#state{listen = undefined,
connection = undefined,
result = [disconnected|Res]}};
handle_exec({listen, Port}, #state{result = Res} = State) ->
p("listen to ~p", [Port]),
Opts = [binary,
{packet, tpkt},
{active, false},
{reuseaddr, true},
{nodelay, true}],
case (catch gen_tcp:listen(Port, Opts)) of
{ok, Listen} ->
d("listen -> listen socket created"),
{ok, State#state{listen = Listen, result = [listening | Res]}};
{error, Reason} ->
e("failed creating listen socket: ~p", [Reason]),
{error, {failed_creating_listen_socket, Reason, Res}}
end;
handle_exec({expect_accept, {Addr, To}},
#state{listen = Listen,
result = Res} = State) ->
p("expect_accept from ~p (~p)", [Addr, To]),
case (catch gen_tcp:accept(Listen, To)) of
{ok, Sock} ->
d("expect_accept -> connection accepted"),
case (catch inet:peername(Sock)) of
{ok, {Addr, _Port}} ->
d("expect_accept -> valid address"),
NewState =
State#state{connection = Sock,
result = [{connected, Addr}|Res]},
{ok, NewState};
{ok, {OtherAddr, _Port}} when Addr == any ->
d("expect_accept -> valid (~p)", [OtherAddr]),
NewState =
State#state{connection = Sock,
result = [{connected, OtherAddr}|Res]},
{ok, NewState};
{ok, AddrAndPort} ->
{error, {invalid_connect, AddrAndPort, Res}};
{error, Reason} ->
e("failed getting peername for socket: ~p", [Reason]),
(catch gen_tcp:close(Sock)),
{error, {failed_getting_peername, Sock, Reason}}
end;
{error, Reason} ->
e("failed accepting connection: ~p", [Reason]),
(catch gen_tcp:close(Listen)),
{error, {failed_accepting_conection, Reason, Listen}}
end;
handle_exec({active, NewState},
#state{connection = Sock,
result = Res} = State) ->
p("active to ~p", [NewState]),
case inet:setopts(Sock, [{active, NewState}]) of
ok ->
d("active -> state changed"),
{ok, State#state{result = [{active, NewState}|Res]}};
{error, Reason} ->
e("failed changing active state to ~w: ~p", [NewState, Reason]),
{error, {failed_setting_active, Reason}}
end;
handle_exec({connect, {Addr, Port, To}},
#state{result = Res} = State) ->
p("connect to ~p, ~p", [Addr, Port]),
Opts = [binary, {packet, tpkt}, {active, once}, {nodelay, true}],
case (catch gen_tcp:connect(Addr, Port, Opts, To)) of
{ok, Sock} ->
d("connect -> connected"),
{ok, State#state{connection = Sock,
result = [{connected, Addr, Port}|Res]}};
{error, Reason} ->
e("failed connecting: ~p", [Reason]),
{error, {failed_connect, Addr, Port, Reason}}
end;
%% Already encoded
handle_exec({send, Desc, Bin},
#state{connection = Sock,
result = Res} = State)
when is_binary(Bin) ->
p("send ~s message", [Desc]),
NewBin = add_tpkt_header(Bin),
d("send -> tpkt header added [~w], now send", [sz(NewBin)]),
case (catch gen_tcp:send(Sock, NewBin)) of
ok ->
d("send -> message sent"),
{ok, State#state{result = [{sent, Desc}|Res]}};
{error, Reason} ->
e("send -> send failed: ~n~p",[Reason]),
{error, {failed_send, Reason}}
end;
handle_exec({send, Desc, Msg},
#state{connection = Sock,
encode = Encode,
result = Res} = State) ->
p("send ~s message", [Desc]),
case (catch Encode(Msg)) of
{ok, Bin} ->
d("send -> message encoded [~w], now add tpkt header: ~n~s",
[sz(Bin), binary_to_list(Bin)]),
NewBin = add_tpkt_header(Bin),
d("send -> tpkt header added [~w], now send", [sz(NewBin)]),
case (catch gen_tcp:send(Sock, NewBin)) of
ok ->
d("send -> message sent"),
{ok, State#state{result = [{sent, Desc}|Res]}};
{error, Reason} ->
e("send -> send failed: ~n~p", [Reason]),
{error, {failed_send, Reason}}
end;
Error ->
e("send -> encode failed: ~n~p", [Error]),
{error, {encode_failed, Error}}
end;
handle_exec({expect_receive, Desc, {Verify, To}},
#state{connection = Sock,
decode = Decode,
result = Acc} = State) ->
p("expect_receive ~s message", [Desc]),
inet:setopts(Sock, [{active, once}]),
receive
{tcp, Sock, <<3:8, _X:8, Length:16, Msg/binary>>} ->
d("expect_receive -> received message: Length = ~p", [Length]),
case (catch Decode(Msg)) of
{ok, MegaMsg} when is_tuple(MegaMsg) ->
d("expect_receive -> decode successfull, now verify"),
case (catch Verify(MegaMsg)) of
{ok, Res} ->
d("expect_receive -> verify successfull"),
{ok, State#state{result = [Res|Acc]}};
Else ->
e("failed to verify message: ~n~p~n~p",
[Else, MegaMsg]),
{error, {expect_receive, {verify_failed, Else}}}
end;
Error ->
e("failed decoding message: ~p", [Error]),
{error, {expect_receive, Error}}
end;
Else ->
e("received unknown message: ~p", [Else]),
{error, {expect_receive, {unexpected_message, Else}}}
after To ->
{error, {expect_receive, timeout}}
end;
handle_exec({expect_closed, To},
#state{connection = Sock,
result = Acc} = State) ->
p("expect_closed ~w", [To]),
inet:setopts(Sock, [{active, once}]),
p("expect_closed - await closed", []),
receive
{tcp_closed, Sock} ->
p("expect_closed - received closed"),
{ok, State#state{connection = undefined,
result = [closed|Acc]}}
after To ->
e("expect_closed timeout after ~w", [To]),
{error, {expect_closed, timeout}}
end;
handle_exec({expect_nothing, To},
#state{connection = Sock,
result = Acc} = State) ->
p("expect_nothing ~w", [To]),
inet:setopts(Sock, [{active, once}]),
p("expect_nothing - await anything", []),
receive
Any ->
e("expect_nothing - received: ~p", [Any]),
{error, {expect_nothing, Any}}
after To ->
p("expect_nothing timeout after ~w", [To]),
{ok, State#state{result = [{nothing, To}|Acc]}}
end;
handle_exec({trigger, Desc, Trigger},
#state{result = Acc} = State) when is_function(Trigger) ->
p("trigger: ~s", [Desc]),
Trigger(),
{ok, State#state{result = [triggered|Acc]}};
handle_exec({sleep, To},
#state{result = Acc} = State) ->
p("sleep ~p", [To]),
sleep(To),
{ok, State#state{result = [{slept, To}|Acc]}};
handle_exec(Instruction, _State) ->
{error, {unknown_instruction, Instruction}}.
%% ----- terminate -----
terminate(normal, #state{listen = Listen,
connection = Sock,
result = Result}) ->
(catch gen_tcp:close(Sock)),
(catch gen_tcp:close(Listen)),
{ok, Result};
terminate(Reason, #state{listen = Listen,
connection = Sock,
result = Result}) ->
(catch gen_tcp:close(Sock)),
(catch gen_tcp:close(Listen)),
{error, {Reason, Result}}.
%%----------------------------------------------------------------------
%% internal utility functions
%%----------------------------------------------------------------------
error(Reason) ->
throw({error, Reason}).
%%% ----------------------------------------------------------------
add_tpkt_header(Bin) when is_binary(Bin) ->
L = size(Bin) + 4,
SZ1 = ((L) bsr 8) band 16#ff,
SZ2 = (L) band 16#ff,
<<3, 0, SZ1, SZ2, Bin/binary>>;
add_tpkt_header(IOList) when is_list(IOList) ->
add_tpkt_header(list_to_binary(IOList)).
sleep(X) -> megaco_test_generator:sleep(X).
sz(X) -> megaco_test_generator:sz(X).
%%% ----------------------------------------------------------------
d(F) -> megaco_test_generator:debug(F).
d(F, A) -> megaco_test_generator:debug(F, A).
e(F, A) -> megaco_test_generator:error(F, A).
p(F ) -> p("", F, []).
p(F, A) -> p("", F, A).
p(P, F, A) -> megaco_test_generator:print(P, F, A).