diff options
Diffstat (limited to 'third_party/rust/neqo-qpack/src/decoder.rs')
-rw-r--r-- | third_party/rust/neqo-qpack/src/decoder.rs | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/third_party/rust/neqo-qpack/src/decoder.rs b/third_party/rust/neqo-qpack/src/decoder.rs new file mode 100644 index 0000000000..e5cbfa09d0 --- /dev/null +++ b/third_party/rust/neqo-qpack/src/decoder.rs @@ -0,0 +1,804 @@ +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::decoder_instructions::DecoderInstruction; +use crate::encoder_instructions::{DecodedEncoderInstruction, EncoderInstructionReader}; +use crate::header_block::{HeaderDecoder, HeaderDecoderResult}; +use crate::qpack_send_buf::QpackData; +use crate::reader::ReceiverConnWrapper; +use crate::stats::Stats; +use crate::table::HeaderTable; +use crate::{Error, QpackSettings, Res}; +use neqo_common::{qdebug, Header}; +use neqo_transport::{Connection, StreamId}; +use std::convert::TryFrom; + +pub const QPACK_UNI_STREAM_TYPE_DECODER: u64 = 0x3; + +#[derive(Debug)] +pub struct QPackDecoder { + instruction_reader: EncoderInstructionReader, + table: HeaderTable, + acked_inserts: u64, + max_entries: u64, + send_buf: QpackData, + local_stream_id: Option<StreamId>, + max_table_size: u64, + max_blocked_streams: usize, + blocked_streams: Vec<(StreamId, u64)>, //stream_id and requested inserts count. + stats: Stats, +} + +impl QPackDecoder { + /// # Panics + /// If settings include invalid values. + #[must_use] + pub fn new(qpack_settings: &QpackSettings) -> Self { + qdebug!("Decoder: creating a new qpack decoder."); + let mut send_buf = QpackData::default(); + send_buf.encode_varint(QPACK_UNI_STREAM_TYPE_DECODER); + Self { + instruction_reader: EncoderInstructionReader::new(), + table: HeaderTable::new(false), + acked_inserts: 0, + max_entries: qpack_settings.max_table_size_decoder >> 5, + send_buf, + local_stream_id: None, + max_table_size: qpack_settings.max_table_size_decoder, + max_blocked_streams: usize::try_from(qpack_settings.max_blocked_streams).unwrap(), + blocked_streams: Vec::new(), + stats: Stats::default(), + } + } + + #[must_use] + fn capacity(&self) -> u64 { + self.table.capacity() + } + + #[must_use] + pub fn get_max_table_size(&self) -> u64 { + self.max_table_size + } + + /// # Panics + /// If the number of blocked streams is too large. + #[must_use] + pub fn get_blocked_streams(&self) -> u16 { + u16::try_from(self.max_blocked_streams).unwrap() + } + + /// returns a list of unblocked streams + /// # Errors + /// May return: `ClosedCriticalStream` if stream has been closed or `EncoderStream` + /// in case of any other transport error. + pub fn receive(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<Vec<StreamId>> { + let base_old = self.table.base(); + self.read_instructions(conn, stream_id) + .map_err(|e| map_error(&e))?; + let base_new = self.table.base(); + if base_old == base_new { + return Ok(Vec::new()); + } + + let r = self + .blocked_streams + .iter() + .filter_map(|(id, req)| if *req <= base_new { Some(*id) } else { None }) + .collect::<Vec<_>>(); + self.blocked_streams.retain(|(_, req)| *req > base_new); + Ok(r) + } + + fn read_instructions(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> { + let mut recv = ReceiverConnWrapper::new(conn, stream_id); + loop { + match self.instruction_reader.read_instructions(&mut recv) { + Ok(instruction) => self.execute_instruction(instruction)?, + Err(Error::NeedMoreData) => break Ok(()), + Err(e) => break Err(e), + } + } + } + + fn execute_instruction(&mut self, instruction: DecodedEncoderInstruction) -> Res<()> { + match instruction { + DecodedEncoderInstruction::Capacity { value } => self.set_capacity(value)?, + DecodedEncoderInstruction::InsertWithNameRefStatic { index, value } => { + Error::map_error( + self.table.insert_with_name_ref(true, index, &value), + Error::EncoderStream, + )?; + self.stats.dynamic_table_inserts += 1; + } + DecodedEncoderInstruction::InsertWithNameRefDynamic { index, value } => { + Error::map_error( + self.table.insert_with_name_ref(false, index, &value), + Error::EncoderStream, + )?; + self.stats.dynamic_table_inserts += 1; + } + DecodedEncoderInstruction::InsertWithNameLiteral { name, value } => { + Error::map_error( + self.table.insert(&name, &value).map(|_| ()), + Error::EncoderStream, + )?; + self.stats.dynamic_table_inserts += 1; + } + DecodedEncoderInstruction::Duplicate { index } => { + Error::map_error(self.table.duplicate(index), Error::EncoderStream)?; + self.stats.dynamic_table_inserts += 1; + } + DecodedEncoderInstruction::NoInstruction => { + unreachable!("This can be call only with an instruction."); + } + } + Ok(()) + } + + fn set_capacity(&mut self, cap: u64) -> Res<()> { + qdebug!([self], "received instruction capacity cap={}", cap); + if cap > self.max_table_size { + return Err(Error::EncoderStream); + } + self.table.set_capacity(cap) + } + + fn header_ack(&mut self, stream_id: StreamId, required_inserts: u64) { + DecoderInstruction::HeaderAck { stream_id }.marshal(&mut self.send_buf); + if required_inserts > self.acked_inserts { + self.acked_inserts = required_inserts; + } + } + + pub fn cancel_stream(&mut self, stream_id: StreamId) { + if self.table.capacity() > 0 { + self.blocked_streams.retain(|(id, _)| *id != stream_id); + DecoderInstruction::StreamCancellation { stream_id }.marshal(&mut self.send_buf); + } + } + + /// # Errors + /// May return an error in case of any transport error. TODO: define transport errors. + /// # Panics + /// Never, but rust doesn't know that. + #[allow(clippy::map_err_ignore)] + pub fn send(&mut self, conn: &mut Connection) -> Res<()> { + // Encode increment instruction if needed. + let increment = self.table.base() - self.acked_inserts; + if increment > 0 { + DecoderInstruction::InsertCountIncrement { increment }.marshal(&mut self.send_buf); + self.acked_inserts = self.table.base(); + } + if self.send_buf.len() != 0 && self.local_stream_id.is_some() { + let r = conn + .stream_send(self.local_stream_id.unwrap(), &self.send_buf[..]) + .map_err(|_| Error::DecoderStream)?; + qdebug!([self], "{} bytes sent.", r); + self.send_buf.read(r as usize); + } + Ok(()) + } + + /// # Errors + /// May return `DecompressionFailed` if header block is incorrect or incomplete. + pub fn refers_dynamic_table(&self, buf: &[u8]) -> Res<bool> { + HeaderDecoder::new(buf).refers_dynamic_table(self.max_entries, self.table.base()) + } + + /// This function returns None if the stream is blocked waiting for table insertions. + /// 'buf' must contain the complete header block. + /// # Errors + /// May return `DecompressionFailed` if header block is incorrect or incomplete. + /// # Panics + /// When there is a programming error. + pub fn decode_header_block( + &mut self, + buf: &[u8], + stream_id: StreamId, + ) -> Res<Option<Vec<Header>>> { + qdebug!([self], "decode header block."); + let mut decoder = HeaderDecoder::new(buf); + + match decoder.decode_header_block(&self.table, self.max_entries, self.table.base()) { + Ok(HeaderDecoderResult::Blocked(req_insert_cnt)) => { + if self.blocked_streams.len() > self.max_blocked_streams { + Err(Error::DecompressionFailed) + } else { + let r = self + .blocked_streams + .iter() + .filter_map(|(id, req)| if *id == stream_id { Some(*req) } else { None }) + .collect::<Vec<_>>(); + if !r.is_empty() { + debug_assert!(r.len() == 1); + debug_assert!(r[0] == req_insert_cnt); + return Ok(None); + } + self.blocked_streams.push((stream_id, req_insert_cnt)); + Ok(None) + } + } + Ok(HeaderDecoderResult::Headers(h)) => { + if decoder.get_req_insert_cnt() != 0 { + self.header_ack(stream_id, decoder.get_req_insert_cnt()); + self.stats.dynamic_table_references += 1; + } + Ok(Some(h)) + } + Err(_) => Err(Error::DecompressionFailed), + } + } + + /// # Panics + /// When a stream has already been added. + pub fn add_send_stream(&mut self, stream_id: StreamId) { + assert!( + self.local_stream_id.is_none(), + "Adding multiple local streams" + ); + self.local_stream_id = Some(stream_id); + } + + #[must_use] + pub fn local_stream_id(&self) -> Option<StreamId> { + self.local_stream_id + } + + #[must_use] + pub fn stats(&self) -> Stats { + self.stats.clone() + } +} + +impl ::std::fmt::Display for QPackDecoder { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "QPackDecoder {}", self.capacity()) + } +} + +fn map_error(err: &Error) -> Error { + if *err == Error::ClosedCriticalStream { + Error::ClosedCriticalStream + } else { + Error::EncoderStream + } +} + +#[cfg(test)] +mod tests { + use super::{Connection, Error, QPackDecoder, Res}; + use crate::QpackSettings; + use neqo_common::Header; + use neqo_transport::{StreamId, StreamType}; + use std::convert::TryFrom; + use std::mem; + use test_fixture::now; + + const STREAM_0: StreamId = StreamId::new(0); + + struct TestDecoder { + decoder: QPackDecoder, + send_stream_id: StreamId, + recv_stream_id: StreamId, + conn: Connection, + peer_conn: Connection, + } + + fn connect() -> TestDecoder { + let (mut conn, mut peer_conn) = test_fixture::connect(); + + // create a stream + let recv_stream_id = peer_conn.stream_create(StreamType::UniDi).unwrap(); + let send_stream_id = conn.stream_create(StreamType::UniDi).unwrap(); + + // create a decoder + let mut decoder = QPackDecoder::new(&QpackSettings { + max_table_size_encoder: 0, + max_table_size_decoder: 300, + max_blocked_streams: 100, + }); + decoder.add_send_stream(send_stream_id); + + TestDecoder { + decoder, + send_stream_id, + recv_stream_id, + conn, + peer_conn, + } + } + + fn recv_instruction(decoder: &mut TestDecoder, encoder_instruction: &[u8], res: &Res<()>) { + let _ = decoder + .peer_conn + .stream_send(decoder.recv_stream_id, encoder_instruction) + .unwrap(); + let out = decoder.peer_conn.process(None, now()); + mem::drop(decoder.conn.process(out.dgram(), now())); + assert_eq!( + decoder + .decoder + .read_instructions(&mut decoder.conn, decoder.recv_stream_id), + *res + ); + } + + fn send_instructions_and_check(decoder: &mut TestDecoder, decoder_instruction: &[u8]) { + decoder.decoder.send(&mut decoder.conn).unwrap(); + let out = decoder.conn.process(None, now()); + mem::drop(decoder.peer_conn.process(out.dgram(), now())); + let mut buf = [0_u8; 100]; + let (amount, fin) = decoder + .peer_conn + .stream_recv(decoder.send_stream_id, &mut buf) + .unwrap(); + assert!(!fin); + assert_eq!(&buf[..amount], decoder_instruction); + } + + fn decode_headers( + decoder: &mut TestDecoder, + header_block: &[u8], + headers: &[Header], + stream_id: StreamId, + ) { + let decoded_headers = decoder + .decoder + .decode_header_block(header_block, stream_id) + .unwrap(); + let h = decoded_headers.unwrap(); + assert_eq!(h, headers); + } + + fn test_instruction( + capacity: u64, + instruction: &[u8], + res: &Res<()>, + decoder_instruction: &[u8], + check_capacity: u64, + ) { + let mut decoder = connect(); + + if capacity > 0 { + assert!(decoder.decoder.set_capacity(capacity).is_ok()); + } + + // recv an instruction + recv_instruction(&mut decoder, instruction, res); + + // send decoder instruction and check that is what we expect. + send_instructions_and_check(&mut decoder, decoder_instruction); + + if check_capacity > 0 { + assert_eq!(decoder.decoder.capacity(), check_capacity); + } + } + + // test insert_with_name_ref which fails because there is not enough space in the table + #[test] + fn test_recv_insert_with_name_ref_1() { + test_instruction( + 0, + &[0xc4, 0x04, 0x31, 0x32, 0x33, 0x34], + &Err(Error::EncoderStream), + &[0x03], + 0, + ); + } + + // test insert_name_ref that succeeds + #[test] + fn test_recv_insert_with_name_ref_2() { + test_instruction( + 100, + &[0xc4, 0x04, 0x31, 0x32, 0x33, 0x34], + &Ok(()), + &[0x03, 0x01], + 0, + ); + } + + // test insert with name literal - succeeds + #[test] + fn test_recv_insert_with_name_litarel_2() { + test_instruction( + 200, + &[ + 0x4e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x04, 0x31, 0x32, 0x33, 0x34, + ], + &Ok(()), + &[0x03, 0x01], + 0, + ); + } + + #[test] + fn test_recv_change_capacity() { + test_instruction(0, &[0x3f, 0xa9, 0x01], &Ok(()), &[0x03], 200); + } + + #[test] + fn test_recv_change_capacity_too_big() { + test_instruction( + 0, + &[0x3f, 0xf1, 0x02], + &Err(Error::EncoderStream), + &[0x03], + 0, + ); + } + + // this test tests header decoding, the header acks command and the insert count increment command. + #[test] + fn test_duplicate() { + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(100).is_ok()); + + // receive an instruction + recv_instruction( + &mut decoder, + &[ + 0x4e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x04, 0x31, 0x32, 0x33, 0x34, + ], + &Ok(()), + ); + + // receive the second instruction, a duplicate instruction. + recv_instruction(&mut decoder, &[0x00], &Ok(())); + + send_instructions_and_check(&mut decoder, &[0x03, 0x02]); + } + + struct TestElement { + pub headers: Vec<Header>, + pub header_block: &'static [u8], + pub encoder_inst: &'static [u8], + } + + #[test] + fn test_encode_incr_encode_header_ack_some() { + // 1. Decoder receives an instruction (header and value both as literal) + // 2. Decoder process the instruction and sends an increment instruction. + // 3. Decoder receives another two instruction (header and value both as literal) and + // a header block. + // 4. Now it sends only a header ack and an increment instruction with increment==1. + let headers = vec![ + Header::new("my-headera", "my-valuea"), + Header::new("my-headerb", "my-valueb"), + ]; + let header_block = &[0x03, 0x81, 0x10, 0x11]; + let first_encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, + ]; + let second_encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x63, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x63, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, first_encoder_inst, &Ok(())); + + send_instructions_and_check(&mut decoder, &[0x03, 0x1]); + + recv_instruction(&mut decoder, second_encoder_inst, &Ok(())); + + decode_headers(&mut decoder, header_block, &headers, STREAM_0); + + send_instructions_and_check(&mut decoder, &[0x80, 0x1]); + } + + #[test] + fn test_encode_incr_encode_header_ack_all() { + // 1. Decoder receives an instruction (header and value both as literal) + // 2. Decoder process the instruction and sends an increment instruction. + // 3. Decoder receives another instruction (header and value both as literal) and + // a header block. + // 4. Now it sends only a header ack. + let headers = vec![ + Header::new("my-headera", "my-valuea"), + Header::new("my-headerb", "my-valueb"), + ]; + let header_block = &[0x03, 0x81, 0x10, 0x11]; + let first_encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, + ]; + let second_encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, first_encoder_inst, &Ok(())); + + send_instructions_and_check(&mut decoder, &[0x03, 0x1]); + + recv_instruction(&mut decoder, second_encoder_inst, &Ok(())); + + decode_headers(&mut decoder, header_block, &headers, STREAM_0); + + send_instructions_and_check(&mut decoder, &[0x80]); + } + + #[test] + fn test_header_ack_all() { + // Send two instructions to insert values into the dynamic table and then send a header + // that references them both. The result should be only a header acknowledgement. + let headers = vec![ + Header::new("my-headera", "my-valuea"), + Header::new("my-headerb", "my-valueb"), + ]; + let header_block = &[0x03, 0x81, 0x10, 0x11]; + let encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, encoder_inst, &Ok(())); + + decode_headers(&mut decoder, header_block, &headers, STREAM_0); + + send_instructions_and_check(&mut decoder, &[0x03, 0x80]); + } + + #[test] + fn test_header_ack_and_incr_instruction() { + // Send two instructions to insert values into the dynamic table and then send a header + // that references only the first. The result should be a header acknowledgement and a + // increment instruction. + let headers = vec![Header::new("my-headera", "my-valuea")]; + let header_block = &[0x02, 0x80, 0x10]; + let encoder_inst = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, encoder_inst, &Ok(())); + + decode_headers(&mut decoder, header_block, &headers, STREAM_0); + + send_instructions_and_check(&mut decoder, &[0x03, 0x80, 0x01]); + } + + #[test] + fn test_header_block_decoder() { + let test_cases: [TestElement; 6] = [ + // test a header with ref to static - encode_indexed + TestElement { + headers: vec![Header::new(":method", "GET")], + header_block: &[0x00, 0x00, 0xd1], + encoder_inst: &[], + }, + // test encode_literal_with_name_ref + TestElement { + headers: vec![Header::new(":path", "/somewhere")], + header_block: &[ + 0x00, 0x00, 0x51, 0x0a, 0x2f, 0x73, 0x6f, 0x6d, 0x65, 0x77, 0x68, 0x65, 0x72, + 0x65, + ], + encoder_inst: &[], + }, + // test adding a new header and encode_post_base_index, also test fix_header_block_prefix + TestElement { + headers: vec![Header::new("my-header", "my-value")], + header_block: &[0x02, 0x80, 0x10], + encoder_inst: &[ + 0x49, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x08, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, + ], + }, + // test encode_indexed with a ref to dynamic table. + TestElement { + headers: vec![Header::new("my-header", "my-value")], + header_block: &[0x02, 0x00, 0x80], + encoder_inst: &[], + }, + // test encode_literal_with_name_ref. + TestElement { + headers: vec![Header::new("my-header", "my-value2")], + header_block: &[ + 0x02, 0x00, 0x40, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, + ], + encoder_inst: &[], + }, + // test multiple headers + TestElement { + headers: vec![ + Header::new(":method", "GET"), + Header::new(":path", "/somewhere"), + Header::new(":authority", "example.com"), + Header::new(":scheme", "https"), + ], + header_block: &[ + 0x00, 0x01, 0xd1, 0x51, 0x0a, 0x2f, 0x73, 0x6f, 0x6d, 0x65, 0x77, 0x68, 0x65, + 0x72, 0x65, 0x50, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, + 0x6f, 0x6d, 0xd7, + ], + encoder_inst: &[], + }, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + for (i, t) in test_cases.iter().enumerate() { + // receive an instruction + if !t.encoder_inst.is_empty() { + recv_instruction(&mut decoder, t.encoder_inst, &Ok(())); + } + + decode_headers( + &mut decoder, + t.header_block, + &t.headers, + StreamId::from(u64::try_from(i).unwrap()), + ); + } + + // test header acks and the insert count increment command + send_instructions_and_check(&mut decoder, &[0x03, 0x82, 0x83, 0x84]); + } + + #[test] + fn test_header_block_decoder_huffman() { + let test_cases: [TestElement; 6] = [ + // test a header with ref to static - encode_indexed + TestElement { + headers: vec![Header::new(":method", "GET")], + header_block: &[0x00, 0x00, 0xd1], + encoder_inst: &[], + }, + // test encode_literal_with_name_ref + TestElement { + headers: vec![Header::new(":path", "/somewhere")], + header_block: &[ + 0x00, 0x00, 0x51, 0x87, 0x61, 0x07, 0xa4, 0xbe, 0x27, 0x2d, 0x85, + ], + encoder_inst: &[], + }, + // test adding a new header and encode_post_base_index, also test fix_header_block_prefix + TestElement { + headers: vec![Header::new("my-header", "my-value")], + header_block: &[0x02, 0x80, 0x10], + encoder_inst: &[ + 0x67, 0xa7, 0xd2, 0xd3, 0x94, 0x72, 0x16, 0xcf, 0x86, 0xa7, 0xd2, 0xdd, 0xc7, + 0x45, 0xa5, + ], + }, + // test encode_indexed with a ref to dynamic table. + TestElement { + headers: vec![Header::new("my-header", "my-value")], + header_block: &[0x02, 0x00, 0x80], + encoder_inst: &[], + }, + // test encode_literal_with_name_ref. + TestElement { + headers: vec![Header::new("my-header", "my-value2")], + header_block: &[ + 0x02, 0x00, 0x40, 0x87, 0xa7, 0xd2, 0xdd, 0xc7, 0x45, 0xa5, 0x17, + ], + encoder_inst: &[], + }, + // test multiple headers + TestElement { + headers: vec![ + Header::new(":method", "GET"), + Header::new(":path", "/somewhere"), + Header::new(":authority", "example.com"), + Header::new(":scheme", "https"), + ], + header_block: &[ + 0x00, 0x01, 0xd1, 0x51, 0x87, 0x61, 0x07, 0xa4, 0xbe, 0x27, 0x2d, 0x85, 0x50, + 0x88, 0x2f, 0x91, 0xd3, 0x5d, 0x05, 0x5c, 0x87, 0xa7, 0xd7, + ], + encoder_inst: &[], + }, + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + for (i, t) in test_cases.iter().enumerate() { + // receive an instruction. + if !t.encoder_inst.is_empty() { + recv_instruction(&mut decoder, t.encoder_inst, &Ok(())); + } + + decode_headers( + &mut decoder, + t.header_block, + &t.headers, + StreamId::from(u64::try_from(i).unwrap()), + ); + } + + // test header acks and the insert count increment command + send_instructions_and_check(&mut decoder, &[0x03, 0x82, 0x83, 0x84]); + } + + #[test] + fn test_subtract_overflow_in_header_ack() { + const HEADER_BLOCK_1: &[u8] = &[0x03, 0x81, 0x10, 0x11]; + const ENCODER_INST: &[u8] = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, + ]; + const HEADER_BLOCK_2: &[u8] = &[0x02, 0x80, 0x10]; + // Test for issue https://github.com/mozilla/neqo/issues/475 + // Send two instructions to insert values into the dynamic table and send a header + // that references them both. This will increase number of acked inserts in the table + // to 2. Then send a header that references only one of them which shouldn't increase + // number of acked inserts. + let headers = vec![ + Header::new("my-headera", "my-valuea"), + Header::new("my-headerb", "my-valueb"), + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, ENCODER_INST, &Ok(())); + + decode_headers(&mut decoder, HEADER_BLOCK_1, &headers, STREAM_0); + + let headers = vec![Header::new("my-headera", "my-valuea")]; + + decode_headers(&mut decoder, HEADER_BLOCK_2, &headers, STREAM_0); + } + + #[test] + fn test_base_larger_than_entry_count() { + // Test for issue https://github.com/mozilla/neqo/issues/533 + // Send instruction that inserts 2 fields into the dynamic table and send a header that + // uses base larger than 2. + const ENCODER_INST: &[u8] = &[ + 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x61, 0x09, 0x6d, 0x79, + 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x61, 0x4a, 0x6d, 0x79, 0x2d, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x62, 0x09, 0x6d, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x62, + ]; + + const HEADER_BLOCK: &[u8] = &[0x03, 0x03, 0x83, 0x84]; + + let headers = vec![ + Header::new("my-headerb", "my-valueb"), + Header::new("my-headera", "my-valuea"), + ]; + + let mut decoder = connect(); + + assert!(decoder.decoder.set_capacity(200).is_ok()); + + recv_instruction(&mut decoder, ENCODER_INST, &Ok(())); + + decode_headers(&mut decoder, HEADER_BLOCK, &headers, STREAM_0); + } +} |