| // Copyright (C) 2019, Cloudflare, Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimer. |
| // |
| // * Redistributions in binary form must reproduce the above copyright |
| // notice, this list of conditions and the following disclaimer in the |
| // documentation and/or other materials provided with the distribution. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| //! HTTP/3 wire protocol and QPACK implementation. |
| //! |
| //! This module provides a high level API for sending and receiving HTTP/3 |
| //! requests and responses on top of the QUIC transport protocol. |
| //! |
| //! ## Connection setup |
| //! |
| //! HTTP/3 connections require a QUIC transport-layer connection, see |
| //! [Connection setup] for a full description of the setup process. |
| //! |
| //! To use HTTP/3, the QUIC connection must be configured with a suitable |
| //! Application Layer Protocol Negotiation (ALPN) Protocol ID: |
| //! |
| //! ``` |
| //! let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! ``` |
| //! |
| //! The QUIC handshake is driven by [sending] and [receiving] QUIC packets. |
| //! Once the handshake has completed, the application should check that HTTP/3 |
| //! was negotiated using the [`application_proto()`] method: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let server_name = "quic.tech"; |
| //! # let scid = [0xba; 16]; |
| //! # let conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap(); |
| //! if conn.is_established() && conn.application_proto() == b"h3-18" { |
| //! // Handshake completed and HTTP/3 negotiated. |
| //! } |
| //! ``` |
| //! |
| //! The first step in establishing an HTTP/3 connection is creating its |
| //! configuration object: |
| //! |
| //! ``` |
| //! let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! ``` |
| //! |
| //! HTTP/3 client and server connections are both created using the |
| //! [`with_transport()`] function, the role is inferred from the type of QUIC |
| //! connection: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let server_name = "quic.tech"; |
| //! # let scid = [0xba; 16]; |
| //! # let mut conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap(); |
| //! # let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! let h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config).unwrap(); |
| //! ``` |
| //! |
| //! ## Sending a request |
| //! |
| //! An HTTP/3 client can send a request by using the connection's |
| //! [`send_request()`] method to queue request headers; [sending] QUIC packets |
| //! causes the requests to get sent to the peer: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let server_name = "quic.tech"; |
| //! # let scid = [0xba; 16]; |
| //! # let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! # let mut conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap(); |
| //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config).unwrap(); |
| //! let req = vec![ |
| //! quiche::h3::Header::new(":method", "GET"), |
| //! quiche::h3::Header::new(":scheme", "https"), |
| //! quiche::h3::Header::new(":authority", "quic.tech"), |
| //! quiche::h3::Header::new(":path", "/"), |
| //! quiche::h3::Header::new(":user-agent", "quiche"), |
| //! ]; |
| //! |
| //! h3_conn.send_request(&mut conn, &req, true).unwrap(); |
| //! ``` |
| //! |
| //! An HTTP/3 client can send a request with additional body data by using |
| //! the connection's [`send_body()`] method: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let server_name = "quic.tech"; |
| //! # let scid = [0xba; 16]; |
| //! # let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! # let mut conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap(); |
| //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config).unwrap(); |
| //! let req = vec![ |
| //! quiche::h3::Header::new(":method", "GET"), |
| //! quiche::h3::Header::new(":scheme", "https"), |
| //! quiche::h3::Header::new(":authority", "quic.tech"), |
| //! quiche::h3::Header::new(":path", "/"), |
| //! quiche::h3::Header::new(":user-agent", "quiche"), |
| //! ]; |
| //! |
| //! let stream_id = h3_conn.send_request(&mut conn, &req, false).unwrap(); |
| //! h3_conn.send_body(&mut conn, stream_id, b"Hello World!", true).unwrap(); |
| //! ``` |
| //! |
| //! ## Handling requests and responses |
| //! |
| //! After [receiving] QUIC packets, HTTP/3 data is processed using the |
| //! connection's [`poll()`] method. On success, this returns an [`Event`] object |
| //! and an ID corresponding to the stream where the `Event` originated. |
| //! |
| //! An HTTP/3 server uses [`poll()`] to read requests and responds to them using |
| //! [`send_response()`] and [`send_body()`]: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let scid = [0xba; 16]; |
| //! # let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! # let mut conn = quiche::accept(&scid, None, &mut config).unwrap(); |
| //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, |
| //! # &h3_config).unwrap(); |
| //! loop { |
| //! match h3_conn.poll(&mut conn) { |
| //! Ok((stream_id, quiche::h3::Event::Headers(headers))) => { |
| //! let mut headers = headers.into_iter(); |
| //! |
| //! // Look for the request's method. |
| //! let method = headers.find(|h| h.name() == ":method").unwrap(); |
| //! |
| //! // Look for the request's path. |
| //! let path = headers.find(|h| h.name() == ":path").unwrap(); |
| //! |
| //! if method.value() == "GET" && path.value() == "/" { |
| //! let resp = vec![ |
| //! quiche::h3::Header::new(":status", &200.to_string()), |
| //! quiche::h3::Header::new("server", "quiche"), |
| //! ]; |
| //! |
| //! h3_conn.send_response(&mut conn, stream_id, &resp, false).unwrap(); |
| //! h3_conn.send_body(&mut conn, stream_id, b"Hello World!", true).unwrap(); |
| //! } |
| //! }, |
| //! |
| //! Ok((stream_id, quiche::h3::Event::Data(data))) => { |
| //! // Request body data, handle it. |
| //! # return; |
| //! }, |
| //! |
| //! Err(quiche::h3::Error::Done) => { |
| //! // Done reading. |
| //! break; |
| //! }, |
| //! |
| //! Err(e) => { |
| //! // An error occurred, handle it. |
| //! break; |
| //! }, |
| //! } |
| //! } |
| //! ``` |
| //! |
| //! An HTTP/3 client uses [`poll()`] to read responses: |
| //! |
| //! ```no_run |
| //! # let mut config = quiche::Config::new(quiche::VERSION_DRAFT19).unwrap(); |
| //! # config.set_application_protos(b"\x05h3-18").unwrap(); |
| //! # let server_name = "quic.tech"; |
| //! # let scid = [0xba; 16]; |
| //! # let h3_config = quiche::h3::Config::new(0, 1024, 0, 0).unwrap(); |
| //! # let mut conn = quiche::connect(Some(&server_name), &scid, &mut config).unwrap(); |
| //! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, |
| //! # &h3_config).unwrap(); |
| //! loop { |
| //! match h3_conn.poll(&mut conn) { |
| //! Ok((stream_id, quiche::h3::Event::Headers(headers))) => { |
| //! let status = headers.iter().find(|h| h.name() == ":status").unwrap(); |
| //! println!("Received {} response on stream {}", |
| //! status.value(), stream_id); |
| //! }, |
| //! |
| //! Ok((stream_id, quiche::h3::Event::Data(data))) => { |
| //! println!("Received {} bytes of payload on stream {}", |
| //! data.len(), stream_id); |
| //! }, |
| //! |
| //! Err(quiche::h3::Error::Done) => { |
| //! // Done reading. |
| //! break; |
| //! }, |
| //! |
| //! Err(e) => { |
| //! // An error occurred, handle it. |
| //! break; |
| //! }, |
| //! } |
| //! } |
| //! ``` |
| //! |
| //! ## Detecting end of stream |
| //! |
| //! HTTP/3 request and response exchanges may consist of several HEADERS and |
| //! DATA frames. Calling [`poll()`] repeatedly will generate an [`Event`] for |
| //! each. The QUIC connection's [`stream_finished()`] method can be used to |
| //! detect if the stream was ended by the peer. Additional HTTP/3 validation |
| //! can be applied by the application to ensure protocol correctness. |
| //! |
| //! [`application_proto()`]: ../struct.Connection.html#method.application_proto |
| //! [`stream_finished()`]: ../struct.Connection.html#method.stream_finished |
| //! [Connection setup]: ../index.html#connection-setup |
| //! [sending]: ../index.html#generating-outgoing-packets |
| //! [receiving]: ../index.html#handling-incoming-packets |
| //! [`with_transport()`]: struct.Connection.html#method.with_transport |
| //! [`poll()`]: struct.Connection.html#method.poll |
| //! [`Event`]: enum.Event.html |
| //! [`send_request()`]: struct.Connection.html#method.send_response |
| //! [`send_response()`]: struct.Connection.html#method.send_response |
| //! [`send_body()`]: struct.Connection.html#method.send_body |
| |
| use std::collections::BTreeMap; |
| |
| use crate::octets; |
| |
| /// A specialized [`Result`] type for quiche HTTP/3 operations. |
| /// |
| /// This type is used throughout quiche's HTTP/3 public API for any operation |
| /// that can produce an error. |
| /// |
| /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html |
| pub type Result<T> = std::result::Result<T, Error>; |
| |
| /// An HTTP/3 error. |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| #[repr(C)] |
| pub enum Error { |
| /// There is no error or no work to do |
| Done = -1, |
| |
| /// The provided buffer is too short. |
| BufferTooShort = -2, |
| |
| /// Setting sent in wrong direction. |
| WrongSettingDirection = -3, |
| |
| /// The server attempted to push content that the client will not accept. |
| PushRefused = -4, |
| |
| /// Internal error in the HTTP/3 stack. |
| InternalError = -5, |
| |
| /// The server attempted to push something the client already has. |
| PushAlreadyInCache = -6, |
| |
| /// The client no longer needs the requested data. |
| RequestCancelled = -7, |
| |
| /// The request stream terminated before completing the request. |
| IncompleteRequest = -8, |
| |
| /// Forward connection failure for CONNECT target. |
| ConnectError = -9, |
| |
| /// Endpoint detected that the peer is exhibiting behavior that causes. |
| /// excessive load. |
| ExcessiveLoad = -10, |
| |
| /// Operation cannot be served over HTTP/3. Retry over HTTP/1.1. |
| VersionFallback = -11, |
| |
| /// Frame received on stream where it is not permitted. |
| WrongStream = -12, |
| |
| /// Stream ID, Push ID or Placeholder Id greater that current maximum was. |
| /// used |
| LimitExceeded = -13, |
| |
| /// Push ID used in two different stream headers. |
| DuplicatePush = -14, |
| |
| /// Unknown unidirection stream type. |
| UnknownStreamType = -15, |
| |
| /// Too many unidirectional streams of a type were created. |
| WrongStreamCount = -16, |
| |
| /// A required critical stream was closed. |
| ClosedCriticalStream = -17, |
| |
| /// Unidirectional stream type opened at peer that is prohibited. |
| WrongStreamDirection = -18, |
| |
| /// Inform client that remainder of request is not needed. Used in |
| /// STOP_SENDING only. |
| EarlyResponse = -19, |
| |
| /// No SETTINGS frame at beginning of control stream. |
| MissingSettings = -20, |
| |
| /// A frame was received which is not permitted in the current state. |
| UnexpectedFrame = -21, |
| |
| /// Server rejected request without performing any application processing. |
| RequestRejected = -22, |
| |
| /// Peer violated protocol requirements in a way that doesn't match a more |
| /// specific code. |
| GeneralProtocolError = -23, |
| |
| /// TODO: malformed frame where last on-wire byte is the frame type. |
| MalformedFrame = -24, |
| |
| /// QPACK Header block decompression failure. |
| QpackDecompressionFailed = -25, |
| |
| /// QPACK encoder stream error. |
| QpackEncoderStreamError = -26, |
| |
| /// QPACK decoder stream error. |
| QpackDecoderStreamError = -27, |
| } |
| |
| impl Error { |
| pub fn to_wire(self) -> u16 { |
| match self { |
| Error::Done => 0x0, |
| Error::WrongSettingDirection => 0x1, |
| Error::PushRefused => 0x2, |
| Error::InternalError => 0x3, |
| Error::PushAlreadyInCache => 0x4, |
| Error::RequestCancelled => 0x5, |
| Error::IncompleteRequest => 0x6, |
| Error::ConnectError => 0x07, |
| Error::ExcessiveLoad => 0x08, |
| Error::VersionFallback => 0x09, |
| Error::WrongStream => 0xA, |
| Error::LimitExceeded => 0xB, |
| Error::DuplicatePush => 0xC, |
| Error::UnknownStreamType => 0xD, |
| Error::WrongStreamCount => 0xE, |
| Error::ClosedCriticalStream => 0xF, |
| Error::WrongStreamDirection => 0x10, |
| Error::EarlyResponse => 0x11, |
| Error::MissingSettings => 0x12, |
| Error::UnexpectedFrame => 0x13, |
| Error::RequestRejected => 0x14, |
| Error::GeneralProtocolError => 0xFF, |
| Error::MalformedFrame => 0x10, |
| |
| Error::QpackDecompressionFailed => 0x20, // TODO: value is TBD |
| Error::QpackEncoderStreamError => 0x21, // TODO: value is TBD |
| Error::QpackDecoderStreamError => 0x22, // TODO: value is TBD |
| Error::BufferTooShort => 0x999, |
| } |
| } |
| |
| fn to_c(self) -> libc::ssize_t { |
| self as _ |
| } |
| } |
| |
| impl std::fmt::Display for Error { |
| fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
| write!(f, "{:?}", self) |
| } |
| } |
| |
| impl std::error::Error for Error { |
| fn description(&self) -> &str { |
| // TODO: fill this |
| "" |
| } |
| |
| fn cause(&self) -> Option<&std::error::Error> { |
| None |
| } |
| } |
| |
| impl std::convert::From<super::Error> for Error { |
| fn from(err: super::Error) -> Self { |
| match err { |
| super::Error::Done => Error::Done, |
| super::Error::BufferTooShort => Error::BufferTooShort, |
| _ => Error::GeneralProtocolError, |
| } |
| } |
| } |
| |
| /// An HTTP/3 configuration. |
| pub struct Config { |
| num_placeholders: u64, |
| max_header_list_size: u64, |
| qpack_max_table_capacity: u64, |
| qpack_blocked_streams: u64, |
| } |
| |
| impl Config { |
| pub fn new( |
| num_placeholders: u64, max_header_list_size: u64, |
| qpack_max_table_capacity: u64, qpack_blocked_streams: u64, |
| ) -> Result<Config> { |
| Ok(Config { |
| num_placeholders, |
| max_header_list_size, |
| qpack_max_table_capacity, |
| qpack_blocked_streams, |
| }) |
| } |
| } |
| |
| /// A name-value pair representing a raw HTTP header. |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct Header(String, String); |
| |
| impl Header { |
| /// Creates a new header. |
| pub fn new(name: &str, value: &str) -> Header { |
| Header(String::from(name), String::from(value)) |
| } |
| |
| /// Returns the header's name. |
| pub fn name(&self) -> &str { |
| &self.0 |
| } |
| |
| /// Returns the header's value. |
| pub fn value(&self) -> &str { |
| &self.1 |
| } |
| } |
| |
| /// An HTTP/3 connection event. |
| #[derive(Clone, Debug, PartialEq)] |
| pub enum Event { |
| /// Request/response headers were received. |
| Headers(Vec<Header>), |
| |
| /// Data was received. |
| Data(Vec<u8>), |
| } |
| |
| struct ConnectionSettings { |
| pub num_placeholders: Option<u64>, |
| pub max_header_list_size: Option<u64>, |
| pub qpack_max_table_capacity: Option<u64>, |
| pub qpack_blocked_streams: Option<u64>, |
| } |
| |
| struct QpackStreams { |
| pub encoder_stream_id: Option<u64>, |
| pub decoder_stream_id: Option<u64>, |
| } |
| |
| /// An HTTP/3 connection. |
| pub struct Connection { |
| is_server: bool, |
| |
| highest_request_stream_id: u64, |
| highest_uni_stream_id: u64, |
| |
| streams: BTreeMap<u64, stream::Stream>, |
| |
| local_settings: ConnectionSettings, |
| peer_settings: ConnectionSettings, |
| |
| control_stream_id: Option<u64>, |
| peer_control_stream_id: Option<u64>, |
| |
| qpack_encoder: qpack::Encoder, |
| qpack_decoder: qpack::Decoder, |
| |
| local_qpack_streams: QpackStreams, |
| peer_qpack_streams: QpackStreams, |
| } |
| |
| impl Connection { |
| fn new(config: &Config, is_server: bool) -> Result<Connection> { |
| let initial_uni_stream_id = if is_server { 0x3 } else { 0x2 }; |
| |
| Ok(Connection { |
| is_server, |
| |
| highest_request_stream_id: 0, |
| highest_uni_stream_id: initial_uni_stream_id, |
| |
| streams: BTreeMap::new(), |
| |
| local_settings: ConnectionSettings { |
| num_placeholders: Some(config.num_placeholders), |
| max_header_list_size: Some(config.max_header_list_size), |
| qpack_max_table_capacity: Some(config.qpack_max_table_capacity), |
| qpack_blocked_streams: Some(config.qpack_blocked_streams), |
| }, |
| |
| peer_settings: ConnectionSettings { |
| num_placeholders: None, |
| max_header_list_size: None, |
| qpack_max_table_capacity: None, |
| qpack_blocked_streams: None, |
| }, |
| |
| control_stream_id: None, |
| peer_control_stream_id: None, |
| |
| qpack_encoder: qpack::Encoder::new(), |
| qpack_decoder: qpack::Decoder::new(), |
| |
| local_qpack_streams: QpackStreams { |
| encoder_stream_id: None, |
| decoder_stream_id: None, |
| }, |
| peer_qpack_streams: QpackStreams { |
| encoder_stream_id: None, |
| decoder_stream_id: None, |
| }, |
| }) |
| } |
| |
| /// Creates a new HTTP/3 connection using the provided QUIC connection. |
| /// |
| /// This will also initiate the HTTP/3 handshake with the peer by opening |
| /// all control streams (including QPACK) and sending the local settings. |
| pub fn with_transport( |
| conn: &mut super::Connection, config: &Config, |
| ) -> Result<Connection> { |
| let mut http3_conn = Connection::new(config, conn.is_server)?; |
| |
| http3_conn.send_settings(conn)?; |
| http3_conn.open_qpack_streams(conn)?; |
| |
| if conn.grease { |
| http3_conn.open_grease_stream(conn)?; |
| } |
| |
| Ok(http3_conn) |
| } |
| |
| /// Sends an HTTP/3 request. |
| /// |
| /// The request is encoded from the provided list of headers, and sent on |
| /// a newly allocated stream. |
| /// |
| /// On success the newly allocated stream ID is returned. |
| pub fn send_request( |
| &mut self, conn: &mut super::Connection, headers: &[Header], fin: bool, |
| ) -> Result<u64> { |
| let stream_id = self.get_available_request_stream()?; |
| self.streams |
| .insert(stream_id, stream::Stream::new(stream_id, true)); |
| |
| self.send_headers(conn, stream_id, headers, fin)?; |
| |
| Ok(stream_id) |
| } |
| |
| /// Sends an HTTP/3 response on the specified stream. |
| pub fn send_response( |
| &mut self, conn: &mut super::Connection, stream_id: u64, |
| headers: &[Header], fin: bool, |
| ) -> Result<()> { |
| self.send_headers(conn, stream_id, headers, fin)?; |
| |
| Ok(()) |
| } |
| |
| fn send_headers( |
| &mut self, conn: &mut super::Connection, stream_id: u64, |
| headers: &[Header], fin: bool, |
| ) -> Result<()> { |
| let mut d = [42; 10]; |
| |
| let headers_len = headers |
| .iter() |
| .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32); |
| let mut header_block = vec![0; headers_len]; |
| let len = self |
| .qpack_encoder |
| .encode(&headers, &mut header_block) |
| .map_err(|_| Error::InternalError)?; |
| header_block.truncate(len); |
| |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(frame::HEADERS_FRAME_TYPE_ID)?; |
| b.put_varint(len as u64)?; |
| |
| let off = b.off(); |
| |
| if conn.grease { |
| self.send_grease_frames(conn, stream_id)?; |
| } |
| |
| trace!( |
| "{} sending HEADERS of size {} on stream {}", |
| conn.trace_id(), |
| off + len, |
| stream_id |
| ); |
| |
| conn.stream_send(stream_id, &d[..off], fin)?; |
| |
| conn.stream_send(stream_id, &header_block, fin)?; |
| |
| Ok(()) |
| } |
| |
| /// Sends an HTTP/3 body chunk on the given stream. |
| /// |
| /// On success the number of bytes written is returned. |
| pub fn send_body( |
| &mut self, conn: &mut super::Connection, stream_id: u64, body: &[u8], |
| fin: bool, |
| ) -> Result<usize> { |
| let mut d = [42; 10]; |
| |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(frame::DATA_FRAME_TYPE_ID)?; |
| b.put_varint(body.len() as u64)?; |
| |
| let off = b.off(); |
| |
| trace!( |
| "{} sending DATA frame of size {} on stream {}", |
| conn.trace_id(), |
| off + body.len(), |
| stream_id |
| ); |
| |
| conn.stream_send(stream_id, &d[..off], false)?; |
| |
| // Return how many bytes were written, excluding the frame header. |
| let written = conn.stream_send(stream_id, body, fin)?; |
| |
| Ok(written) |
| } |
| |
| /// Processes HTTP/3 data received from the peer. |
| /// |
| /// On success it returns an [`Event`] as well as the event's source stream |
| /// ID. The stream ID can be used when calling [`send_response()`] and |
| /// [`send_body()`] when responding to incoming requests. |
| /// |
| /// [`Event`]: enum.Event.html |
| /// [`send_response()`]: struct.Connection.html#method.send_response |
| /// [`send_body()`]: struct.Connection.html#method.send_body |
| pub fn poll(&mut self, conn: &mut super::Connection) -> Result<(u64, Event)> { |
| let streams: Vec<u64> = conn.readable().collect(); |
| |
| // Process HTTP/3 data from readable streams. |
| for s in streams { |
| trace!("{} stream id {} is readable", conn.trace_id(), s); |
| |
| loop { |
| match self.handle_stream(conn, s) { |
| Ok(_) => break, |
| |
| Err(Error::Done) => break, |
| |
| Err(Error::BufferTooShort) => { |
| // Keep processing transport stream. |
| }, |
| |
| Err(e) => return Err(e), |
| }; |
| } |
| } |
| |
| for (stream_id, stream) in self.streams.iter_mut() { |
| if let Some(frame) = stream.get_frame() { |
| trace!( |
| "{} rx frm {:?} on stream {}", |
| conn.trace_id(), |
| frame, |
| stream_id |
| ); |
| |
| match frame { |
| frame::Frame::Settings { |
| num_placeholders, |
| max_header_list_size, |
| qpack_max_table_capacity, |
| qpack_blocked_streams, |
| .. |
| } => { |
| if self.is_server && num_placeholders.is_some() { |
| conn.close( |
| true, |
| Error::WrongSettingDirection.to_wire(), |
| b"Num placeholder setting received by server.", |
| )?; |
| |
| return Err(Error::WrongSettingDirection); |
| } |
| |
| self.peer_settings = ConnectionSettings { |
| num_placeholders, |
| max_header_list_size, |
| qpack_max_table_capacity, |
| qpack_blocked_streams, |
| }; |
| }, |
| |
| frame::Frame::Headers { mut header_block } => { |
| if Some(*stream_id) == self.peer_control_stream_id { |
| Connection::close_unexpected_frame( |
| conn, |
| b"HEADERS received on control stream", |
| )?; |
| } |
| |
| let headers = self |
| .qpack_decoder |
| .decode(&mut header_block[..]) |
| .map_err(|_| Error::QpackDecompressionFailed)?; |
| return Ok((*stream_id, Event::Headers(headers))); |
| }, |
| |
| frame::Frame::Data { payload } => { |
| if Some(*stream_id) == self.peer_control_stream_id { |
| Connection::close_unexpected_frame( |
| conn, |
| b"DATA received on control stream", |
| )?; |
| } |
| |
| return Ok((*stream_id, Event::Data(payload))); |
| }, |
| |
| frame::Frame::GoAway { .. } => { |
| if Some(*stream_id) != self.peer_control_stream_id { |
| Connection::close_unexpected_frame( |
| conn, |
| b"GOAWAY received on non-control stream", |
| )?; |
| } |
| |
| if self.is_server { |
| Connection::close_unexpected_frame( |
| conn, |
| b"GOWAY received on server", |
| )?; |
| } |
| |
| // TODO: implement GOAWAY |
| }, |
| |
| frame::Frame::MaxPushId { .. } => { |
| if Some(*stream_id) != self.peer_control_stream_id { |
| Connection::close_unexpected_frame( |
| conn, |
| b"MAX_PUSH_ID received on non-control stream", |
| )?; |
| } |
| |
| if !self.is_server { |
| Connection::close_unexpected_frame( |
| conn, |
| b"MAX_PUSH_ID received by client", |
| )?; |
| } |
| |
| // TODO: implement MAX_PUSH_ID |
| }, |
| |
| frame::Frame::PushPromise { .. } => { |
| if stream_id % 4 != 0 { |
| Connection::close_unexpected_frame( |
| conn, |
| b"PUSH_PROMISE received on non-request stream", |
| )?; |
| } |
| |
| // TODO: implement PUSH_PROMISE |
| }, |
| |
| frame::Frame::DuplicatePush { .. } => { |
| if stream_id % 4 != 0 { |
| Connection::close_unexpected_frame( |
| conn, |
| b"DUPLICATE_PUSH received on non-request stream", |
| )?; |
| } |
| |
| // TODO: implement DUPLICATE_PUSH |
| }, |
| |
| frame::Frame::CancelPush { .. } => { |
| if Some(*stream_id) != self.peer_control_stream_id { |
| Connection::close_unexpected_frame( |
| conn, |
| b"CANCEL_PUSH received on non-control stream", |
| )?; |
| } |
| |
| // TODO: implement CANCEL_PUSH frame |
| }, |
| } |
| } |
| } |
| |
| Err(Error::Done) |
| } |
| |
| fn close_unexpected_frame( |
| conn: &mut super::Connection, reason: &[u8], |
| ) -> Result<()> { |
| conn.close(true, Error::UnexpectedFrame.to_wire(), reason)?; |
| |
| Err(Error::UnexpectedFrame) |
| } |
| |
| fn close_wrong_stream_count( |
| conn: &mut super::Connection, reason: &[u8], |
| ) -> Result<()> { |
| conn.close(true, Error::WrongStreamCount.to_wire(), reason)?; |
| |
| Err(Error::WrongStreamCount) |
| } |
| |
| /// Allocates a new request stream ID for the local endpoint to use. |
| fn get_available_request_stream(&mut self) -> Result<u64> { |
| if self.highest_request_stream_id < std::u64::MAX { |
| let ret = self.highest_request_stream_id; |
| self.highest_request_stream_id += 4; |
| return Ok(ret); |
| } |
| |
| Err(Error::LimitExceeded) |
| } |
| |
| /// Allocates a new unidirectional stream ID for the local endpoint to use. |
| fn get_available_uni_stream(&mut self) -> Result<u64> { |
| if self.highest_uni_stream_id < std::u64::MAX { |
| let ret = self.highest_uni_stream_id; |
| self.highest_uni_stream_id += 4; |
| return Ok(ret); |
| } |
| |
| Err(Error::LimitExceeded) |
| } |
| |
| /// Opens HTTP/3 control stream, if not already opened. |
| fn open_control_stream( |
| &mut self, conn: &mut super::Connection, |
| ) -> Result<()> { |
| if self.control_stream_id.is_none() { |
| let stream_id = self.get_available_uni_stream()?; |
| |
| let mut d = [42; 8]; |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(stream::HTTP3_CONTROL_STREAM_TYPE_ID)?; |
| let off = b.off(); |
| |
| conn.stream_send(stream_id, &d[..off], false)?; |
| |
| self.control_stream_id = Some(stream_id); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Opens QPACK encoder and decoder streams, if not already opened. |
| fn open_qpack_streams(&mut self, conn: &mut super::Connection) -> Result<()> { |
| if self.local_qpack_streams.encoder_stream_id.is_none() { |
| let stream_id = self.get_available_uni_stream()?; |
| |
| let mut d = [0; 8]; |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(stream::QPACK_ENCODER_STREAM_TYPE_ID)?; |
| let off = b.off(); |
| |
| conn.stream_send(stream_id, &d[..off], false)?; |
| |
| self.local_qpack_streams.encoder_stream_id = Some(stream_id); |
| } |
| |
| if self.local_qpack_streams.decoder_stream_id.is_none() { |
| let stream_id = self.get_available_uni_stream()?; |
| |
| let mut d = [0; 8]; |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(stream::QPACK_DECODER_STREAM_TYPE_ID)?; |
| let off = b.off(); |
| |
| conn.stream_send(stream_id, &d[..off], false)?; |
| |
| self.local_qpack_streams.decoder_stream_id = Some(stream_id); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Generate am HTTP/3 GREASE variable length integer. |
| fn grease_value() -> u64 { |
| let n = std::cmp::min(super::rand::rand_u64(), 148_764_065_110_560_899); |
| 31 * n + 33 |
| } |
| |
| /// Send GREASE frames on the provided stream ID. |
| fn send_grease_frames( |
| &mut self, conn: &mut super::Connection, stream_id: u64, |
| ) -> Result<()> { |
| let mut d = [42; 128]; |
| |
| let mut b = octets::Octets::with_slice(&mut d); |
| |
| // Empty GREASE frame. |
| b.put_varint(Connection::grease_value())?; |
| b.put_varint(0)?; |
| |
| // GREASE frame with payload. |
| b.put_varint(Connection::grease_value())?; |
| b.put_varint(18)?; |
| |
| trace!( |
| "{} sending GREASE frames on stream id {}", |
| conn.trace_id(), |
| stream_id |
| ); |
| |
| let off = b.off(); |
| conn.stream_send(stream_id, &d[..off], false)?; |
| |
| conn.stream_send(stream_id, b"GREASE is the word", false)?; |
| |
| Ok(()) |
| } |
| |
| /// Opens a new unidirectional stream with a GREASE type and sends some |
| /// unframed payload. |
| fn open_grease_stream(&mut self, conn: &mut super::Connection) -> Result<()> { |
| let stream_id = self.get_available_uni_stream()?; |
| |
| let mut d = [0; 8]; |
| let mut b = octets::Octets::with_slice(&mut d); |
| b.put_varint(Connection::grease_value())?; |
| |
| let off = b.off(); |
| |
| match conn.stream_send(stream_id, &d[..off], false) { |
| Ok(_v) => { |
| trace!( |
| "{} sending GREASE stream on stream id {}", |
| conn.trace_id(), |
| stream_id |
| ); |
| conn.stream_send(stream_id, b"GREASE is the word", false)?; |
| }, |
| Err(super::Error::StreamLimit) => { |
| trace!( |
| "{} sending GREASE stream was blocked on stream id {}", |
| conn.trace_id(), |
| stream_id |
| ); |
| |
| return Ok(()); |
| }, |
| Err(e) => { |
| return Err(Error::from(e)); |
| }, |
| }; |
| |
| Ok(()) |
| } |
| |
| /// Sends SETTINGS frame based on HTTP/3 configuration. |
| fn send_settings(&mut self, conn: &mut super::Connection) -> Result<()> { |
| let mut d = [42; 128]; |
| |
| self.open_control_stream(conn)?; |
| |
| // Client cannot send placeholders, so validate here |
| let num_placeholders = if self.is_server { |
| self.local_settings.num_placeholders |
| } else { |
| None |
| }; |
| |
| let grease = if conn.grease { |
| Some((Connection::grease_value(), Connection::grease_value())) |
| } else { |
| None |
| }; |
| |
| let frame = frame::Frame::Settings { |
| num_placeholders, |
| max_header_list_size: self.local_settings.max_header_list_size, |
| qpack_max_table_capacity: self |
| .local_settings |
| .qpack_max_table_capacity, |
| qpack_blocked_streams: self.local_settings.qpack_blocked_streams, |
| grease, |
| }; |
| |
| let mut b = octets::Octets::with_slice(&mut d); |
| |
| frame.to_bytes(&mut b)?; |
| |
| let off = b.off(); |
| |
| if let Some(id) = self.control_stream_id { |
| conn.stream_send(id, &d[..off], false)?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn handle_stream( |
| &mut self, conn: &mut super::Connection, stream_id: u64, |
| ) -> Result<()> { |
| let mut d = [0; 32768]; |
| |
| let stream = self |
| .streams |
| .entry(stream_id) |
| .or_insert_with(|| stream::Stream::new(stream_id, false)); |
| |
| let (read, _fin) = conn.stream_recv(stream_id, &mut d)?; |
| stream.push(&d[..read])?; |
| |
| trace!( |
| "{} read {} bytes on stream {}", |
| conn.trace_id(), |
| read, |
| stream_id |
| ); |
| |
| while stream.more() { |
| match stream.state() { |
| stream::State::StreamTypeLen => { |
| let varint_byte = stream.buf_bytes(1)?[0]; |
| stream.set_next_varint_len(octets::varint_parse_len( |
| varint_byte, |
| ))?; |
| }, |
| |
| stream::State::StreamType => { |
| let varint = stream.get_varint()?; |
| |
| let ty = stream::Type::deserialize(varint)?; |
| |
| stream.set_stream_type(ty)?; |
| |
| match &ty { |
| stream::Type::Control => { |
| // Only one control stream allowed. |
| if self.peer_control_stream_id.is_some() { |
| Connection::close_wrong_stream_count( |
| conn, |
| b"Received multiple control streams", |
| )?; |
| } |
| |
| trace!( |
| "{} peer's control stream: {}", |
| conn.trace_id(), |
| stream_id |
| ); |
| |
| self.peer_control_stream_id = Some(stream_id); |
| }, |
| |
| stream::Type::Push => { |
| // Only clients can receive push stream. |
| if self.is_server { |
| conn.close( |
| true, |
| Error::WrongStreamDirection.to_wire(), |
| b"Server received push stream.", |
| )?; |
| |
| return Err(Error::WrongStreamDirection); |
| } |
| }, |
| |
| stream::Type::QpackEncoder => { |
| // Only one qpack encoder stream allowed. |
| if self.peer_qpack_streams.encoder_stream_id.is_some() |
| { |
| Connection::close_wrong_stream_count( |
| conn, |
| b"Received multiple QPACK encoder streams", |
| )?; |
| } |
| |
| self.peer_qpack_streams.encoder_stream_id = |
| Some(stream_id); |
| }, |
| |
| stream::Type::QpackDecoder => { |
| // Only one qpack decoder allowed. |
| if self.peer_qpack_streams.decoder_stream_id.is_some() |
| { |
| Connection::close_wrong_stream_count( |
| conn, |
| b"Received multiple QPACK decoder streams", |
| )?; |
| } |
| |
| self.peer_qpack_streams.decoder_stream_id = |
| Some(stream_id); |
| }, |
| |
| stream::Type::Unknown => { |
| // Unknown stream types are ignored. |
| // TODO: we MAY send STOP_SENDING |
| }, |
| |
| stream::Type::Request => unreachable!(), |
| } |
| }, |
| |
| stream::State::FramePayloadLenLen => { |
| let varint_byte = stream.buf_bytes(1)?[0]; |
| stream.set_next_varint_len(octets::varint_parse_len( |
| varint_byte, |
| ))? |
| }, |
| |
| stream::State::FramePayloadLen => { |
| let varint = stream.get_varint()?; |
| stream.set_frame_payload_len(varint)?; |
| }, |
| |
| stream::State::FrameTypeLen => { |
| let varint_byte = stream.buf_bytes(1)?[0]; |
| stream.set_next_varint_len(octets::varint_parse_len( |
| varint_byte, |
| ))? |
| }, |
| |
| stream::State::FrameType => { |
| let varint = stream.get_varint()?; |
| stream.set_frame_type(varint)?; |
| }, |
| |
| stream::State::FramePayload => { |
| stream.parse_frame()?; |
| }, |
| |
| stream::State::QpackInstruction => { |
| return Err(Error::Done); |
| }, |
| |
| stream::State::Done => { |
| return Err(Error::Done); |
| }, |
| |
| _ => (), |
| } |
| } |
| |
| Err(Error::Done) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use crate::testing; |
| |
| #[test] |
| fn grease_value_in_varint_limit() { |
| assert!(Connection::grease_value() < 2u64.pow(62) - 1); |
| } |
| |
| #[test] |
| fn simple_request() { |
| let mut buf = [0; 65535]; |
| |
| let mut config = crate::Config::new(crate::VERSION_DRAFT19).unwrap(); |
| config |
| .load_cert_chain_from_pem_file("examples/cert.crt") |
| .unwrap(); |
| config |
| .load_priv_key_from_pem_file("examples/cert.key") |
| .unwrap(); |
| config.set_application_protos(b"\x02h3").unwrap(); |
| config.set_initial_max_data(150); |
| config.set_initial_max_stream_data_bidi_local(150); |
| config.set_initial_max_stream_data_bidi_remote(150); |
| config.set_initial_max_stream_data_uni(150); |
| config.set_initial_max_streams_bidi(3); |
| config.set_initial_max_streams_uni(3); |
| config.verify_peer(false); |
| |
| let mut pipe = testing::Pipe::with_config(&mut config).unwrap(); |
| |
| assert_eq!(pipe.handshake(&mut buf), Ok(())); |
| |
| let config = Config::new(0, 1024, 0, 0).unwrap(); |
| |
| let mut h3_cln = |
| Connection::with_transport(&mut pipe.client, &config).unwrap(); |
| let mut h3_srv = |
| Connection::with_transport(&mut pipe.server, &config).unwrap(); |
| |
| pipe.advance(&mut buf).ok(); |
| |
| let req = [ |
| Header::new(":method", "GET"), |
| Header::new(":scheme", "https"), |
| Header::new(":authority", "quic.tech"), |
| Header::new(":path", "/test"), |
| Header::new("user-agent", "quiche-test"), |
| ]; |
| |
| let stream = h3_cln.send_request(&mut pipe.client, &req, true).unwrap(); |
| assert_eq!(stream, 0); |
| |
| pipe.advance(&mut buf).ok(); |
| |
| let ev = h3_srv.poll(&mut pipe.server).unwrap(); |
| assert_eq!(ev, (stream, Event::Headers(req.to_vec()))); |
| |
| let resp = [ |
| Header::new(":status", "200"), |
| Header::new("server", "quiche-test"), |
| ]; |
| |
| h3_srv |
| .send_response(&mut pipe.server, stream, &resp, true) |
| .unwrap(); |
| |
| pipe.advance(&mut buf).ok(); |
| |
| let ev = h3_cln.poll(&mut pipe.client).unwrap(); |
| assert_eq!(ev, (stream, Event::Headers(resp.to_vec()))); |
| } |
| |
| } |
| |
| mod ffi; |
| mod frame; |
| #[doc(hidden)] |
| pub mod qpack; |
| mod stream; |