v / vlib / net / http / h2_frame_test.v
306 lines · 271 sloc · 7.88 KB · 57badade8f66da0ba48442bccf5773ffae8bbd2c
Raw
1module http
2
3// Tests for the HTTP/2 framing layer (RFC 7540 Sections 4 and 6).
4
5fn roundtrip(f H2Frame) !H2Frame {
6 encoded := f.encode()
7 decoded, consumed := h2_read_frame(encoded)!
8 assert consumed == encoded.len, 'consumed ${consumed} != encoded ${encoded.len}'
9 return decoded
10}
11
12// --- Frame header ---
13
14fn test_frame_header_layout() {
15 // DATA frame, length 5, flags END_STREAM, stream 1, payload "hello".
16 f := H2Frame(H2DataFrame{
17 stream_id: 1
18 data: 'hello'.bytes()
19 end_stream: true
20 })
21 enc := f.encode()
22 // 9-byte header: length(3) type(1) flags(1) stream_id(4)
23 assert enc[0] == 0 && enc[1] == 0 && enc[2] == 5 // length = 5
24 assert enc[3] == h2_frame_data
25 assert enc[4] == h2_flag_end_stream
26 assert enc[5] == 0 && enc[6] == 0 && enc[7] == 0 && enc[8] == 1 // stream 1
27 assert enc[9..] == 'hello'.bytes()
28
29 h := h2_parse_frame_header(enc)!
30 assert h.length == 5
31 assert h.typ == h2_frame_data
32 assert h.flags == h2_flag_end_stream
33 assert h.stream_id == 1
34}
35
36fn test_frame_header_clears_reserved_bit() {
37 // The reserved high bit of the stream id must be ignored on read.
38 raw := [u8(0), 0, 0, h2_frame_window_update, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 1]
39 h := h2_parse_frame_header(raw)!
40 assert h.stream_id == 0x7fff_ffff
41}
42
43// --- Round-trips for each frame type ---
44
45fn test_roundtrip_data() {
46 got := roundtrip(H2DataFrame{
47 stream_id: 3
48 data: 'abc'.bytes()
49 end_stream: true
50 })!
51 d := got as H2DataFrame
52 assert d.stream_id == 3
53 assert d.data == 'abc'.bytes()
54 assert d.end_stream
55}
56
57fn test_roundtrip_headers_plain() {
58 got := roundtrip(H2HeadersFrame{
59 stream_id: 1
60 fragment: [u8(0x82), 0x86, 0x84]
61 end_stream: true
62 end_headers: true
63 })!
64 h := got as H2HeadersFrame
65 assert h.stream_id == 1
66 assert h.fragment == [u8(0x82), 0x86, 0x84]
67 assert h.end_stream
68 assert h.end_headers
69 assert !h.has_priority
70}
71
72fn test_roundtrip_headers_with_priority() {
73 got := roundtrip(H2HeadersFrame{
74 stream_id: 5
75 fragment: [u8(0x88)]
76 end_headers: true
77 has_priority: true
78 exclusive: true
79 stream_dep: 3
80 weight: 201
81 })!
82 h := got as H2HeadersFrame
83 assert h.has_priority
84 assert h.exclusive
85 assert h.stream_dep == 3
86 assert h.weight == 201
87 assert h.fragment == [u8(0x88)]
88}
89
90fn test_roundtrip_priority() {
91 got := roundtrip(H2PriorityFrame{
92 stream_id: 7
93 exclusive: false
94 stream_dep: 1
95 weight: 16
96 })!
97 p := got as H2PriorityFrame
98 assert p.stream_id == 7
99 assert p.stream_dep == 1
100 assert p.weight == 16
101 assert !p.exclusive
102}
103
104fn test_roundtrip_rst_stream() {
105 got := roundtrip(H2RstStreamFrame{
106 stream_id: 9
107 error_code: u32(H2ErrorCode.cancel)
108 })!
109 r := got as H2RstStreamFrame
110 assert r.stream_id == 9
111 assert r.error_code == u32(H2ErrorCode.cancel)
112}
113
114fn test_roundtrip_settings() {
115 got := roundtrip(H2SettingsFrame{
116 settings: [
117 H2Setting{h2_settings_header_table_size, 4096},
118 H2Setting{h2_settings_enable_push, 0},
119 H2Setting{h2_settings_initial_window_size, 65535},
120 ]
121 })!
122 s := got as H2SettingsFrame
123 assert !s.ack
124 assert s.settings.len == 3
125 assert s.settings[0].id == h2_settings_header_table_size
126 assert s.settings[0].value == 4096
127 assert s.settings[1].id == h2_settings_enable_push
128 assert s.settings[1].value == 0
129 assert s.settings[2].value == 65535
130}
131
132fn test_roundtrip_settings_ack() {
133 got := roundtrip(H2SettingsFrame{
134 ack: true
135 })!
136 s := got as H2SettingsFrame
137 assert s.ack
138 assert s.settings.len == 0
139}
140
141fn test_roundtrip_push_promise() {
142 got := roundtrip(H2PushPromiseFrame{
143 stream_id: 1
144 promised_stream_id: 2
145 fragment: [u8(0x82), 0x84]
146 end_headers: true
147 })!
148 p := got as H2PushPromiseFrame
149 assert p.stream_id == 1
150 assert p.promised_stream_id == 2
151 assert p.fragment == [u8(0x82), 0x84]
152 assert p.end_headers
153}
154
155fn test_roundtrip_ping() {
156 got := roundtrip(H2PingFrame{
157 ack: true
158 data: [u8(1), 2, 3, 4, 5, 6, 7, 8]
159 })!
160 p := got as H2PingFrame
161 assert p.ack
162 assert p.data == [u8(1), 2, 3, 4, 5, 6, 7, 8]
163}
164
165fn test_roundtrip_goaway() {
166 got := roundtrip(H2GoawayFrame{
167 last_stream_id: 7
168 error_code: u32(H2ErrorCode.protocol_error)
169 debug_data: 'oops'.bytes()
170 })!
171 g := got as H2GoawayFrame
172 assert g.last_stream_id == 7
173 assert g.error_code == u32(H2ErrorCode.protocol_error)
174 assert g.debug_data == 'oops'.bytes()
175}
176
177fn test_roundtrip_window_update() {
178 got := roundtrip(H2WindowUpdateFrame{
179 stream_id: 0
180 window_size_increment: 65535
181 })!
182 w := got as H2WindowUpdateFrame
183 assert w.stream_id == 0
184 assert w.window_size_increment == 65535
185}
186
187fn test_roundtrip_continuation() {
188 got := roundtrip(H2ContinuationFrame{
189 stream_id: 3
190 fragment: [u8(0x40), 0x88]
191 end_headers: true
192 })!
193 c := got as H2ContinuationFrame
194 assert c.stream_id == 3
195 assert c.fragment == [u8(0x40), 0x88]
196 assert c.end_headers
197}
198
199// --- Padding ---
200
201fn test_data_padding_decode() {
202 // Hand-built padded DATA frame: pad length 3, data "hi", 3 pad bytes.
203 mut payload := [u8(3)] // pad length
204 payload << 'hi'.bytes()
205 payload << [u8(0), 0, 0] // padding
206 raw := h2_frame_bytes(h2_frame_data, h2_flag_padded, 1, payload)
207 frame, _ := h2_read_frame(raw)!
208 d := frame as H2DataFrame
209 assert d.data == 'hi'.bytes()
210}
211
212fn test_padding_length_exceeds_frame_rejected() {
213 // pad length 5 but only 2 bytes of payload after it -> error.
214 payload := [u8(5), 0x61, 0x62]
215 raw := h2_frame_bytes(h2_frame_data, h2_flag_padded, 1, payload)
216 h2_read_frame(raw) or { return }
217 assert false, 'expected pad-length error'
218}
219
220// --- Unknown frames ---
221
222fn test_unknown_frame_preserved() {
223 // Type 0x16 is not defined; it must be preserved, not rejected.
224 raw := h2_frame_bytes(0x16, 0x0, 0, [u8(0xde), 0xad, 0xbe, 0xef])
225 frame, consumed := h2_read_frame(raw)!
226 assert consumed == raw.len
227 u := frame as H2UnknownFrame
228 assert u.header.typ == 0x16
229 assert u.payload == [u8(0xde), 0xad, 0xbe, 0xef]
230 // And it round-trips back to the same bytes.
231 assert frame.encode() == raw
232}
233
234// --- Structural validation ---
235
236fn test_read_frame_truncated_payload() {
237 // Header claims 10 bytes of payload but none follow.
238 raw := [u8(0), 0, 10, h2_frame_data, 0, 0, 0, 0, 1]
239 h2_read_frame(raw) or { return }
240 assert false, 'expected truncated payload error'
241}
242
243fn test_settings_bad_length_rejected() {
244 // SETTINGS payload not a multiple of 6.
245 raw := h2_frame_bytes(h2_frame_settings, 0, 0, [u8(0), 1, 0, 0, 0])
246 h2_read_frame(raw) or { return }
247 assert false, 'expected SETTINGS length error'
248}
249
250fn test_settings_ack_with_payload_rejected() {
251 raw := h2_frame_bytes(h2_frame_settings, h2_flag_ack, 0, [u8(0), 1, 0, 0, 0x10, 0])
252 h2_read_frame(raw) or { return }
253 assert false, 'expected SETTINGS ACK payload error'
254}
255
256fn test_window_update_bad_length_rejected() {
257 raw := h2_frame_bytes(h2_frame_window_update, 0, 0, [u8(0), 0, 1])
258 h2_read_frame(raw) or { return }
259 assert false, 'expected WINDOW_UPDATE length error'
260}
261
262fn test_ping_bad_length_rejected() {
263 raw := h2_frame_bytes(h2_frame_ping, 0, 0, [u8(1), 2, 3, 4])
264 h2_read_frame(raw) or { return }
265 assert false, 'expected PING length error'
266}
267
268fn test_data_on_stream_zero_rejected() {
269 raw := h2_frame_bytes(h2_frame_data, 0, 0, 'x'.bytes())
270 h2_read_frame(raw) or { return }
271 assert false, 'expected DATA-on-stream-0 error'
272}
273
274fn test_settings_on_nonzero_stream_rejected() {
275 raw := h2_frame_bytes(h2_frame_settings, 0, 1, [])
276 h2_read_frame(raw) or { return }
277 assert false, 'expected SETTINGS-on-stream-1 error'
278}
279
280// --- Multiple frames back to back ---
281
282fn test_read_consecutive_frames() {
283 mut buf := []u8{}
284 buf << H2Frame(H2SettingsFrame{}).encode()
285 buf << H2Frame(H2DataFrame{
286 stream_id: 1
287 data: 'one'.bytes()
288 }).encode()
289 buf << H2Frame(H2PingFrame{
290 data: [u8(0), 0, 0, 0, 0, 0, 0, 9]
291 }).encode()
292
293 mut pos := 0
294 f1, c1 := h2_read_frame(buf[pos..])!
295 pos += c1
296 f2, c2 := h2_read_frame(buf[pos..])!
297 pos += c2
298 f3, c3 := h2_read_frame(buf[pos..])!
299 pos += c3
300 assert pos == buf.len
301 assert f1 is H2SettingsFrame
302 d := f2 as H2DataFrame
303 assert d.data == 'one'.bytes()
304 p := f3 as H2PingFrame
305 assert p.data[7] == 9
306}
307