// Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::fmt::{self, Display, Formatter}; use neqo_common::{Encoder, Header, qdebug}; use neqo_transport::{Connection, StreamId}; use crate::{ Error, Res, Settings, decoder_instructions::DecoderInstruction, encoder_instructions::{DecodedEncoderInstruction, EncoderInstructionReader}, header_block::{HeaderDecoder, HeaderDecoderResult}, reader::{ReadByte, Reader, ReceiverConnWrapper}, stats::Stats, table::HeaderTable, }; pub const QPACK_UNI_STREAM_TYPE_DECODER: u64 = 0x3; #[derive(Debug)] pub struct Decoder { instruction_reader: EncoderInstructionReader, table: HeaderTable, acked_inserts: u64, max_entries: u64, send_buf: Encoder, local_stream_id: Option, max_table_size: u64, max_blocked_streams: usize, blocked_streams: Vec<(StreamId, u64)>, // stream_id and requested inserts count. stats: Stats, } impl Decoder { /// # Panics /// /// If settings include invalid values. #[must_use] pub fn new(qpack_settings: &Settings) -> Self { qdebug!("Decoder: creating a new qpack decoder"); let mut send_buf = Encoder::default(); send_buf.encode_varint(QPACK_UNI_STREAM_TYPE_DECODER); let max_blocked_streams = usize::from(qpack_settings.max_blocked_streams); Self { instruction_reader: EncoderInstructionReader::default(), 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, blocked_streams: Vec::with_capacity(max_blocked_streams), stats: Stats::default(), } } #[must_use] const fn capacity(&self) -> u64 { self.table.capacity() } /// 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> { 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()); } Ok(self .blocked_streams .extract_if(.., |(_, req)| *req <= base_new) .map(|(id, _)| id) .collect()) } fn read_instructions(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> { let mut recv = ReceiverConnWrapper::new(conn, stream_id); self.process_instructions(&mut recv) } pub(crate) fn process_instructions(&mut self, recv: &mut T) -> Res<()> { loop { match self.instruction_reader.read_instructions(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 [`Error::Internal`] if the decoder stream is not initialized, /// or [`Error::DecoderStream`] if sending on the decoder stream fails. /// /// # Panics /// /// Never, but rust doesn't know that. 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.is_empty() && self.local_stream_id.is_some() { let r = conn .stream_send( self.local_stream_id.ok_or(Error::Internal)?, self.send_buf.as_ref(), ) .map_err(|_| Error::DecoderStream)?; qdebug!("[{self}] {r} bytes sent"); self.send_buf.skip(r); } Ok(()) } /// # Errors /// /// May return `Error::Decompression` if header block is incorrect or incomplete. pub fn refers_dynamic_table(&self, buf: &[u8]) -> Res { 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 `Error::Decompression` 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>> { #[cfg(feature = "build-fuzzing-corpus")] crate::fuzz::write_item_to_fuzzing_corpus(stream_id, buf); 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::Decompression) } else { let found = self .blocked_streams .iter() .any(|(id, _req)| *id == stream_id); if !found { 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::Decompression), } } /// # 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 const fn local_stream_id(&self) -> Option { self.local_stream_id } #[must_use] pub fn stats(&self) -> Stats { self.stats.clone() } } impl Display for Decoder { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "QPack {}", self.capacity()) } } fn map_error(err: &Error) -> Error { if *err == Error::ClosedCriticalStream { Error::ClosedCriticalStream } else { Error::EncoderStream } } #[cfg(test)] #[cfg_attr(coverage_nightly, coverage(off))] mod tests { use neqo_common::Header; use neqo_transport::{StreamId, StreamType}; use test_fixture::now; use super::{Connection, Decoder, Error, Res}; use crate::Settings; const STREAM_0: StreamId = StreamId::new(0); struct TestDecoder { decoder: Decoder, 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 = Decoder::new(&Settings { 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<()>) { _ = decoder .peer_conn .stream_send(decoder.recv_stream_id, encoder_instruction) .unwrap(); let out = decoder.peer_conn.process_output(now()); 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_output(now()); 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 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 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 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 recv_change_capacity() { test_instruction(0, &[0x3f, 0xa9, 0x01], &Ok(()), &[0x03], 200); } #[test] fn 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 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
, pub header_block: &'static [u8], pub encoder_inst: &'static [u8], } #[test] fn 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 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 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 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 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 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 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 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); } }