diff options
Diffstat (limited to 'lib/tftp/test')
-rw-r--r-- | lib/tftp/test/Makefile | 250 | ||||
-rw-r--r-- | lib/tftp/test/tftp.config | 1 | ||||
-rw-r--r-- | lib/tftp/test/tftp.cover | 2 | ||||
-rw-r--r-- | lib/tftp/test/tftp.spec | 1 | ||||
-rw-r--r-- | lib/tftp/test/tftp_SUITE.erl | 1001 | ||||
-rw-r--r-- | lib/tftp/test/tftp_bench.spec | 1 | ||||
-rw-r--r-- | lib/tftp/test/tftp_test_lib.erl | 308 | ||||
-rw-r--r-- | lib/tftp/test/tftp_test_lib.hrl | 44 |
8 files changed, 1608 insertions, 0 deletions
diff --git a/lib/tftp/test/Makefile b/lib/tftp/test/Makefile new file mode 100644 index 0000000000..99f36256b0 --- /dev/null +++ b/lib/tftp/test/Makefile @@ -0,0 +1,250 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2018. 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% +# +# +# For an outline of how this all_SUITE_data stuff works, see the +# make file ../../ssl/test/Makefile. +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN = $(TFTP_VSN) + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +INCLUDES = -I. \ + -I$(ERL_TOP)/lib/tftp/src + +CP = cp + +ifeq ($(TESTROOT_DIR),) +TESTROOT_DIR = /ldisk/tests/$(USER)/tftp +endif + +ifeq ($(TFTP_DATA_DIR),) +TFTP_DATA_DIR = $(TESTROOT_DIR)/data_dir +endif + +ifeq ($(TFTP_PRIV_DIR),) +TFTP_PRIV_DIR = $(TESTROOT_DIR)/priv_dir +endif + +TFTP_FLAGS = -Dtftp__data_dir='"$(TFTP_DATA_DIR)"' \ + -Dtftp_priv_dir='"$(TFTP_PRIV_DIR)"' + + +### +### test suite debug flags +### +ifeq ($(TFTP_DEBUG_CLIENT),) + TFTP_DEBUG_CLIENT = y +endif + +ifeq ($(TFTP_DEBUG_CLIENT),) + TFTP_FLAGS += -Dtftp_debug_client +endif + +ifeq ($(TFTP_TRACE_CLIENT),) + TFTP_DEBUG_CLIENT = y +endif + +ifeq ($(TFTP_TRACE_CLIENT),y) + TFTP_FLAGS += -Dtftp_trace_client +endif + +ifneq ($(TFTP_DEBUG),) + TFTP_DEBUG = s +endif + +ifeq ($(TFTP_DEBUG),l) + TFTP_FLAGS += -Dtftp_log +endif + +ifeq ($(TFTP_DEBUG),d) + TFTP_FLAGS += -Dtftp_debug -Dtftp_log +endif + + +TFTP_FLAGS += -pa ../tftp/ebin + +TFTP_ROOT = ../tftp + +MODULES = \ + tftp_SUITE \ + tftp_test_lib + + +EBIN = . + +HRL_FILES = \ + ../src/tftp.hrl \ + tftp_test_lib.hrl + +ERL_FILES = $(MODULES:%=%.erl) + +SOURCE = $(ERL_FILES) $(HRL_FILES) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +TFTP_SPECS = tftp.spec tftp_bench.spec +COVER_FILE = tftp.cover +TFTP_FILES = tftp.config $(TFTP_SPECS) + + +TFTP_DATADIRS = tftp_SUITE_data + +DATADIRS = $(TFTP_DATADIRS) + +EMAKEFILE = Emakefile +MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile) + +ifeq ($(MAKE_EMAKE),) +BUILDTARGET = $(TARGET_FILES) +RELTEST_FILES = $(COVER_FILE) $(TFTP_SPECS) $(SOURCE) +else +BUILDTARGET = emakebuild +RELTEST_FILES = $(EMAKEFILE) $(COVER_FILE) $(TFTP_SPECS) $(SOURCE) +endif + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- + +RELTESTSYSDIR = "$(RELEASE_PATH)/tftp_test" +RELTESTSYSALLDATADIR = $(RELTESTSYSDIR)/all_SUITE_data +RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin + + +# ---------------------------------------------------- +# FLAGS +# The path to the test_server ebin dir is needed when +# running the target "targets". +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += \ + $(INCLUDES) \ + $(TFTP_FLAGS) + +# ---------------------------------------------------- +# Targets +# erl -sname kalle -pa ../ebin +# If you intend to run the test suite locally (private), then +# there is some requirements: +# 1) TFTP_PRIV_DIR must be created +# ---------------------------------------------------- + +tests debug opt: $(BUILDTARGET) + +targets: $(TARGET_FILES) + +.PHONY: emakebuild + +emakebuild: $(EMAKEFILE) + +$(EMAKEFILE): + $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE) + $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE) + +clean: + rm -f $(EMAKEFILE) + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) "$(RELSYSDIR)/test" + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) "$(RELSYSDIR)/test" + $(INSTALL_DATA) $(TFTP_FILES) "$(RELSYSDIR)/test" + @for d in $(DATADIRS); do \ + echo "installing data dir $$d"; \ + if test -f $$d/TAR.exclude; then \ + echo $$d/TAR.exclude2 > $$d/TAR.exclude2; \ + cat $$d/TAR.exclude >> $$d/TAR.exclude2; \ + find $$d -name '*.contrib*' >> $$d/TAR.exclude2; \ + find $$d -name '*.keep*' >> $$d/TAR.exclude2; \ + find $$d -name '*.mkelem*' >> $$d/TAR.exclude2; \ + find $$d -name '*~' >> $$d/TAR.exclude2; \ + find $$d -name 'erl_crash.dump' >> $$d/TAR.exclude2; \ + find $$d -name 'core' >> $$d/TAR.exclude2; \ + find $$d -name '.cmake.state' >> $$d/TAR.exclude2; \ + tar cfX - $$d/TAR.exclude2 $$d | (cd "$(RELSYSDIR)/test"; tar xf -); \ + else \ + tar cf - $$d | (cd "$(RELSYSDIR)/test"; tar xf -); \ + fi; \ + done + +release_tests_spec: opt + $(INSTALL_DIR) $(RELTESTSYSDIR) + $(INSTALL_DATA) $(RELTEST_FILES) $(RELTESTSYSDIR) + chmod -R u+w $(RELTESTSYSDIR) + tar chf - $(DATADIRS) | (cd $(RELTESTSYSDIR); tar xf -) + $(INSTALL_DIR) $(RELTESTSYSALLDATADIR) + $(INSTALL_DIR) $(RELTESTSYSBINDIR) + chmod -R +x $(RELTESTSYSBINDIR) + $(INSTALL_DIR) $(RELTESTSYSALLDATADIR)/win32/lib + +release_docs_spec: + +info: + @echo "MAKE_EMAKE = $(MAKE_EMAKE)" + @echo "EMAKEFILE = $(EMAKEFILE)" + @echo "BUILDTARGET = $(BUILDTARGET)" + @echo "" + @echo "MODULES = $(MODULES)" + @echo "ERL_FILES = $(ERL_FILES)" + @echo "SOURCE = $(SOURCE)" + @echo "TARGET_FILES = $(TARGET_FILES)" + @echo "" + @echo "TFTP_SPECS = $(TFTP_SPECS)" + @echo "TFTP_FILES = $(TFTP_FILES)" + @echo "" + @echo "RELEASE_PATH = "$(RELEASE_PATH)"" + @echo "RELSYSDIR = "$(RELSYSDIR)"" + @echo "RELTESTSYSDIR = $(RELTESTSYSDIR)" + @echo "RELTESTSYSALLDATADIR = $(RELTESTSYSALLDATADIR)" + @echo "RELTESTSYSBINDIR = $(RELTESTSYSBINDIR)" + @echo "" + @echo "DATADIRS = $(DATADIRS)" + @echo "REL_DATADIRS = $(REL_DATADIRS)" + @echo "" + @echo "TFTP_DATA_DIR = $(TFTP_DATA_DIR)" + @echo "TFTP_PRIV_DIR = $(TFTP_PRIV_DIR)" + @echo "TFTP_ROOT = $(TFTP_ROOT)" + @echo "TFTP_FLAGS = $(TFTP_FLAGS)" + + diff --git a/lib/tftp/test/tftp.config b/lib/tftp/test/tftp.config new file mode 100644 index 0000000000..2600237da9 --- /dev/null +++ b/lib/tftp/test/tftp.config @@ -0,0 +1 @@ +[].
\ No newline at end of file diff --git a/lib/tftp/test/tftp.cover b/lib/tftp/test/tftp.cover new file mode 100644 index 0000000000..22ef5d0dda --- /dev/null +++ b/lib/tftp/test/tftp.cover @@ -0,0 +1,2 @@ +{incl_app,tftp,details}. + diff --git a/lib/tftp/test/tftp.spec b/lib/tftp/test/tftp.spec new file mode 100644 index 0000000000..f3537bc652 --- /dev/null +++ b/lib/tftp/test/tftp.spec @@ -0,0 +1 @@ +{suites,"../tftp_test", all}. diff --git a/lib/tftp/test/tftp_SUITE.erl b/lib/tftp/test/tftp_SUITE.erl new file mode 100644 index 0000000000..a0f6cb1ca4 --- /dev/null +++ b/lib/tftp/test/tftp_SUITE.erl @@ -0,0 +1,1001 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2018. 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% +%% + +-module(tftp_SUITE). + +-compile(export_all). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Includes and defines +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-include_lib("common_test/include/ct.hrl"). +-include("tftp_test_lib.hrl"). + +-define(START_DAEMON(Port, Options), + begin + {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). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + simple, + extra, + reuse_connection, + resend_client, + resend_server, + large_file, + app, + appup, + start_tftpd + ]. + +groups() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +app() -> + [{doc, "Test that the tftp app file is ok"}]. +app(Config) when is_list(Config) -> + ok = ?t:app_test(tftp). + +%%-------------------------------------------------------------------- +appup() -> + [{doc, "Test that the tftp appup file is ok"}]. +appup(Config) when is_list(Config) -> + ok = ?t:appup_test(tftp). + +start_tftpd() -> + [{doc, "Start/stop of tfpd service"}]. +start_tftpd(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ok = tftp:start(), + {ok, Pid0} = tftp:start_service([{host, "localhost"}, {port, 0}]), + Pids0 = [ServicePid || {_, ServicePid} <- tftp:services()], + true = lists:member(Pid0, Pids0), + {ok, [_|_]} = tftp:service_info(Pid0), + tftp:stop_service(Pid0), + ct:sleep(100), + Pids1 = [ServicePid || {_, ServicePid} <- tftp:services()], + false = lists:member(Pid0, Pids1), + + {ok, Pid1} = + tftp:start_standalone([{host, "localhost"}, {port, 0}]), + Pids2 = [ServicePid || {_, ServicePid} <- tftp:services()], + false = lists:member(Pid1, Pids2), + %% Standalone service is not supervised + {error,not_found} = tftp:stop_service(Pid1), + ok = tftp:stop(), + + application:load(tftp), + application:set_env(tftp, services, [{tftpd, [{host, "localhost"}, + {port, 0}]}]), + ok = tftp:start(), + 1 = length(tftp:services()), + application:unset_env(tftp, services), + ok = tftp:stop(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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(tftp)), + + {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(tftp)), + 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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Large file: transfer > 65535 blocks +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +large_file(doc) -> + ["Start the daemon and test transfer of files greater than 32M."]; +large_file(suite) -> + []; +large_file(Config) when is_list(Config) -> + ?VERIFY(ok, application:start(tftp)), + + {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])), + + %% Read fail + RemoteFilename = "tftp_temporary_large_file_remote_test_file.txt", + LocalFilename = "tftp_temporary_large_file_local_test_file.txt", + + {ok, FH} = file:open(LocalFilename, [write,exclusive]), + {ok, Size} = file:position(FH, {eof, 2*512*65535}), + ok = file:truncate(FH), + ?IGNORE(file:close(FH)), + + %% Write and read + ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, LocalFilename, [{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(tftp)), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Goodies +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +recv(Timeout) -> + receive + Msg -> + Msg + after Timeout -> + timeout + end. diff --git a/lib/tftp/test/tftp_bench.spec b/lib/tftp/test/tftp_bench.spec new file mode 100644 index 0000000000..43fa385c85 --- /dev/null +++ b/lib/tftp/test/tftp_bench.spec @@ -0,0 +1 @@ +{suites,"../tftp_test",[]}. diff --git a/lib/tftp/test/tftp_test_lib.erl b/lib/tftp/test/tftp_test_lib.erl new file mode 100644 index 0000000000..04534228c2 --- /dev/null +++ b/lib/tftp/test/tftp_test_lib.erl @@ -0,0 +1,308 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2018. 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% +%% + +-module(tftp_test_lib). + +-compile(export_all). + +-include("tftp_test_lib.hrl"). + +%% +%% ----- +%% + +init_per_testcase(_Case, Config) when is_list(Config) -> + io:format("\n ", []), + ?IGNORE(application:stop(tftp)), + Config. + +end_per_testcase(_Case, Config) when is_list(Config) -> + ?IGNORE(application:stop(tftp)), + Config. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Infrastructure for test suite +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +error(Actual, Mod, Line) -> + (catch global:send(tftp_global_logger, {failed, Mod, Line})), + log("<ERROR> Bad result: ~p\n", [Actual], Mod, Line), + Label = lists:concat([Mod, "(", Line, ") unexpected result"]), + et:report_event(60, Mod, Mod, Label, + [{line, Mod, Line}, {error, Actual}]), + case global:whereis_name(tftp_test_case_sup) of + undefined -> + ignore; + Pid -> + Fail = #'REASON'{mod = Mod, line = Line, desc = Actual}, + Pid ! {fail, self(), Fail} + end, + Actual. + +log(Format, Args, Mod, Line) -> + case global:whereis_name(tftp_global_logger) of + undefined -> + io:format(user, "~p(~p): " ++ Format, + [Mod, Line] ++ Args); + Pid -> + io:format(Pid, "~p(~p): " ++ Format, + [Mod, Line] ++ Args) + end. + +default_config() -> + []. + +t() -> + t([{?MODULE, all}]). + +t(Cases) -> + t(Cases, default_config()). + +t(Cases, Config) -> + process_flag(trap_exit, true), + Res = lists:flatten(do_test(Cases, Config)), + io:format("Res: ~p\n", [Res]), + display_result(Res), + Res. + +do_test({Mod, Fun}, Config) when is_atom(Mod), is_atom(Fun) -> + case catch apply(Mod, Fun, [suite]) of + [] -> + io:format("Eval: ~p:", [{Mod, Fun}]), + Res = eval(Mod, Fun, Config), + {R, _, _} = Res, + io:format(" ~p\n", [R]), + Res; + + Cases when is_list(Cases) -> + io:format("Expand: ~p ...\n", [{Mod, Fun}]), + Map = fun(Case) when is_atom(Case)-> {Mod, Case}; + (Case) -> Case + end, + do_test(lists:map(Map, Cases), Config); + + {req, _, {conf, Init, Cases, Finish}} -> + case (catch apply(Mod, Init, [Config])) of + Conf when is_list(Conf) -> + io:format("Expand: ~p ...\n", [{Mod, Fun}]), + Map = fun(Case) when is_atom(Case)-> {Mod, Case}; + (Case) -> Case + end, + Res = do_test(lists:map(Map, Cases), Conf), + (catch apply(Mod, Finish, [Conf])), + Res; + + {'EXIT', {skipped, Reason}} -> + io:format(" => skipping: ~p\n", [Reason]), + [{skipped, {Mod, Fun}, Reason}]; + + Error -> + io:format(" => failed: ~p\n", [Error]), + [{failed, {Mod, Fun}, Error}] + end; + + {'EXIT', {undef, _}} -> + io:format("Undefined: ~p\n", [{Mod, Fun}]), + [{nyi, {Mod, Fun}, ok}]; + + Error -> + io:format("Ignoring: ~p: ~p\n", [{Mod, Fun}, Error]), + [{failed, {Mod, Fun}, Error}] + end; +do_test(Mod, Config) when is_atom(Mod) -> + Res = do_test({Mod, all}, Config), + Res; +do_test(Cases, Config) when is_list(Cases) -> + [do_test(Case, Config) || Case <- Cases]; +do_test(Bad, _Config) -> + [{badarg, Bad, ok}]. + +eval(Mod, Fun, Config) -> + TestCase = {?MODULE, Mod, Fun}, + Label = lists:concat(["TEST CASE: ", Fun]), + et:report_event(40, ?MODULE, Mod, Label ++ " started", + [TestCase, Config]), + global:register_name(tftp_test_case_sup, self()), + Flag = process_flag(trap_exit, true), + Config2 = Mod:init_per_testcase(Fun, Config), + Pid = spawn_link(?MODULE, do_eval, [self(), Mod, Fun, Config2]), + R = wait_for_evaluator(Pid, Mod, Fun, Config2, []), + Mod:end_per_testcase(Fun, Config2), + global:unregister_name(tftp_test_case_sup), + process_flag(trap_exit, Flag), + R. + +wait_for_evaluator(Pid, Mod, Fun, Config, Errors) -> + TestCase = {?MODULE, Mod, Fun}, + Label = lists:concat(["TEST CASE: ", Fun]), + receive + {done, Pid, ok} when Errors == [] -> + et:report_event(40, Mod, ?MODULE, Label ++ " ok", + [TestCase, Config]), + {ok, {Mod, Fun}, Errors}; + {done, Pid, {ok, _}} when Errors == [] -> + et:report_event(40, Mod, ?MODULE, Label ++ " ok", + [TestCase, Config]), + {ok, {Mod, Fun}, Errors}; + {done, Pid, Fail} -> + et:report_event(20, Mod, ?MODULE, Label ++ " failed", + [TestCase, Config, {return, Fail}, Errors]), + {failed, {Mod,Fun}, Fail}; + {'EXIT', Pid, {skipped, Reason}} -> + et:report_event(20, Mod, ?MODULE, Label ++ " skipped", + [TestCase, Config, {skipped, Reason}]), + {skipped, {Mod, Fun}, Errors}; + {'EXIT', Pid, Reason} -> + et:report_event(20, Mod, ?MODULE, Label ++ " crashed", + [TestCase, Config, {'EXIT', Reason}]), + {crashed, {Mod, Fun}, [{'EXIT', Reason} | Errors]}; + {fail, Pid, Reason} -> + wait_for_evaluator(Pid, Mod, Fun, Config, Errors ++ [Reason]) + end. + +do_eval(ReplyTo, Mod, Fun, Config) -> + case (catch apply(Mod, Fun, [Config])) of + {'EXIT', {skipped, Reason}} -> + ReplyTo ! {'EXIT', self(), {skipped, Reason}}; + Other -> + ReplyTo ! {done, self(), Other} + end, + unlink(ReplyTo), + exit(shutdown). + +display_result([]) -> + io:format("OK\n", []); +display_result(Res) when is_list(Res) -> + Ok = [MF || {ok, MF, _} <- Res], + Nyi = [MF || {nyi, MF, _} <- Res], + Skipped = [{MF, Reason} || {skipped, MF, Reason} <- Res], + Failed = [{MF, Reason} || {failed, MF, Reason} <- Res], + Crashed = [{MF, Reason} || {crashed, MF, Reason} <- Res], + display_summary(Ok, Nyi, Skipped, Failed, Crashed), + display_skipped(Skipped), + display_failed(Failed), + display_crashed(Crashed). + +display_summary(Ok, Nyi, Skipped, Failed, Crashed) -> + io:format("\nTest case summary:\n", []), + display_summary(Ok, "successful"), + display_summary(Nyi, "not yet implemented"), + display_summary(Skipped, "skipped"), + display_summary(Failed, "failed"), + display_summary(Crashed, "crashed"), + io:format("\n", []). + +display_summary(Res, Info) -> + io:format(" ~w test cases ~s\n", [length(Res), Info]). + +display_skipped([]) -> + ok; +display_skipped(Skipped) -> + io:format("Skipped test cases:\n", []), + F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, + lists:foreach(F, Skipped), + io:format("\n", []). + + +display_failed([]) -> + ok; +display_failed(Failed) -> + io:format("Failed test cases:\n", []), + F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, + lists:foreach(F, Failed), + io:format("\n", []). + +display_crashed([]) -> + ok; +display_crashed(Crashed) -> + io:format("Crashed test cases:\n", []), + F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, + lists:foreach(F, Crashed), + io:format("\n", []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% generic callback +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-record(generic_state, {state, prepare, open, read, write, abort}). + +prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) -> + State = lookup_option(state, mandatory, Initial), + Prepare = lookup_option(prepare, mandatory, Initial), + Open = lookup_option(open, mandatory, Initial), + Read = lookup_option(read, mandatory, Initial), + Write = lookup_option(write, mandatory, Initial), + Abort = lookup_option(abort, mandatory, Initial), + case Prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of + {ok, AcceptedOptions, NewState} -> + {ok, + AcceptedOptions, + #generic_state{state = NewState, + prepare = Prepare, + open = Open, + read = Read, + write = Write, + abort = Abort}}; + Other -> + Other + end. + +open(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) -> + case prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) of + {ok, SuggestedOptions2, GenericState} -> + open(Peer, Access, LocalFilename, Mode, SuggestedOptions2, GenericState); + Other -> + Other + end; +open(Peer, Access, LocalFilename, Mode, SuggestedOptions, #generic_state{state = State, open = Open} = GenericState) -> + case Open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of + {ok, SuggestedOptions2, NewState} -> + {ok, SuggestedOptions2, GenericState#generic_state{state = NewState}}; + Other -> + Other + end. + +read(#generic_state{state = State, read = Read} = GenericState) -> + case Read(State) of + {more, DataBlock, NewState} -> + {more, DataBlock, GenericState#generic_state{state = NewState}}; + Other -> + Other + end. + +write(DataBlock, #generic_state{state = State, write = Write} = GenericState) -> + case Write(DataBlock, State) of + {more, NewState} -> + {more, GenericState#generic_state{state = NewState}}; + Other -> + Other + end. + +abort(Code, Text, #generic_state{state = State, abort = Abort}) -> + Abort(Code, Text, State). + +lookup_option(Key, Default, Options) -> + case lists:keysearch(Key, 1, Options) of + {value, {_, Val}} -> + Val; + false -> + Default + end. + diff --git a/lib/tftp/test/tftp_test_lib.hrl b/lib/tftp/test/tftp_test_lib.hrl new file mode 100644 index 0000000000..eb8ed77fc1 --- /dev/null +++ b/lib/tftp/test/tftp_test_lib.hrl @@ -0,0 +1,44 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2018. 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% +%% + +-record('REASON', {mod, line, desc}). + +-define(LOG(Format, Args), + tftp_test_lib:log(Format, Args, ?MODULE, ?LINE)). + +-define(ERROR(Reason), + tftp_test_lib:error(Reason, ?MODULE, ?LINE)). + +-define(VERIFY(Expected, Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + case AcTuAlReS of + Expected -> ?LOG("Ok, ~p\n", [AcTuAlReS]); + _ -> ?ERROR(AcTuAlReS) + end, + AcTuAlReS + end()). + +-define(IGNORE(Expr), + fun() -> + AcTuAlReS = (catch (Expr)), + ?LOG("Ok, ~p\n", [AcTuAlReS]), + AcTuAlReS + end()). |