sendfile_SUITE.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. %% Copyright (c) 2013, James Fish <james@fishcakez.com>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(sendfile_SUITE).
  15. -include_lib("common_test/include/ct.hrl").
  16. %% ct.
  17. -export([all/0]).
  18. -export([suite/0]).
  19. -export([groups/0]).
  20. -export([init_per_suite/1]).
  21. -export([end_per_suite/1]).
  22. -export([init_per_group/2]).
  23. -export([end_per_group/2]).
  24. %% Tests.
  25. -export([filename/1]).
  26. -export([rawfile/1]).
  27. -export([rawfile_bytes_large/1]).
  28. -export([rawfile_bytes_zero/1]).
  29. -export([rawfile_chunk_size_large/1]).
  30. -export([rawfile_offset_large/1]).
  31. -export([rawfile_range_large/1]).
  32. -export([rawfile_range_medium/1]).
  33. -export([rawfile_range_small/1]).
  34. -export([ssl_chunk_size/1]).
  35. all() ->
  36. [{group, tcp}, {group, ssl}].
  37. suite() ->
  38. [{timetrap, {seconds, 60}}].
  39. groups() ->
  40. Tests = [
  41. filename,
  42. rawfile,
  43. rawfile_bytes_large,
  44. rawfile_bytes_zero,
  45. rawfile_chunk_size_large,
  46. rawfile_offset_large,
  47. rawfile_range_large,
  48. rawfile_range_medium,
  49. rawfile_range_small
  50. ],
  51. [{tcp, [parallel], Tests}, {ssl, [parallel], Tests ++ [ssl_chunk_size]}].
  52. init_per_suite(Config) ->
  53. ok = application:start(ranch),
  54. ok = application:start(crypto),
  55. Filename = filename:join(?config(priv_dir, Config), "sendfile"),
  56. Binary = crypto:rand_bytes(20 * 1024 * 1024),
  57. ok = file:write_file(Filename, Binary),
  58. [{filename, Filename} | Config].
  59. end_per_suite(Config) ->
  60. application:stop(ranch),
  61. application:stop(crypto),
  62. Filename = ?config(filename, Config),
  63. ok = file:delete(Filename),
  64. ok.
  65. init_per_group(ssl, Config) ->
  66. application:start(asn1),
  67. application:start(public_key),
  68. application:start(ssl),
  69. {_, Cert, Key} = ct_helper:make_certs(),
  70. SslOpts = [{cert, Cert}, {key, Key}],
  71. [{transport, ranch_ssl}, {transport_opts, SslOpts} | Config];
  72. init_per_group(tcp, Config) ->
  73. [{transport, ranch_tcp}, {transport_opts, []} | Config].
  74. end_per_group(ssl, _) ->
  75. application:stop(ssl),
  76. application:stop(public_key),
  77. application:stop(asn1),
  78. ok;
  79. end_per_group(_, _) ->
  80. ok.
  81. %% Check can send a whole file given with filename.
  82. filename(Config) ->
  83. Transport = ?config(transport, Config),
  84. Filename = ?config(filename, Config),
  85. {ok, Binary} = file:read_file(Filename),
  86. Size = byte_size(Binary),
  87. {ok, {Server, Client}} = sockets(Config),
  88. Ref = recv(Transport, Server, Size),
  89. {ok, Size} = Transport:sendfile(Client, Filename),
  90. {ok, Binary} = result(Ref),
  91. {error, timeout} = Transport:recv(Server, 1, 100),
  92. ok = Transport:close(Client),
  93. ok = Transport:close(Server).
  94. %% Check can send a whole file with rawfile.
  95. rawfile(Config) ->
  96. Transport = ?config(transport, Config),
  97. Filename = ?config(filename, Config),
  98. {ok, Binary} = file:read_file(Filename),
  99. Size = byte_size(Binary),
  100. {ok, {Server, Client}} = sockets(Config),
  101. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  102. Ref = recv(Transport, Server, Size),
  103. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size),
  104. {ok, Binary} = result(Ref),
  105. {error, timeout} = Transport:recv(Server, 1, 100),
  106. {ok, 0} = file:position(RawFile, {cur, 0}),
  107. ok = file:close(RawFile),
  108. ok = Transport:close(Client),
  109. ok = Transport:close(Server).
  110. %% Check can send a file where Bytes is larger than file size.
  111. rawfile_bytes_large(Config) ->
  112. Transport = ?config(transport, Config),
  113. Filename = ?config(filename, Config),
  114. {ok, Binary} = file:read_file(Filename),
  115. Size = byte_size(Binary),
  116. {ok, {Server, Client}} = sockets(Config),
  117. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  118. Ref = recv(Transport, Server, Size),
  119. %% Only send Size not Size * 2
  120. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size * 2),
  121. {ok, Binary} = result(Ref),
  122. {error, timeout} = Transport:recv(Server, 1, 100),
  123. {ok, 0} = file:position(RawFile, {cur, 0}),
  124. ok = file:close(RawFile),
  125. ok = Transport:close(Client),
  126. ok = Transport:close(Server).
  127. %% Check can send whole file when Bytes =:= 0.
  128. rawfile_bytes_zero(Config) ->
  129. Transport = ?config(transport, Config),
  130. Filename = ?config(filename, Config),
  131. {ok, Binary} = file:read_file(Filename),
  132. Size = byte_size(Binary),
  133. {ok, {Server, Client}} = sockets(Config),
  134. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  135. Ref = recv(Transport, Server, Size),
  136. {ok, Size} = Transport:sendfile(Client, RawFile, 0, 0),
  137. {ok, Binary} = result(Ref),
  138. {error, timeout} = Transport:recv(Server, 1, 100),
  139. {ok, 0} = file:position(RawFile, {cur, 0}),
  140. ok = file:close(RawFile),
  141. ok = Transport:close(Client),
  142. ok = Transport:close(Server).
  143. %% Check can send file where chunk_size is greater than file size.
  144. rawfile_chunk_size_large(Config) ->
  145. Transport = ?config(transport, Config),
  146. Filename = ?config(filename, Config),
  147. {ok, Binary} = file:read_file(Filename),
  148. Size = byte_size(Binary),
  149. {ok, {Server, Client}} = sockets(Config),
  150. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  151. Ref = recv(Transport, Server, Size),
  152. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size,
  153. [{chunk_size, Size * 2}]),
  154. {ok, Binary} = result(Ref),
  155. {error, timeout} = Transport:recv(Server, 1, 100),
  156. {ok, 0} = file:position(RawFile, {cur, 0}),
  157. ok = file:close(RawFile),
  158. ok = Transport:close(Client),
  159. ok = Transport:close(Server).
  160. %% Check send file where offset is larger than file size sends no bytes and
  161. %% returns {ok, 0}.
  162. rawfile_offset_large(Config) ->
  163. Transport = ?config(transport, Config),
  164. Filename = ?config(filename, Config),
  165. {ok, Binary} = file:read_file(Filename),
  166. Size = byte_size(Binary),
  167. {ok, {Server, Client}} = sockets(Config),
  168. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  169. {ok, 0} = Transport:sendfile(Client, RawFile, Size, 1),
  170. {error, timeout} = Transport:recv(Server, 1, 100),
  171. ok = file:close(RawFile),
  172. ok = Transport:close(Client),
  173. ok = Transport:close(Server).
  174. %% Check can send file with positive Offset and Offset + Bytes larger than file
  175. %% size.
  176. rawfile_range_large(Config) ->
  177. Transport = ?config(transport, Config),
  178. Filename = ?config(filename, Config),
  179. {ok, Binary} = file:read_file(Filename),
  180. Size = byte_size(Binary),
  181. {ok, {Server, Client}} = sockets(Config),
  182. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  183. Initial = 499,
  184. {ok, _} = file:position(RawFile, {bof, Initial}),
  185. Offset = 75,
  186. Bytes = Size * 2,
  187. Sent = Size - Offset,
  188. Ref = recv(Transport, Server, Sent),
  189. {ok, Sent} = Transport:sendfile(Client, RawFile, Offset, Bytes),
  190. Binary2 = binary:part(Binary, Offset, Sent),
  191. {ok, Binary2} = result(Ref),
  192. {error, timeout} = Transport:recv(Server, 1, 100),
  193. {ok, Initial} = file:position(RawFile, {cur, 0}),
  194. ok = file:close(RawFile),
  195. ok = Transport:close(Client),
  196. ok = Transport:close(Server).
  197. %% Check can send file with positive Offset and Offset + Bytes less than file
  198. %% size.
  199. rawfile_range_medium(Config) ->
  200. Transport = ?config(transport, Config),
  201. Filename = ?config(filename, Config),
  202. {ok, Binary} = file:read_file(Filename),
  203. Size = byte_size(Binary),
  204. {ok, {Server, Client}} = sockets(Config),
  205. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  206. Initial = 50,
  207. {ok, _} = file:position(RawFile, {bof, Initial}),
  208. Offset = 50,
  209. Bytes = Size - Offset - 50,
  210. Ref = recv(Transport, Server, Bytes),
  211. {ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes),
  212. Binary2 = binary:part(Binary, Offset, Bytes),
  213. {ok, Binary2} = result(Ref),
  214. {error, timeout} = Transport:recv(Server, 1, 100),
  215. {ok, Initial} = file:position(RawFile, {cur, 0}),
  216. ok = file:close(RawFile),
  217. ok = Transport:close(Client),
  218. ok = Transport:close(Server).
  219. %% Check can send file with positive Offset, Offset + Bytes less than file
  220. %% size and Bytes less than chunk_size.
  221. rawfile_range_small(Config) ->
  222. Transport = ?config(transport, Config),
  223. Filename = ?config(filename, Config),
  224. {ok, Binary} = file:read_file(Filename),
  225. {ok, {Server, Client}} = sockets(Config),
  226. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  227. Initial = 3,
  228. {ok, _} = file:position(RawFile, {bof, Initial}),
  229. Offset = 7,
  230. Bytes = 19,
  231. Ref = recv(Transport, Server, Bytes),
  232. {ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes,
  233. [{chunk_size, 16#FFFF}]),
  234. Binary2 = binary:part(Binary, Offset, Bytes),
  235. {ok, Binary2} = result(Ref),
  236. {error, timeout} = Transport:recv(Server, 1, 100),
  237. {ok, Initial} = file:position(RawFile, {cur, 0}),
  238. ok = file:close(RawFile),
  239. ok = Transport:close(Client),
  240. ok = Transport:close(Server).
  241. %% Check ssl obeys chunk_size.
  242. ssl_chunk_size(Config) ->
  243. Transport = ?config(transport, Config),
  244. Filename = ?config(filename, Config),
  245. {ok, Binary} = file:read_file(Filename),
  246. Size = byte_size(Binary),
  247. Self = self(),
  248. ChunkSize = 8 * 1024,
  249. Fun = fun() ->
  250. receive go -> ok after 1000 -> error(timeout) end,
  251. {ok, {Server, Client}} = sockets(Config),
  252. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  253. Ref = recv(Transport, Server, Size),
  254. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size,
  255. [{chunk_size, ChunkSize}]),
  256. {ok, Binary} = result(Ref),
  257. {error, timeout} = Transport:recv(Server, 1, 100),
  258. Self ! done,
  259. ok = file:close(RawFile),
  260. ok = Transport:close(Client),
  261. ok = Transport:close(Server)
  262. end,
  263. Pid = spawn_link(Fun),
  264. 1 = erlang:trace(Pid, true, [call]),
  265. _ = erlang:trace_pattern({Transport, send, 2}, true, [global]),
  266. Pid ! go,
  267. receive done -> ok after 30000 -> error(timeout) end,
  268. Sizes = lists:duplicate(Size div ChunkSize, ChunkSize) ++
  269. [Size rem ChunkSize || (Size rem ChunkSize) =/= 0],
  270. ok = recv_send_trace(Sizes, Pid),
  271. _ = erlang:trace(all, false, [all]),
  272. ok = clean_traces().
  273. sockets(Config) ->
  274. Transport = ?config(transport, Config),
  275. TransportOpts = ?config(transport_opts, Config),
  276. {ok, LSocket} = Transport:listen(TransportOpts),
  277. {ok, {_, Port}} = Transport:sockname(LSocket),
  278. Self = self(),
  279. Fun = fun() ->
  280. {ok, Client} = Transport:connect("localhost", Port, TransportOpts),
  281. ok = Transport:controlling_process(Client, Self),
  282. Self ! {ok, Client}
  283. end,
  284. _ = spawn_link(Fun),
  285. {ok, Server} = Transport:accept(LSocket, 500),
  286. ok = Transport:accept_ack(Server, 500),
  287. receive
  288. {ok, Client} ->
  289. ok = Transport:close(LSocket),
  290. {ok, {Server, Client}}
  291. after 1000 ->
  292. {error, timeout}
  293. end.
  294. recv(Transport, Server, Size) ->
  295. Self = self(),
  296. Ref = make_ref(),
  297. spawn_link(fun() -> Self ! {Ref, Transport:recv(Server, Size, 20000)} end),
  298. Ref.
  299. result(Ref) ->
  300. receive
  301. {Ref, Result} ->
  302. Result
  303. after
  304. 30000 ->
  305. {error, result_timedout}
  306. end.
  307. recv_send_trace([], _Pid) ->
  308. ok;
  309. recv_send_trace([Size | Rest], Pid) ->
  310. receive
  311. {trace, Pid, call, {_, _, [_, Chunk]}} when byte_size(Chunk) == Size ->
  312. recv_send_trace(Rest, Pid);
  313. {trace, Pid, call, {_, _, [_, Chunk]}} ->
  314. {error, {invalid_chunk, Size, byte_size(Chunk)}}
  315. after 1000 ->
  316. {error, timeout}
  317. end.
  318. clean_traces() ->
  319. receive
  320. {trace, _, _, _} ->
  321. clean_traces();
  322. {trace, _, _, _, _} ->
  323. clean_traces()
  324. after 0 ->
  325. ok
  326. end.