draft_h2_websockets_SUITE.erl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. %% Copyright (c) 2018, Loïc Hoguin <essen@ninenines.eu>
  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(draft_h2_websockets_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. all() -> [{group, enabled}].
  20. groups() ->
  21. Tests = ct_helper:all(?MODULE),
  22. [{enabled, [parallel], Tests}].
  23. init_per_group(Name = enabled, Config) ->
  24. cowboy_test:init_http(Name, #{
  25. enable_connect_protocol => true,
  26. env => #{dispatch => cowboy_router:compile(init_routes(Config))}
  27. }, Config).
  28. end_per_group(Name, _) ->
  29. ok = cowboy:stop_listener(Name).
  30. init_routes(_) -> [
  31. {"localhost", [
  32. {"/ws", ws_echo, []}
  33. ]}
  34. ].
  35. %% Do a prior knowledge handshake.
  36. do_handshake(Config) ->
  37. {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
  38. %% Send a valid preface.
  39. ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
  40. %% Receive the server preface.
  41. {ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
  42. {ok, << 4:8, 0:40, SettingsPayload:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
  43. Settings = cow_http2:parse_settings_payload(SettingsPayload),
  44. %% Send the SETTINGS ack.
  45. ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
  46. %% Receive the SETTINGS ack.
  47. {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
  48. {ok, Socket, Settings}.
  49. % The SETTINGS_ENABLE_CONNECT_PROTOCOL SETTINGS Parameter.
  50. % The new parameter name is SETTINGS_ENABLE_CONNECT_PROTOCOL. The
  51. % value of the parameter MUST be 0 or 1.
  52. % Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1 a
  53. % client MAY use the Extended CONNECT definition of this document when
  54. % creating new streams. Receipt of this parameter by a server does not
  55. % have any impact.
  56. %% @todo ignore_client_enable_setting(Config) ->
  57. % A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter
  58. % with the value of 0 after previously sending a value of 1.
  59. reject_handshake_when_disabled(Config0) ->
  60. doc("Extended CONNECT requests MUST be rejected with a "
  61. "PROTOCOL_ERROR stream error when enable_connect_protocol=false. (draft-01 3)"),
  62. Config = cowboy_test:init_http(disabled, #{
  63. enable_connect_protocol => false,
  64. env => #{dispatch => cowboy_router:compile(init_routes(Config0))}
  65. }, Config0),
  66. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.
  67. {ok, Socket, Settings} = do_handshake(Config),
  68. #{enable_connect_protocol := false} = Settings,
  69. %% Send a CONNECT :protocol request to upgrade the stream to Websocket.
  70. {ReqHeadersBlock, _} = cow_hpack:encode([
  71. {<<":method">>, <<"CONNECT">>},
  72. {<<":protocol">>, <<"websocket">>},
  73. {<<":scheme">>, <<"http">>},
  74. {<<":path">>, <<"/ws">>},
  75. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  76. {<<"sec-websocket-version">>, <<"13">>},
  77. {<<"origin">>, <<"http://localhost">>}
  78. ]),
  79. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  80. %% Receive a PROTOCOL_ERROR stream error.
  81. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  82. ok.
  83. reject_handshake_disabled_by_default(Config0) ->
  84. doc("Extended CONNECT requests MUST be rejected with a "
  85. "PROTOCOL_ERROR stream error with default enable_connect_protocol. (draft-01 3)"),
  86. Config = cowboy_test:init_http(disabled_by_default, #{
  87. env => #{dispatch => cowboy_router:compile(init_routes(Config0))}
  88. }, Config0),
  89. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.
  90. {ok, Socket, Settings} = do_handshake(Config),
  91. #{enable_connect_protocol := false} = Settings,
  92. %% Send a CONNECT :protocol request to upgrade the stream to Websocket.
  93. {ReqHeadersBlock, _} = cow_hpack:encode([
  94. {<<":method">>, <<"CONNECT">>},
  95. {<<":protocol">>, <<"websocket">>},
  96. {<<":scheme">>, <<"http">>},
  97. {<<":path">>, <<"/ws">>},
  98. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  99. {<<"sec-websocket-version">>, <<"13">>},
  100. {<<"origin">>, <<"http://localhost">>}
  101. ]),
  102. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  103. %% Receive a PROTOCOL_ERROR stream error.
  104. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  105. ok.
  106. % The Extended CONNECT Method.
  107. accept_uppercase_pseudo_header_protocol(Config) ->
  108. doc("The :protocol pseudo header is case insensitive. (draft-01 4)"),
  109. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  110. {ok, Socket, Settings} = do_handshake(Config),
  111. #{enable_connect_protocol := true} = Settings,
  112. %% Send a CONNECT :protocol request to upgrade the stream to Websocket.
  113. {ReqHeadersBlock, _} = cow_hpack:encode([
  114. {<<":method">>, <<"CONNECT">>},
  115. {<<":protocol">>, <<"WEBSOCKET">>},
  116. {<<":scheme">>, <<"http">>},
  117. {<<":path">>, <<"/ws">>},
  118. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  119. {<<"sec-websocket-version">>, <<"13">>},
  120. {<<"origin">>, <<"http://localhost">>}
  121. ]),
  122. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  123. %% Receive a 200 response.
  124. {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
  125. {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),
  126. {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),
  127. {_, <<"200">>} = lists:keyfind(<<":status">>, 1, RespHeaders),
  128. ok.
  129. reject_many_pseudo_header_protocol(Config) ->
  130. doc("An extended CONNECT request containing more than one protocol component "
  131. "must be rejected with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)"),
  132. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  133. {ok, Socket, Settings} = do_handshake(Config),
  134. #{enable_connect_protocol := true} = Settings,
  135. %% Send an extended CONNECT request with more than one :protocol pseudo-header.
  136. {ReqHeadersBlock, _} = cow_hpack:encode([
  137. {<<":method">>, <<"CONNECT">>},
  138. {<<":protocol">>, <<"websocket">>},
  139. {<<":protocol">>, <<"mqtt">>},
  140. {<<":scheme">>, <<"http">>},
  141. {<<":path">>, <<"/ws">>},
  142. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  143. {<<"sec-websocket-version">>, <<"13">>},
  144. {<<"origin">>, <<"http://localhost">>}
  145. ]),
  146. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  147. %% Receive a PROTOCOL_ERROR stream error.
  148. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  149. ok.
  150. reject_unknown_pseudo_header_protocol(Config) ->
  151. doc("An extended CONNECT request with an unknown protocol must be rejected "
  152. "with a 400 error. (draft-01 4)"),
  153. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  154. {ok, Socket, Settings} = do_handshake(Config),
  155. #{enable_connect_protocol := true} = Settings,
  156. %% Send an extended CONNECT request with an unknown :protocol pseudo-header.
  157. {ReqHeadersBlock, _} = cow_hpack:encode([
  158. {<<":method">>, <<"CONNECT">>},
  159. {<<":protocol">>, <<"mqtt">>},
  160. {<<":scheme">>, <<"http">>},
  161. {<<":path">>, <<"/ws">>},
  162. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  163. {<<"sec-websocket-version">>, <<"13">>},
  164. {<<"origin">>, <<"http://localhost">>}
  165. ]),
  166. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  167. %% Receive a 400 response.
  168. {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
  169. {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),
  170. {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),
  171. {_, <<"400">>} = lists:keyfind(<<":status">>, 1, RespHeaders),
  172. ok.
  173. reject_invalid_pseudo_header_protocol(Config) ->
  174. doc("An extended CONNECT request with an invalid protocol must be rejected "
  175. "with a 400 error. (draft-01 4)"),
  176. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  177. {ok, Socket, Settings} = do_handshake(Config),
  178. #{enable_connect_protocol := true} = Settings,
  179. %% Send an extended CONNECT request with an invalid :protocol pseudo-header.
  180. {ReqHeadersBlock, _} = cow_hpack:encode([
  181. {<<":method">>, <<"CONNECT">>},
  182. {<<":protocol">>, <<"websocket mqtt">>},
  183. {<<":scheme">>, <<"http">>},
  184. {<<":path">>, <<"/ws">>},
  185. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  186. {<<"sec-websocket-version">>, <<"13">>},
  187. {<<"origin">>, <<"http://localhost">>}
  188. ]),
  189. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  190. %% Receive a 400 response.
  191. {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
  192. {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),
  193. {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),
  194. {_, <<"400">>} = lists:keyfind(<<":status">>, 1, RespHeaders),
  195. ok.
  196. reject_missing_pseudo_header_scheme(Config) ->
  197. doc("An extended CONNECT request without a scheme component must be rejected "
  198. "with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)"),
  199. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  200. {ok, Socket, Settings} = do_handshake(Config),
  201. #{enable_connect_protocol := true} = Settings,
  202. %% Send an extended CONNECT request without a :scheme pseudo-header.
  203. {ReqHeadersBlock, _} = cow_hpack:encode([
  204. {<<":method">>, <<"CONNECT">>},
  205. {<<":protocol">>, <<"websocket">>},
  206. {<<":path">>, <<"/ws">>},
  207. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  208. {<<"sec-websocket-version">>, <<"13">>},
  209. {<<"origin">>, <<"http://localhost">>}
  210. ]),
  211. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  212. %% Receive a PROTOCOL_ERROR stream error.
  213. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  214. ok.
  215. reject_missing_pseudo_header_path(Config) ->
  216. doc("An extended CONNECT request without a path component must be rejected "
  217. "with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)"),
  218. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  219. {ok, Socket, Settings} = do_handshake(Config),
  220. #{enable_connect_protocol := true} = Settings,
  221. %% Send an extended CONNECT request without a :path pseudo-header.
  222. {ReqHeadersBlock, _} = cow_hpack:encode([
  223. {<<":method">>, <<"CONNECT">>},
  224. {<<":protocol">>, <<"websocket">>},
  225. {<<":scheme">>, <<"http">>},
  226. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  227. {<<"sec-websocket-version">>, <<"13">>},
  228. {<<"origin">>, <<"http://localhost">>}
  229. ]),
  230. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  231. %% Receive a PROTOCOL_ERROR stream error.
  232. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  233. ok.
  234. % On requests bearing the :protocol pseudo-header, the :authority
  235. % pseudo-header field is interpreted according to Section 8.1.2.3 of
  236. % [RFC7540] instead of Section 8.3 of [RFC7540]. In particular the
  237. % server MUST not make a new TCP connection to the host and port
  238. % indicated by the :authority.
  239. reject_missing_pseudo_header_authority(Config) ->
  240. doc("An extended CONNECT request without an authority component must be rejected "
  241. "with a PROTOCOL_ERROR stream error. (draft-01 4, draft-01 5)"),
  242. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  243. {ok, Socket, Settings} = do_handshake(Config),
  244. #{enable_connect_protocol := true} = Settings,
  245. %% Send an extended CONNECT request without an :authority pseudo-header.
  246. {ReqHeadersBlock, _} = cow_hpack:encode([
  247. {<<":method">>, <<"CONNECT">>},
  248. {<<":protocol">>, <<"websocket">>},
  249. {<<":scheme">>, <<"http">>},
  250. {<<":path">>, <<"/ws">>},
  251. {<<"sec-websocket-version">>, <<"13">>},
  252. {<<"origin">>, <<"http://localhost">>}
  253. ]),
  254. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  255. %% Receive a PROTOCOL_ERROR stream error.
  256. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  257. ok.
  258. % Using Extended CONNECT To Bootstrap The WebSocket Protocol.
  259. reject_missing_pseudo_header_protocol(Config) ->
  260. doc("An extended CONNECT request without a protocol component must be rejected "
  261. "with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)"),
  262. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  263. {ok, Socket, Settings} = do_handshake(Config),
  264. #{enable_connect_protocol := true} = Settings,
  265. %% Send an extended CONNECT request without a :scheme pseudo-header.
  266. {ReqHeadersBlock, _} = cow_hpack:encode([
  267. {<<":method">>, <<"CONNECT">>},
  268. {<<":scheme">>, <<"http">>},
  269. {<<":path">>, <<"/ws">>},
  270. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  271. {<<"sec-websocket-version">>, <<"13">>},
  272. {<<"origin">>, <<"http://localhost">>}
  273. ]),
  274. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  275. %% Receive a PROTOCOL_ERROR stream error.
  276. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  277. ok.
  278. % The scheme of the Target URI [RFC7230] MUST be https for wss schemed
  279. % WebSockets and http for ws schemed WebSockets. The websocket URI is
  280. % still used for proxy autoconfiguration.
  281. reject_connection_header(Config) ->
  282. doc("An extended CONNECT request with a connection header must be rejected "
  283. "with a PROTOCOL_ERROR stream error. (draft-01 5, RFC7540 8.1.2.6)"),
  284. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  285. {ok, Socket, Settings} = do_handshake(Config),
  286. #{enable_connect_protocol := true} = Settings,
  287. %% Send an extended CONNECT request without a :scheme pseudo-header.
  288. {ReqHeadersBlock, _} = cow_hpack:encode([
  289. {<<":method">>, <<"CONNECT">>},
  290. {<<":protocol">>, <<"websocket">>},
  291. {<<":scheme">>, <<"http">>},
  292. {<<":path">>, <<"/ws">>},
  293. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  294. {<<"connection">>, <<"upgrade">>},
  295. {<<"sec-websocket-version">>, <<"13">>},
  296. {<<"origin">>, <<"http://localhost">>}
  297. ]),
  298. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  299. %% Receive a PROTOCOL_ERROR stream error.
  300. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  301. ok.
  302. reject_upgrade_header(Config) ->
  303. doc("An extended CONNECT request with a upgrade header must be rejected "
  304. "with a PROTOCOL_ERROR stream error. (draft-01 5, RFC7540 8.1.2.6)"),
  305. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  306. {ok, Socket, Settings} = do_handshake(Config),
  307. #{enable_connect_protocol := true} = Settings,
  308. %% Send an extended CONNECT request without a :scheme pseudo-header.
  309. {ReqHeadersBlock, _} = cow_hpack:encode([
  310. {<<":method">>, <<"CONNECT">>},
  311. {<<":protocol">>, <<"websocket">>},
  312. {<<":scheme">>, <<"http">>},
  313. {<<":path">>, <<"/ws">>},
  314. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  315. {<<"upgrade">>, <<"websocket">>},
  316. {<<"sec-websocket-version">>, <<"13">>},
  317. {<<"origin">>, <<"http://localhost">>}
  318. ]),
  319. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  320. %% Receive a PROTOCOL_ERROR stream error.
  321. {ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
  322. ok.
  323. % After successfully processing the opening handshake the peers should
  324. % proceed with The WebSocket Protocol [RFC6455] using the HTTP/2 stream
  325. % from the CONNECT transaction as if it were the TCP connection
  326. % referred to in [RFC6455]. The state of the WebSocket connection at
  327. % this point is OPEN as defined by [RFC6455], Section 4.1.
  328. %% @todo I'm guessing we should test for things like RST_STREAM,
  329. %% closing the connection and others?
  330. % Examples.
  331. %% @todo Probably worth testing that we get the correct option
  332. %% over all different connection types (alpn, prior, upgrade).
  333. accept_handshake_when_enabled(Config) ->
  334. doc("Confirm the example for Websocket over HTTP/2 works. (draft-01 5.1)"),
  335. %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.
  336. {ok, Socket, Settings} = do_handshake(Config),
  337. #{enable_connect_protocol := true} = Settings,
  338. %% Send a CONNECT :protocol request to upgrade the stream to Websocket.
  339. {ReqHeadersBlock, _} = cow_hpack:encode([
  340. {<<":method">>, <<"CONNECT">>},
  341. {<<":protocol">>, <<"websocket">>},
  342. {<<":scheme">>, <<"http">>},
  343. {<<":path">>, <<"/ws">>},
  344. {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
  345. {<<"sec-websocket-version">>, <<"13">>},
  346. {<<"origin">>, <<"http://localhost">>}
  347. ]),
  348. ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),
  349. %% Receive a 200 response.
  350. {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
  351. {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),
  352. {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),
  353. {_, <<"200">>} = lists:keyfind(<<":status">>, 1, RespHeaders),
  354. %% Masked text hello echoed back clear by the server.
  355. %%
  356. %% We receive WINDOW_UPDATE frames before the actual data
  357. %% due to flow control updates every time a data frame is received.
  358. Mask = 16#37fa213d,
  359. MaskedHello = ws_SUITE:do_mask(<<"Hello">>, Mask, <<>>),
  360. ok = gen_tcp:send(Socket, cow_http2:data(1, nofin,
  361. <<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary>>)),
  362. {ok, <<4:24, 8:8, _:72>>} = gen_tcp:recv(Socket, 13, 1000),
  363. {ok, <<4:24, 8:8, _:72>>} = gen_tcp:recv(Socket, 13, 1000),
  364. {ok, <<Len2:24, _:8, _:8, _:32>>} = gen_tcp:recv(Socket, 9, 1000),
  365. {ok, <<1:1, 0:3, 1:4, 0:1, 5:7, "Hello">>} = gen_tcp:recv(Socket, Len2, 1000),
  366. ok.
  367. %% Closing a Websocket stream.
  368. %% @todo client close frame with END_STREAM
  369. %% @todo server close frame with END_STREAM
  370. %% @todo client other frame with END_STREAM
  371. %% @todo server other frame with END_STREAM
  372. %% @todo client close connection