%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(tftp_SUITE).
-compile(export_all).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Includes and defines
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-include("tftp_test_lib.hrl").
-define(START_DAEMON(PortX, OptionsX),
fun(Port, Options) ->
{ok, Pid} = ?VERIFY({ok, _Pid}, tftp:start([{port, Port} | Options])),
if
Port == 0 ->
{ok, ActualOptions} = ?IGNORE(tftp:info(Pid)),
{value, {port, ActualPort}} =
lists:keysearch(port, 1, ActualOptions),
{ActualPort, Pid};
true ->
{Port, Pid}
end
end(PortX, OptionsX)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
t() ->
tftp_test_lib:t([{?MODULE, all}]).
t(Cases) ->
tftp_test_lib:t(Cases, default_config()).
t(Cases, Config) ->
tftp_test_lib:t(Cases, Config).
default_config() ->
[].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test server callbacks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_testcase(Case, Config) ->
tftp_test_lib:init_per_testcase(Case, Config).
end_per_testcase(Case, Config) when is_list(Config) ->
tftp_test_lib:end_per_testcase(Case, Config).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Top test case
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
[simple, extra, reuse_connection, resend_client,
resend_server].
groups() ->
[].
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Simple
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
simple(doc) ->
["Start the daemon and perform simple a read and write."];
simple(suite) ->
[];
simple(Config) when is_list(Config) ->
?VERIFY(ok, application:start(inets)),
{Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])),
%% Read fail
RemoteFilename = "tftp_temporary_remote_test_file.txt",
LocalFilename = "tftp_temporary_local_test_file.txt",
Blob = list_to_binary(lists:duplicate(2000, $1)),
%% Blob = <<"Some file contents\n">>,
Size = size(Blob),
?IGNORE(file:delete(RemoteFilename)),
?VERIFY({error, {client_open, enoent, _}},
tftp:read_file(RemoteFilename, binary, [{port, Port}])),
%% Write and read
?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, [{port, Port}])),
?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, [{port, Port}])),
?IGNORE(file:delete(LocalFilename)),
?VERIFY({ok, Size}, tftp:read_file(RemoteFilename, LocalFilename, [{port, Port}])),
%% Cleanup
unlink(DaemonPid),
exit(DaemonPid, kill),
?VERIFY(ok, file:delete(LocalFilename)),
?VERIFY(ok, file:delete(RemoteFilename)),
?VERIFY(ok, application:stop(inets)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Extra
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
extra(doc) ->
["Verify new stuff for IS 1.2."];
extra(suite) ->
[];
extra(Config) when is_list(Config) ->
?VERIFY({'EXIT', {badarg,{fake_key, fake_flag}}},
tftp:start([{port, 0}, {fake_key, fake_flag}])),
{Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])),
RemoteFilename = "tftp_extra_temporary_remote_test_file.txt",
LocalFilename = "tftp_extra_temporary_local_test_file.txt",
Blob = <<"Some file contents\n">>,
Size = size(Blob),
Host = "127.0.0.1",
Peer = {inet, Host, Port},
Generic =
[
{state, []},
{prepare, fun extra_prepare/6},
{open, fun extra_open/6},
{read, fun extra_read/1},
{write, fun extra_write/2},
{abort, fun extra_abort/3 }
],
Options = [{host, Host},
{port, Port},
%%{ debug,all},
{callback, {".*", tftp_test_lib, Generic}}],
?VERIFY(ok, file:write_file(LocalFilename, Blob)),
?VERIFY({ok, [{count, Size}, Peer]},
tftp:write_file(RemoteFilename, LocalFilename, Options)),
?VERIFY(ok, file:delete(LocalFilename)),
?VERIFY({ok,[{bin, Blob}, Peer]},
tftp:read_file(RemoteFilename, LocalFilename, Options)),
%% Cleanup
unlink(DaemonPid),
exit(DaemonPid, kill),
?VERIFY(ok, file:delete(LocalFilename)),
?VERIFY(ok, file:delete(RemoteFilename)),
ok.
-record(extra_state, {file, blksize, count, acc, peer}).
%%-------------------------------------------------------------------
%% Prepare
%%-------------------------------------------------------------------
extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) ->
%% Client side
BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", SuggestedOptions)),
State = #extra_state{blksize = BlkSize, peer = Peer},
extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State),
{ok, SuggestedOptions, State};
extra_prepare(_Peer, _Access, _Bin, _Mode, _SuggestedOptions, _Initial) ->
{error, {undef, "Illegal callback options."}}.
%%-------------------------------------------------------------------
%% Open
%%-------------------------------------------------------------------
extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) ->
%% Server side
case extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) of
{ok, AcceptedOptions, []} ->
BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", AcceptedOptions)),
State = #extra_state{blksize = BlkSize, peer = Peer},
extra_open(Peer, Access, LocalFilename, Mode, AcceptedOptions, State);
{error, {Code, Text}} ->
{error, {Code, Text}}
end;
extra_open(_Peer, Access, LocalFilename, _Mode, NegotiatedOptions, #extra_state{} = State) ->
{File, Acc} =
case Access of
read ->
if
is_binary(LocalFilename) ->
{undefined, LocalFilename};
is_list(LocalFilename) ->
{ok, Bin} = file:read_file(LocalFilename),
{LocalFilename, Bin}
end;
write ->
{LocalFilename, []}
end,
%% Both sides
State2 = State#extra_state{file = File, acc = Acc, count = 0},
{ok, NegotiatedOptions, State2}.
%%-------------------------------------------------------------------
%% Read
%%-------------------------------------------------------------------
extra_read(#extra_state{acc = Bin} = State) when is_binary(Bin) ->
BlkSize = State#extra_state.blksize,
Count = State#extra_state.count + size(Bin),
if
size(Bin) >= BlkSize ->
<<Block:BlkSize/binary, Bin2/binary>> = Bin,
State2 = State#extra_state{acc = Bin2, count = Count},
{more, Block, State2};
size(Bin) < BlkSize ->
Res = [{count, Count}, State#extra_state.peer],
{last, Bin, Res}
end.
%%-------------------------------------------------------------------
%% Write
%%-------------------------------------------------------------------
extra_write(Bin, #extra_state{acc = List} = State) when is_binary(Bin), is_list(List) ->
Size = size(Bin),
BlkSize = State#extra_state.blksize,
if
Size == BlkSize ->
{more, State#extra_state{acc = [Bin | List]}};
Size < BlkSize ->
Bin2 = list_to_binary(lists:reverse([Bin | List])),
Res = [{bin, Bin2}, State#extra_state.peer],
file:write_file(State#extra_state.file, Bin2),
{last, Res}
end.
%%-------------------------------------------------------------------
%% Abort
%%-------------------------------------------------------------------
extra_abort(_Code, _Text, #extra_state{}) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Re-send client
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
resend_client(doc) ->
["Verify that the server behaves correctly when the client re-sends packets."];
resend_client(suite) ->
[];
resend_client(Config) when is_list(Config) ->
Host = {127, 0, 0, 1},
{Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])),
?VERIFY(ok, resend_read_client(Host, Port, 10)),
?VERIFY(ok, resend_read_client(Host, Port, 512)),
?VERIFY(ok, resend_read_client(Host, Port, 1025)),
?VERIFY(ok, resend_write_client(Host, Port, 10)),
?VERIFY(ok, resend_write_client(Host, Port, 512)),
?VERIFY(ok, resend_write_client(Host, Port, 1025)),
%% Cleanup
unlink(DaemonPid),
exit(DaemonPid, kill),
ok.
resend_read_client(Host, Port, BlkSize) ->
RemoteFilename = "tftp_resend_read_client.tmp",
Block1 = lists:duplicate(BlkSize, $1),
Block2 = lists:duplicate(BlkSize, $2),
Block3 = lists:duplicate(BlkSize, $3),
Block4 = lists:duplicate(BlkSize, $4),
Block5 = lists:duplicate(BlkSize, $5),
Blocks = [Block1, Block2, Block3, Block4, Block5],
Blob = list_to_binary(Blocks),
?VERIFY(ok, file:write_file(RemoteFilename, Blob)),
Timeout = timer:seconds(3),
?VERIFY(timeout, recv(0)),
%% Open socket
{ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
NewPort =
if
BlkSize =:= 512 ->
%% Send READ
ReadBin = list_to_binary(ReadList),
?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
%% Sleep a while in order to provoke the server to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv DATA #1 (the packet that the server think that we have lost)
{udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Data1Bin}, recv(Timeout)),
NewPort0;
true ->
%% Send READ
BlkSizeList = integer_to_list(BlkSize),
Options = ["blksize", 0, BlkSizeList, 0],
ReadBin = list_to_binary([ReadList | Options]),
?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
%% Recv OACK
OptionAckBin = list_to_binary([0, 6 | Options]),
{udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
%% Send ACK #0
Ack0Bin = <<0, 4, 0, 0>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)),
%% Send ACK #0 AGAIN (pretend that we timed out)
timer:sleep(timer:seconds(1)),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)),
%% Recv DATA #1 (the packet that the server think that we have lost)
?VERIFY({udp, Socket, Host, NewPort0, Data1Bin}, recv(Timeout)),
NewPort0
end,
%% Recv DATA #1 AGAIN (the re-sent package)
?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)),
%% Send ACK #1
Ack1Bin = <<0, 4, 0, 1>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)),
%% Recv DATA #2
Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)),
%% Send ACK #2
Ack2Bin = <<0, 4, 0, 2>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
%% Recv DATA #3
Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
?VERIFY({udp, Socket, Host, NewPort, Data3Bin}, recv(Timeout)),
%% Send ACK #3
Ack3Bin = <<0, 4, 0, 3>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)),
%% Send ACK #3 AGAIN (pretend that we timed out)
timer:sleep(timer:seconds(1)),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)),
%% Recv DATA #4 (the packet that the server think that we have lost)
Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)),
%% Recv DATA #4 AGAIN (the re-sent package)
?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)),
%% Send ACK #2 which is out of range
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
%% Send ACK #4
Ack4Bin = <<0, 4, 0, 4>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack4Bin)),
%% Recv DATA #5
Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
?VERIFY({udp, Socket, Host, NewPort, Data5Bin}, recv(Timeout)),
%% Send ACK #5
Ack5Bin = <<0, 4, 0, 5>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack5Bin)),
%% Close socket
?VERIFY(ok, gen_udp:close(Socket)),
?VERIFY(timeout, recv(Timeout)),
?VERIFY(ok, file:delete(RemoteFilename)),
ok.
resend_write_client(Host, Port, BlkSize) ->
RemoteFilename = "tftp_resend_write_client.tmp",
Block1 = lists:duplicate(BlkSize, $1),
Block2 = lists:duplicate(BlkSize, $2),
Block3 = lists:duplicate(BlkSize, $3),
Block4 = lists:duplicate(BlkSize, $4),
Block5 = lists:duplicate(BlkSize, $5),
Blocks = [Block1, Block2, Block3, Block4, Block5],
Blob = list_to_binary(Blocks),
?IGNORE(file:delete(RemoteFilename)),
?VERIFY({error, enoent}, file:read_file(RemoteFilename)),
Timeout = timer:seconds(3),
?VERIFY(timeout, recv(0)),
%% Open socket
{ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
WriteList = [0, 2, RemoteFilename, 0, "octet", 0],
NewPort =
if
BlkSize =:= 512 ->
%% Send WRITE
WriteBin = list_to_binary(WriteList),
?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)),
%% Sleep a while in order to provoke the server to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv ACK #0 (the packet that the server think that we have lost)
Ack0Bin = <<0, 4, 0, 0>>,
?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)),
%% Recv ACK #0 AGAIN (the re-sent package)
{udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)),
NewPort0;
true ->
%% Send WRITE
BlkSizeList = integer_to_list(BlkSize),
WriteBin = list_to_binary([WriteList, "blksize", 0, BlkSizeList, 0]),
?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)),
%% Sleep a while in order to provoke the server to re-send the packet
timer:sleep(timer:seconds(1)),
%% Recv OACK (the packet that the server think that we have lost)
OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
%% Recv OACK AGAIN (the re-sent package)
{udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
NewPort0
end,
%% Send DATA #1
Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data1Bin)),
%% Recv ACK #1
Ack1Bin = <<0, 4, 0, 1>>,
?VERIFY({udp, Socket, Host, NewPort, Ack1Bin}, recv(Timeout)),
%% Send DATA #2
Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)),
%% Recv ACK #2
Ack2Bin = <<0, 4, 0, 2>>,
?VERIFY({udp, Socket, Host, NewPort, Ack2Bin}, recv(Timeout)),
%% Send DATA #3
Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)),
%% Recv ACK #3
Ack3Bin = <<0, 4, 0, 3>>,
?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)),
%% Send DATA #3 AGAIN (pretend that we timed out)
timer:sleep(timer:seconds(1)),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)),
%% Recv ACK #3 AGAIN (the packet that the server think that we have lost)
?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)),
%% Send DATA #2 which is out of range
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)),
%% Send DATA #4
Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data4Bin)),
%% Recv ACK #4
Ack4Bin = <<0, 4, 0, 4>>,
?VERIFY({udp, Socket, Host, NewPort, Ack4Bin}, recv(Timeout)),
%% Send DATA #5
Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data5Bin)),
%% Recv ACK #5
Ack5Bin = <<0, 4, 0, 5>>,
?VERIFY({udp, Socket, Host, NewPort, Ack5Bin}, recv(Timeout)),
%% Close socket
?VERIFY(ok, gen_udp:close(Socket)),
?VERIFY(timeout, recv(Timeout)),
?VERIFY({ok, Blob}, file:read_file(RemoteFilename)),
?VERIFY(ok, file:delete(RemoteFilename)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Re-send server
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
resend_server(doc) ->
["Verify that the server behaves correctly when the server re-sends packets."];
resend_server(suite) ->
[];
resend_server(Config) when is_list(Config) ->
Host = {127, 0, 0, 1},
?VERIFY(ok, resend_read_server(Host, 10)),
?VERIFY(ok, resend_read_server(Host, 512)),
?VERIFY(ok, resend_read_server(Host, 1025)),
?VERIFY(ok, resend_write_server(Host, 10)),
?VERIFY(ok, resend_write_server(Host, 512)),
?VERIFY(ok, resend_write_server(Host, 1025)),
ok.
resend_read_server(Host, BlkSize) ->
RemoteFilename = "tftp_resend_read_server.tmp",
Block1 = lists:duplicate(BlkSize, $1),
Block2 = lists:duplicate(BlkSize, $2),
Block3 = lists:duplicate(BlkSize, $3),
Block4 = lists:duplicate(BlkSize, $4),
Block5 = lists:duplicate(BlkSize, $5),
Block6 = [],
Blocks = [Block1, Block2, Block3, Block4, Block5, Block6],
Blob = list_to_binary(Blocks),
Timeout = timer:seconds(3),
?VERIFY(timeout, recv(0)),
%% Open daemon socket
{ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
{ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)),
%% Open server socket
{ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
?IGNORE(inet:port(ServerSocket)),
%% Prepare client process
ReplyTo = self(),
ClientFun =
fun(Extra) ->
Options = [{port, DaemonPort}, {debug, brief}] ++ Extra,
Res = ?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, Options)),
ReplyTo ! {self(), {tftp_client_reply, Res}},
exit(normal)
end,
ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
Ack1Bin = <<0, 4, 0, 1>>,
{ClientPort, ClientPid} =
if
BlkSize =:= 512 ->
%% Start client process
ClientPid0 = spawn_link(fun() -> ClientFun([]) end),
%% Recv READ
ReadBin = list_to_binary(ReadList),
{udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)),
%% Send DATA #1
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)),
%% Sleep a while in order to provoke the client to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv ACK #1 (the packet that the server think that we have lost)
?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack1Bin}, recv(Timeout)),
%% Recv ACK #1 AGAIN (the re-sent package)
?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)),
{ClientPort0, ClientPid0};
true ->
%% Start client process
BlkSizeList = integer_to_list(BlkSize),
ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end),
%% Recv READ
Options = ["blksize", 0, BlkSizeList, 0],
ReadBin = list_to_binary([ReadList | Options]),
{udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)),
%% Send OACK
BlkSizeList = integer_to_list(BlkSize),
OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)),
%% Sleep a while in order to provoke the client to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv ACK #0 (the packet that the server think that we have lost)
Ack0Bin = <<0, 4, 0, 0>>,
?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)),
%% Recv ACK #0 AGAIN (the re-sent package)
?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)),
%% Send DATA #1
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)),
%% Recv ACK #1
?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)),
{ClientPort0, ClientPid0}
end,
%% Send DATA #2
Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data2Bin)),
%% Recv ACK #2
Ack2Bin = <<0, 4, 0, 2>>,
?VERIFY({udp, ServerSocket, Host, ClientPort, Ack2Bin}, recv(Timeout)),
%% Send DATA #3
Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
%% Recv ACK #3
Ack3Bin = <<0, 4, 0, 3>>,
?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)),
%% Send DATA #3 AGAIN (pretend that we timed out)
timer:sleep(timer:seconds(1)),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
%% Recv ACK #3 AGAIN (the packet that the server think that we have lost)
?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)),
%% Send DATA #4
Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data4Bin)),
%% Recv ACK #4
Ack4Bin = <<0, 4, 0, 4>>,
?VERIFY({udp, ServerSocket, Host, ClientPort, Ack4Bin}, recv(Timeout)),
%% Send DATA #3 which is out of range
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)),
%% Send DATA #5
Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data5Bin)),
%% Recv ACK #5
Ack5Bin = <<0, 4, 0, 5>>,
?VERIFY({udp, ServerSocket, Host, ClientPort, Ack5Bin}, recv(Timeout)),
%% Send DATA #6
Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data6Bin)),
%% Close daemon and server sockets
?VERIFY(ok, gen_udp:close(ServerSocket)),
?VERIFY(ok, gen_udp:close(DaemonSocket)),
?VERIFY({ClientPid, {tftp_client_reply, {ok, Blob}}}, recv(Timeout)),
?VERIFY(timeout, recv(Timeout)),
ok.
resend_write_server(Host, BlkSize) ->
RemoteFilename = "tftp_resend_write_server.tmp",
Block1 = lists:duplicate(BlkSize, $1),
Block2 = lists:duplicate(BlkSize, $2),
Block3 = lists:duplicate(BlkSize, $3),
Block4 = lists:duplicate(BlkSize, $4),
Block5 = lists:duplicate(BlkSize, $5),
Block6 = [],
Blocks = [Block1, Block2, Block3, Block4, Block5, Block6],
Blob = list_to_binary(Blocks),
Size = size(Blob),
Timeout = timer:seconds(3),
?VERIFY(timeout, recv(0)),
%% Open daemon socket
{ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
{ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)),
%% Open server socket
{ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
?IGNORE(inet:port(ServerSocket)),
%% Prepare client process
ReplyTo = self(),
ClientFun =
fun(Extra) ->
Options = [{port, DaemonPort}, {debug, brief}] ++ Extra,
Res = ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, Options)),
ReplyTo ! {self(), {tftp_client_reply, Res}},
exit(normal)
end,
WriteList = [0, 2, RemoteFilename, 0, "octet", 0],
Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
{ClientPort, ClientPid} =
if
BlkSize =:= 512 ->
%% Start client process
ClientPid0 = spawn_link(fun() -> ClientFun([]) end),
%% Recv WRITE
WriteBin = list_to_binary(WriteList),
io:format("WriteBin ~p\n", [WriteBin]),
{udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)),
%% Send ACK #1
Ack0Bin = <<0, 4, 0, 0>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Ack0Bin)),
%% Sleep a while in order to provoke the client to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv DATA #1 (the packet that the server think that we have lost)
?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
%% Recv DATA #1 AGAIN (the re-sent package)
?VERIFY({udp, ServerSocket, Host, _, Data1Bin}, recv(Timeout)),
{ClientPort0, ClientPid0};
true ->
%% Start client process
BlkSizeList = integer_to_list(BlkSize),
ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end),
%% Recv WRITE
Options = ["blksize", 0, BlkSizeList, 0],
WriteBin = list_to_binary([WriteList | Options]),
{udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)),
%% Send OACK
BlkSizeList = integer_to_list(BlkSize),
OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)),
%% Sleep a while in order to provoke the client to re-send the packet
timer:sleep(Timeout + timer:seconds(1)),
%% Recv DATA #1 (the packet that the server think that we have lost)
?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
%% Recv DATA #1 AGAIN (the re-sent package)
?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)),
{ClientPort0, ClientPid0}
end,
%% Send ACK #1
Ack1Bin = <<0, 4, 0, 1>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack1Bin)),
%% Recv DATA #2
Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
?VERIFY({udp, ServerSocket, Host, ClientPort, Data2Bin}, recv(Timeout)),
%% Send ACK #2
Ack2Bin = <<0, 4, 0, 2>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack2Bin)),
%% Recv DATA #3
Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]),
?VERIFY({udp, ServerSocket, Host, ClientPort, Data3Bin}, recv(Timeout)),
%% Send ACK #3
Ack3Bin = <<0, 4, 0, 3>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
%% Send ACK #3 AGAIN (pretend that we timed out)
timer:sleep(timer:seconds(1)),
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
%% Recv DATA #4 (the packet that the server think that we have lost)
Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]),
?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)),
%% Recv DATA #4 AGAIN (the re-sent package)
?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)),
%% Send ACK #4
Ack4Bin = <<0, 4, 0, 4>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack4Bin)),
%% Recv DATA #5
Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]),
?VERIFY({udp, ServerSocket, Host, ClientPort, Data5Bin}, recv(Timeout)),
%% Send ACK #3 which is out of range
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)),
%% Send ACK #5
Ack5Bin = <<0, 4, 0, 5>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack5Bin)),
%% Recv DATA #6
Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]),
?VERIFY({udp, ServerSocket, Host, ClientPort, Data6Bin}, recv(Timeout)),
%% Send ACK #6
Ack6Bin = <<0, 4, 0, 6>>,
?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack6Bin)),
%% Close daemon and server sockets
?VERIFY(ok, gen_udp:close(ServerSocket)),
?VERIFY(ok, gen_udp:close(DaemonSocket)),
?VERIFY({ClientPid, {tftp_client_reply, {ok, Size}}}, recv(Timeout)),
?VERIFY(timeout, recv(Timeout)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
reuse_connection(doc) ->
["Verify that the server can reuse an ongiong connection when same client resends request."];
reuse_connection(suite) ->
[];
reuse_connection(Config) when is_list(Config) ->
Host = {127, 0, 0, 1},
{Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])),
RemoteFilename = "reuse_connection.tmp",
BlkSize = 512,
Block1 = lists:duplicate(BlkSize, $1),
Block2 = lists:duplicate(BlkSize div 2, $2),
Blocks = [Block1, Block2],
Blob = list_to_binary(Blocks),
?VERIFY(ok, file:write_file(RemoteFilename, Blob)),
Seconds = 3,
Timeout = timer:seconds(Seconds),
?VERIFY(timeout, recv(0)),
%% Open socket
{ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])),
ReadList = [0, 1, RemoteFilename, 0, "octet", 0],
Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]),
%% Send READ
TimeoutList = integer_to_list(Seconds),
Options = ["timeout", 0, TimeoutList, 0],
ReadBin = list_to_binary([ReadList | Options]),
?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
%% Send yet another READ for same file
?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)),
%% Recv OACK
OptionAckBin = list_to_binary([0, 6 | Options]),
{udp, _, _, NewPort, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)),
%% Send ACK #0
Ack0Bin = <<0, 4, 0, 0>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack0Bin)),
%% Recv DATA #1
?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)),
%% Send ACK #1
Ack1Bin = <<0, 4, 0, 1>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)),
%% Recv DATA #2
Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]),
?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)),
%% Send ACK #2
Ack2Bin = <<0, 4, 0, 2>>,
?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)),
%% Close socket
?VERIFY(ok, gen_udp:close(Socket)),
?VERIFY(timeout, recv(Timeout)),
?VERIFY(ok, file:delete(RemoteFilename)),
%% Cleanup
unlink(DaemonPid),
exit(DaemonPid, kill),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Goodies
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
recv(Timeout) ->
receive
Msg ->
Msg
after Timeout ->
timeout
end.