sendfile_SUITE.erl 12 KB

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