1use crate::{Bytes, BytesMut, Http2ErrorCode, ProtoError, Result};
4
5pub const CLIENT_PREFACE: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
6pub const DEFAULT_INITIAL_WINDOW_SIZE: i64 = 65_535;
7pub const DEFAULT_MAX_FRAME_SIZE: u32 = 16_384;
8pub const MAX_FLOW_CONTROL_WINDOW: i64 = 0x7fff_ffff;
9const MAX_FRAME_SIZE: u32 = 0x00ff_ffff;
10const STREAM_MASK: u32 = 0x7fff_ffff;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum Http2FrameType {
16 Data = 0x0,
17 Headers = 0x1,
18 Priority = 0x2,
19 RstStream = 0x3,
20 Settings = 0x4,
21 PushPromise = 0x5,
22 Ping = 0x6,
23 GoAway = 0x7,
24 WindowUpdate = 0x8,
25 Continuation = 0x9,
26 Unknown(u8),
27}
28
29impl Http2FrameType {
30 fn from_byte(value: u8) -> Self {
31 match value {
32 0 => Self::Data,
33 1 => Self::Headers,
34 2 => Self::Priority,
35 3 => Self::RstStream,
36 4 => Self::Settings,
37 5 => Self::PushPromise,
38 6 => Self::Ping,
39 7 => Self::GoAway,
40 8 => Self::WindowUpdate,
41 9 => Self::Continuation,
42 value => Self::Unknown(value),
43 }
44 }
45
46 fn as_byte(self) -> u8 {
47 match self {
48 Self::Data => 0,
49 Self::Headers => 1,
50 Self::Priority => 2,
51 Self::RstStream => 3,
52 Self::Settings => 4,
53 Self::PushPromise => 5,
54 Self::Ping => 6,
55 Self::GoAway => 7,
56 Self::WindowUpdate => 8,
57 Self::Continuation => 9,
58 Self::Unknown(value) => value,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct Http2FrameHeader {
65 pub length: u32,
66 pub frame_type: Http2FrameType,
67 pub flags: u8,
68 pub stream_id: u32,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Http2Settings {
74 pub header_table_size: u32,
75 pub enable_push: bool,
76 pub max_concurrent_streams: u32,
77 pub initial_window_size: u32,
78 pub max_frame_size: u32,
79 pub max_header_list_size: u32,
80}
81
82impl Default for Http2Settings {
83 fn default() -> Self {
84 Self {
85 header_table_size: 4096,
86 enable_push: true,
87 max_concurrent_streams: u32::MAX,
88 initial_window_size: DEFAULT_INITIAL_WINDOW_SIZE as u32,
89 max_frame_size: DEFAULT_MAX_FRAME_SIZE,
90 max_header_list_size: u32::MAX,
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum Http2Frame {
99 Data {
100 stream_id: u32,
101 end_stream: bool,
102 data: Bytes,
103 },
104 Headers {
105 stream_id: u32,
106 end_stream: bool,
107 end_headers: bool,
108 header_block: Bytes,
109 },
110 Priority {
111 stream_id: u32,
112 exclusive: bool,
113 stream_dependency: u32,
114 weight: u8,
115 },
116 RstStream {
117 stream_id: u32,
118 error_code: Http2ErrorCode,
119 },
120 Settings {
121 ack: bool,
122 settings: Http2Settings,
123 },
124 Ping {
125 ack: bool,
126 opaque_data: [u8; 8],
127 },
128 GoAway {
129 last_stream_id: u32,
130 error_code: Http2ErrorCode,
131 debug_data: Bytes,
132 },
133 WindowUpdate {
134 stream_id: u32,
135 increment: u32,
136 },
137 Continuation {
138 stream_id: u32,
139 end_headers: bool,
140 header_block: Bytes,
141 },
142 Unknown {
143 header: Http2FrameHeader,
144 payload: Bytes,
145 },
146}
147
148pub fn encode_frame(frame: &Http2Frame, out: &mut BytesMut) -> Result<()> {
160 let header = encoded_header(frame)?;
161 validate_header(header, MAX_FRAME_SIZE)?;
162 match frame {
163 Http2Frame::Priority {
164 stream_id,
165 stream_dependency,
166 ..
167 } => validate_dependency(*stream_id, *stream_dependency)?,
168 Http2Frame::Settings {
169 ack: false,
170 settings,
171 } => validate_settings(settings)?,
172 Http2Frame::GoAway { last_stream_id, .. } if *last_stream_id > STREAM_MASK => {
173 return Err(protocol("last stream ID exceeds 31 bits"));
174 }
175 Http2Frame::WindowUpdate {
176 stream_id,
177 increment,
178 } => {
179 validate_increment(*stream_id, *increment)?;
180 }
181 Http2Frame::Unknown { payload, .. } => {
182 validate_raw_send(header, payload.as_ref())?;
184 decode_payload(header, payload.as_ref())?;
185 }
186 _ => {}
187 }
188 out.put_u24(header.length);
189 out.put_u8(header.frame_type.as_byte());
190 out.put_u8(header.flags);
191 out.put_u32(header.stream_id);
192 match frame {
193 Http2Frame::Data { data, .. } => out.extend_from_slice(data.as_ref()),
194 Http2Frame::Headers { header_block, .. }
195 | Http2Frame::Continuation { header_block, .. } => {
196 out.extend_from_slice(header_block.as_ref());
197 }
198 Http2Frame::Priority {
199 exclusive,
200 stream_dependency,
201 weight,
202 ..
203 } => {
204 out.put_u32(*stream_dependency | (u32::from(*exclusive) << 31));
205 out.put_u8(*weight);
206 }
207 Http2Frame::RstStream { error_code, .. } => out.put_u32(error_code.as_u32()),
208 Http2Frame::Settings {
209 ack: false,
210 settings,
211 } => {
212 for (id, value) in [
213 (1, settings.header_table_size),
214 (2, u32::from(settings.enable_push)),
215 (3, settings.max_concurrent_streams),
216 (4, settings.initial_window_size),
217 (5, settings.max_frame_size),
218 (6, settings.max_header_list_size),
219 ] {
220 out.put_u16(id);
221 out.put_u32(value);
222 }
223 }
224 Http2Frame::Settings { ack: true, .. } => {}
225 Http2Frame::Ping { opaque_data, .. } => out.extend_from_slice(opaque_data),
226 Http2Frame::GoAway {
227 last_stream_id,
228 error_code,
229 debug_data,
230 } => {
231 out.put_u32(*last_stream_id);
232 out.put_u32(error_code.as_u32());
233 out.extend_from_slice(debug_data.as_ref());
234 }
235 Http2Frame::WindowUpdate { increment, .. } => out.put_u32(*increment),
236 Http2Frame::Unknown { payload, .. } => out.extend_from_slice(payload.as_ref()),
237 }
238 Ok(())
239}
240
241pub fn decode_frame(src: &mut BytesMut, max_frame_size: u32) -> Result<Option<Http2Frame>> {
254 let bytes = src.as_slice();
255 if bytes.len() < 9 {
256 return Ok(None);
257 }
258 let header = Http2FrameHeader {
259 length: (u32::from(bytes[0]) << 16) | (u32::from(bytes[1]) << 8) | u32::from(bytes[2]),
260 frame_type: Http2FrameType::from_byte(bytes[3]),
261 flags: bytes[4],
262 stream_id: read_u32(&bytes[5..9]) & STREAM_MASK,
263 };
264 validate_header(header, max_frame_size.min(MAX_FRAME_SIZE))?;
265 let end = 9 + header.length as usize;
266 if bytes.len() < end {
267 return Ok(None);
268 }
269 let frame = decode_payload(header, &bytes[9..end])?;
270 src.advance(end);
271 Ok(Some(frame))
272}
273
274fn validate_raw_send(header: Http2FrameHeader, payload: &[u8]) -> Result<()> {
275 let allowed_flags = match header.frame_type {
276 Http2FrameType::Data => 0x09,
277 Http2FrameType::Headers => 0x2d,
278 Http2FrameType::Settings | Http2FrameType::Ping => 0x01,
279 Http2FrameType::PushPromise => 0x0c,
280 Http2FrameType::Continuation => 0x04,
281 Http2FrameType::Unknown(_) => 0xff,
282 _ => 0,
283 };
284 if header.flags & !allowed_flags != 0 {
285 return Err(protocol("unused flags must be unset when sending"));
286 }
287 if matches!(
288 header.frame_type,
289 Http2FrameType::GoAway | Http2FrameType::WindowUpdate
290 ) && read_u32(payload) & !STREAM_MASK != 0
291 {
292 return Err(protocol("reserved payload bits must be unset when sending"));
293 }
294 if header.frame_type == Http2FrameType::PushPromise {
295 let block = unpadded(payload, header.flags & 8 != 0)?;
296 if block.len() >= 4 && read_u32(block) & !STREAM_MASK != 0 {
297 return Err(protocol(
298 "reserved promised stream bit must be unset when sending",
299 ));
300 }
301 }
302 Ok(())
303}
304
305fn encoded_header(frame: &Http2Frame) -> Result<Http2FrameHeader> {
306 let (length, frame_type, flags, stream_id) = match frame {
307 Http2Frame::Data {
308 stream_id,
309 end_stream,
310 data,
311 } => (
312 data.len(),
313 Http2FrameType::Data,
314 u8::from(*end_stream),
315 *stream_id,
316 ),
317 Http2Frame::Headers {
318 stream_id,
319 end_stream,
320 end_headers,
321 header_block,
322 } => (
323 header_block.len(),
324 Http2FrameType::Headers,
325 u8::from(*end_stream) | (u8::from(*end_headers) << 2),
326 *stream_id,
327 ),
328 Http2Frame::Priority { stream_id, .. } => (5, Http2FrameType::Priority, 0, *stream_id),
329 Http2Frame::RstStream { stream_id, .. } => (4, Http2FrameType::RstStream, 0, *stream_id),
330 Http2Frame::Settings { ack, .. } => (
331 if *ack { 0 } else { 36 },
332 Http2FrameType::Settings,
333 u8::from(*ack),
334 0,
335 ),
336 Http2Frame::Ping { ack, .. } => (8, Http2FrameType::Ping, u8::from(*ack), 0),
337 Http2Frame::GoAway { debug_data, .. } => (
338 debug_data
339 .len()
340 .checked_add(8)
341 .ok_or_else(|| frame_size("GOAWAY too large"))?,
342 Http2FrameType::GoAway,
343 0,
344 0,
345 ),
346 Http2Frame::WindowUpdate { stream_id, .. } => {
347 (4, Http2FrameType::WindowUpdate, 0, *stream_id)
348 }
349 Http2Frame::Continuation {
350 stream_id,
351 end_headers,
352 header_block,
353 } => (
354 header_block.len(),
355 Http2FrameType::Continuation,
356 u8::from(*end_headers) << 2,
357 *stream_id,
358 ),
359 Http2Frame::Unknown { header, payload } => {
360 if header.length as usize != payload.len() {
361 return Err(frame_size("raw frame length differs from payload"));
362 }
363 (
364 payload.len(),
365 Http2FrameType::from_byte(header.frame_type.as_byte()),
366 header.flags,
367 header.stream_id,
368 )
369 }
370 };
371 if length > MAX_FRAME_SIZE as usize {
372 return Err(frame_size("payload exceeds the 24-bit frame size"));
373 }
374 if stream_id > STREAM_MASK {
375 return Err(protocol("stream ID exceeds 31 bits"));
376 }
377 Ok(Http2FrameHeader {
378 length: length as u32,
379 frame_type,
380 flags,
381 stream_id,
382 })
383}
384
385fn validate_header(header: Http2FrameHeader, max_frame_size: u32) -> Result<()> {
386 if header.length > max_frame_size {
387 return Err(frame_size("payload exceeds maximum frame size"));
388 }
389 match header.frame_type {
390 Http2FrameType::Settings | Http2FrameType::Ping | Http2FrameType::GoAway => {
391 if header.stream_id != 0 {
392 return Err(protocol("connection frame uses a nonzero stream ID"));
393 }
394 }
395 Http2FrameType::Data
396 | Http2FrameType::Headers
397 | Http2FrameType::Priority
398 | Http2FrameType::RstStream
399 | Http2FrameType::PushPromise
400 | Http2FrameType::Continuation => {
401 if header.stream_id == 0 {
402 return Err(protocol("stream frame uses stream ID zero"));
403 }
404 }
405 Http2FrameType::WindowUpdate | Http2FrameType::Unknown(_) => {}
406 }
407 let invalid_length = match header.frame_type {
408 Http2FrameType::Priority => header.length != 5,
409 Http2FrameType::RstStream | Http2FrameType::WindowUpdate => header.length != 4,
410 Http2FrameType::Settings => {
411 !header.length.is_multiple_of(6) || (header.flags & 1 != 0 && header.length != 0)
412 }
413 Http2FrameType::Ping => header.length != 8,
414 Http2FrameType::GoAway => header.length < 8,
415 Http2FrameType::PushPromise => header.length < 4 + u32::from(header.flags & 8 != 0),
416 Http2FrameType::Headers => {
417 header.length
418 < u32::from(header.flags & 8 != 0) + if header.flags & 0x20 != 0 { 5 } else { 0 }
419 }
420 Http2FrameType::Data => header.flags & 8 != 0 && header.length == 0,
421 _ => false,
422 };
423 if invalid_length {
424 if header.frame_type == Http2FrameType::Priority {
425 return Err(stream_error(
426 header.stream_id,
427 Http2ErrorCode::FrameSizeError,
428 "PRIORITY payload must be five octets",
429 ));
430 }
431 return Err(frame_size("invalid payload length for frame type"));
432 }
433 Ok(())
434}
435
436fn decode_payload(header: Http2FrameHeader, payload: &[u8]) -> Result<Http2Frame> {
437 let stream_id = header.stream_id;
438 let flag = |mask| header.flags & mask != 0;
439 Ok(match header.frame_type {
440 Http2FrameType::Data => Http2Frame::Data {
441 stream_id,
442 end_stream: flag(1),
443 data: Bytes::copy_from_slice(unpadded(payload, flag(8))?),
444 },
445 Http2FrameType::Headers => {
446 let mut block = unpadded(payload, flag(8))?;
447 if flag(0x20) {
448 if block.len() < 5 {
449 return Err(frame_size("truncated HEADERS priority fields"));
450 }
451 validate_dependency(stream_id, read_u32(&block[..4]) & STREAM_MASK)?;
452 block = &block[5..];
453 }
454 Http2Frame::Headers {
455 stream_id,
456 end_stream: flag(1),
457 end_headers: flag(4),
458 header_block: Bytes::copy_from_slice(block),
459 }
460 }
461 Http2FrameType::Priority => {
462 let dependency = read_u32(&payload[..4]);
463 validate_dependency(stream_id, dependency & STREAM_MASK)?;
464 Http2Frame::Priority {
465 stream_id,
466 exclusive: dependency & !STREAM_MASK != 0,
467 stream_dependency: dependency & STREAM_MASK,
468 weight: payload[4],
469 }
470 }
471 Http2FrameType::RstStream => Http2Frame::RstStream {
472 stream_id,
473 error_code: Http2ErrorCode::from_u32(read_u32(payload)),
474 },
475 Http2FrameType::Settings => {
476 let mut settings = Http2Settings::default();
477 for pair in payload.as_chunks::<6>().0 {
478 let id = u16::from_be_bytes([pair[0], pair[1]]);
479 let value = read_u32(&pair[2..]);
480 match id {
481 1 => settings.header_table_size = value,
482 2 => {
483 if value > 1 {
484 return Err(protocol("ENABLE_PUSH must be zero or one"));
485 }
486 settings.enable_push = value != 0;
487 }
488 3 => settings.max_concurrent_streams = value,
489 4 => settings.initial_window_size = value,
490 5 => settings.max_frame_size = value,
491 6 => settings.max_header_list_size = value,
492 _ => {}
493 }
494 validate_settings(&settings)?;
496 }
497 Http2Frame::Settings {
498 ack: flag(1),
499 settings,
500 }
501 }
502 Http2FrameType::PushPromise => {
503 let block = unpadded(payload, flag(8))?;
504 if block.len() < 4 {
505 return Err(frame_size("truncated promised stream ID"));
506 }
507 let promised_id = read_u32(&block[..4]) & STREAM_MASK;
508 if promised_id == 0 || !promised_id.is_multiple_of(2) {
509 return Err(protocol("promised stream ID must be nonzero and even"));
510 }
511 Http2Frame::Unknown {
512 header,
513 payload: Bytes::copy_from_slice(payload),
514 }
515 }
516 Http2FrameType::Ping => {
517 let mut opaque_data = [0; 8];
518 opaque_data.copy_from_slice(payload);
519 Http2Frame::Ping {
520 ack: flag(1),
521 opaque_data,
522 }
523 }
524 Http2FrameType::GoAway => Http2Frame::GoAway {
525 last_stream_id: read_u32(&payload[..4]) & STREAM_MASK,
526 error_code: Http2ErrorCode::from_u32(read_u32(&payload[4..8])),
527 debug_data: Bytes::copy_from_slice(&payload[8..]),
528 },
529 Http2FrameType::WindowUpdate => {
530 let increment = read_u32(payload) & STREAM_MASK;
531 validate_increment(stream_id, increment)?;
532 Http2Frame::WindowUpdate {
533 stream_id,
534 increment,
535 }
536 }
537 Http2FrameType::Continuation => Http2Frame::Continuation {
538 stream_id,
539 end_headers: flag(4),
540 header_block: Bytes::copy_from_slice(payload),
541 },
542 Http2FrameType::Unknown(_) => Http2Frame::Unknown {
543 header,
544 payload: Bytes::copy_from_slice(payload),
545 },
546 })
547}
548
549fn unpadded(payload: &[u8], padded: bool) -> Result<&[u8]> {
550 if !padded {
551 return Ok(payload);
552 }
553 let Some(&padding) = payload.first() else {
554 return Err(frame_size("missing padding length"));
555 };
556 if usize::from(padding) >= payload.len() {
557 return Err(protocol("padding exceeds frame payload"));
558 }
559 let end = payload.len() - usize::from(padding);
560 if payload[end..].iter().any(|&byte| byte != 0) {
561 return Err(protocol("padding octets must be zero"));
562 }
563 Ok(&payload[1..end])
564}
565
566fn validate_settings(settings: &Http2Settings) -> Result<()> {
567 if settings.initial_window_size > STREAM_MASK {
568 return Err(connection_error(
569 Http2ErrorCode::FlowControlError,
570 "INITIAL_WINDOW_SIZE exceeds the flow-control window limit",
571 ));
572 }
573 if !(DEFAULT_MAX_FRAME_SIZE..=MAX_FRAME_SIZE).contains(&settings.max_frame_size) {
574 return Err(protocol("MAX_FRAME_SIZE is outside its permitted range"));
575 }
576 Ok(())
577}
578
579fn validate_dependency(stream_id: u32, dependency: u32) -> Result<()> {
580 if dependency > STREAM_MASK {
581 return Err(protocol("stream dependency exceeds 31 bits"));
582 }
583 if stream_id == dependency {
584 return Err(stream_error(
585 stream_id,
586 Http2ErrorCode::ProtocolError,
587 "stream cannot depend on itself",
588 ));
589 }
590 Ok(())
591}
592
593fn validate_increment(stream_id: u32, increment: u32) -> Result<()> {
594 if increment == 0 || increment > STREAM_MASK {
595 if stream_id != 0 {
596 return Err(stream_error(
597 stream_id,
598 Http2ErrorCode::ProtocolError,
599 "WINDOW_UPDATE increment must be in 1..=2^31-1",
600 ));
601 }
602 return Err(protocol("WINDOW_UPDATE increment must be in 1..=2^31-1"));
603 }
604 Ok(())
605}
606
607fn read_u32(bytes: &[u8]) -> u32 {
608 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
609}
610
611fn connection_error(code: Http2ErrorCode, reason: &str) -> ProtoError {
612 ProtoError::Http2ConnectionError {
613 code,
614 reason: reason.to_owned(),
615 }
616}
617
618fn stream_error(stream_id: u32, code: Http2ErrorCode, reason: &str) -> ProtoError {
619 ProtoError::Http2StreamError {
620 stream_id,
621 code,
622 reason: reason.to_owned(),
623 }
624}
625
626fn protocol(reason: &str) -> ProtoError {
627 connection_error(Http2ErrorCode::ProtocolError, reason)
628}
629
630fn frame_size(reason: &str) -> ProtoError {
631 connection_error(Http2ErrorCode::FrameSizeError, reason)
632}
633
634#[cfg(test)]
635mod unit_tests {
636 use super::*;
637
638 #[test]
639 fn scenario_raw_send_flags_and_reserved_bits_preserve_output_on_error() -> Result<()> {
640 for (frame_type, flags, stream_id, payload) in [
641 (Http2FrameType::Data, 0x80, 1, &b"x"[..]),
642 (Http2FrameType::Ping, 0x80, 0, &[0; 8][..]),
643 (Http2FrameType::WindowUpdate, 0, 0, &[0x80, 0, 0, 1][..]),
644 (
645 Http2FrameType::GoAway,
646 0,
647 0,
648 &[0x80, 0, 0, 1, 0, 0, 0, 0][..],
649 ),
650 (Http2FrameType::PushPromise, 4, 1, &[0x80, 0, 0, 2][..]),
651 (
652 Http2FrameType::PushPromise,
653 0x0c,
654 1,
655 &[0, 0x80, 0, 0, 2][..],
656 ),
657 ] {
658 let header = Http2FrameHeader {
659 length: payload.len() as u32,
660 frame_type,
661 flags,
662 stream_id,
663 };
664 let frame = Http2Frame::Unknown {
665 header,
666 payload: Bytes::copy_from_slice(payload),
667 };
668 let mut output = BytesMut::new();
669 output.extend_from_slice(b"existing");
670 assert!(matches!(
671 encode_frame(&frame, &mut output),
672 Err(ProtoError::Http2ConnectionError {
673 code: Http2ErrorCode::ProtocolError,
674 ..
675 })
676 ));
677 assert_eq!(output.as_slice(), b"existing");
678
679 let mut wire = BytesMut::new();
681 wire.put_u24(header.length);
682 wire.put_u8(header.frame_type.as_byte());
683 wire.put_u8(header.flags);
684 wire.put_u32(header.stream_id);
685 wire.extend_from_slice(payload);
686 assert!(decode_frame(&mut wire, DEFAULT_MAX_FRAME_SIZE)?.is_some());
687 assert!(wire.is_empty());
688 }
689 Ok(())
690 }
691}