#![cfg(test)]
use super::instruction::{
encoder::{EncoderInstruction, parse as parse_encoder_instr},
field_section::{FieldLineInstruction, FieldSectionPrefix},
};
use futures_lite::future;
use std::path::Path;
#[derive(Debug, Default, Clone, Copy)]
pub(in crate::headers::qpack) struct WireHistogram {
pub(in crate::headers::qpack) set_capacity: u64,
pub(in crate::headers::qpack) insert_literal_name: u64,
pub(in crate::headers::qpack) insert_static_name_ref: u64,
pub(in crate::headers::qpack) insert_dynamic_name_ref: u64,
pub(in crate::headers::qpack) duplicate: u64,
pub(in crate::headers::qpack) indexed_static: u64,
pub(in crate::headers::qpack) indexed_dynamic_pre_base: u64,
pub(in crate::headers::qpack) indexed_post_base: u64,
pub(in crate::headers::qpack) literal_static_name_ref: u64,
pub(in crate::headers::qpack) literal_dynamic_name_ref: u64,
pub(in crate::headers::qpack) literal_post_base_name_ref: u64,
pub(in crate::headers::qpack) literal_literal_name: u64,
pub(in crate::headers::qpack) n_sections: u64,
pub(in crate::headers::qpack) section_bytes: u64,
pub(in crate::headers::qpack) encoder_stream_bytes: u64,
}
impl WireHistogram {
pub(in crate::headers::qpack) fn add(&mut self, other: &Self) {
self.set_capacity += other.set_capacity;
self.insert_literal_name += other.insert_literal_name;
self.insert_static_name_ref += other.insert_static_name_ref;
self.insert_dynamic_name_ref += other.insert_dynamic_name_ref;
self.duplicate += other.duplicate;
self.indexed_static += other.indexed_static;
self.indexed_dynamic_pre_base += other.indexed_dynamic_pre_base;
self.indexed_post_base += other.indexed_post_base;
self.literal_static_name_ref += other.literal_static_name_ref;
self.literal_dynamic_name_ref += other.literal_dynamic_name_ref;
self.literal_post_base_name_ref += other.literal_post_base_name_ref;
self.literal_literal_name += other.literal_literal_name;
self.n_sections += other.n_sections;
self.section_bytes += other.section_bytes;
self.encoder_stream_bytes += other.encoder_stream_bytes;
}
pub(in crate::headers::qpack) fn inserts_total(&self) -> u64 {
self.insert_literal_name + self.insert_static_name_ref + self.insert_dynamic_name_ref
}
pub(in crate::headers::qpack) fn indexed_dynamic_total(&self) -> u64 {
self.indexed_dynamic_pre_base + self.indexed_post_base
}
pub(in crate::headers::qpack) fn literal_dyn_name_total(&self) -> u64 {
self.literal_dynamic_name_ref + self.literal_post_base_name_ref
}
}
pub(in crate::headers::qpack) fn histogram_from_out_file(
path: &Path,
) -> std::io::Result<Option<WireHistogram>> {
let data = match std::fs::read(path) {
Ok(data) => data,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e),
};
let mut hist = WireHistogram::default();
let mut pos = 0usize;
while pos + 12 <= data.len() {
let stream_id = u64::from_be_bytes(data[pos..pos + 8].try_into().unwrap());
let length = u32::from_be_bytes(data[pos + 8..pos + 12].try_into().unwrap()) as usize;
pos += 12;
if pos + length > data.len() {
break;
}
let payload = &data[pos..pos + length];
if stream_id == 0 {
hist.encoder_stream_bytes += length as u64;
classify_encoder_stream(payload, &mut hist);
} else {
hist.n_sections += 1;
hist.section_bytes += length as u64;
classify_header_block(payload, &mut hist);
}
pos += length;
}
Ok(Some(hist))
}
pub(in crate::headers::qpack) fn classify_encoder_stream(bytes: &[u8], hist: &mut WireHistogram) {
let mut stream = bytes;
while let Ok(Some(instr)) = future::block_on(parse_encoder_instr(usize::MAX, &mut stream)) {
match instr {
EncoderInstruction::SetCapacity(_) => hist.set_capacity += 1,
EncoderInstruction::InsertWithLiteralName { .. } => hist.insert_literal_name += 1,
EncoderInstruction::InsertWithStaticNameRef { .. } => {
hist.insert_static_name_ref += 1;
}
EncoderInstruction::InsertWithDynamicNameRef { .. } => {
hist.insert_dynamic_name_ref += 1;
}
EncoderInstruction::Duplicate { .. } => hist.duplicate += 1,
}
}
}
pub(in crate::headers::qpack) fn classify_header_block(bytes: &[u8], hist: &mut WireHistogram) {
let Ok((_prefix, mut rest)) = FieldSectionPrefix::parse(bytes) else {
return;
};
while !rest.is_empty() {
match FieldLineInstruction::parse(rest) {
Ok((instr, remaining)) => {
match instr {
FieldLineInstruction::IndexedStatic { .. } => hist.indexed_static += 1,
FieldLineInstruction::IndexedDynamic { .. } => {
hist.indexed_dynamic_pre_base += 1;
}
FieldLineInstruction::IndexedPostBase { .. } => hist.indexed_post_base += 1,
FieldLineInstruction::LiteralStaticNameRef { .. } => {
hist.literal_static_name_ref += 1;
}
FieldLineInstruction::LiteralDynamicNameRef { .. } => {
hist.literal_dynamic_name_ref += 1;
}
FieldLineInstruction::LiteralPostBaseNameRef { .. } => {
hist.literal_post_base_name_ref += 1;
}
FieldLineInstruction::LiteralLiteralName { .. } => {
hist.literal_literal_name += 1;
}
}
rest = remaining;
}
Err(_) => break,
}
}
}
pub(in crate::headers::qpack) struct OutGroup {
pub(in crate::headers::qpack) enc_stream: Vec<Vec<u8>>,
pub(in crate::headers::qpack) header_block: Vec<u8>,
}
pub(in crate::headers::qpack) fn parse_out_groups(path: &Path) -> Option<Vec<OutGroup>> {
let data = std::fs::read(path).ok()?;
let mut groups = Vec::new();
let mut pending_enc: Vec<Vec<u8>> = Vec::new();
let mut pos = 0usize;
while pos + 12 <= data.len() {
let stream_id = u64::from_be_bytes(data[pos..pos + 8].try_into().unwrap());
let length = u32::from_be_bytes(data[pos + 8..pos + 12].try_into().unwrap()) as usize;
pos += 12;
if pos + length > data.len() {
break;
}
let payload = data[pos..pos + length].to_vec();
if stream_id == 0 {
pending_enc.push(payload);
} else {
groups.push(OutGroup {
enc_stream: std::mem::take(&mut pending_enc),
header_block: payload,
});
}
pos += length;
}
Some(groups)
}
pub(in crate::headers::qpack) fn render_encoder_instruction(i: &EncoderInstruction) -> String {
use EncoderInstruction::*;
match i {
SetCapacity(n) => format!("SET_CAPACITY {n}"),
InsertWithLiteralName { name, value } => format!(
"INS_LITERAL_NAME {:<30} = {}",
render_name(name),
render_value(value),
),
InsertWithStaticNameRef { name_index, value } => format!(
"INS_STATIC_NAME static[{name_index}] = {}",
render_value(value),
),
InsertWithDynamicNameRef {
relative_index,
value,
} => format!(
"INS_DYN_NAME dyn[rel={relative_index}] = {}",
render_value(value),
),
Duplicate { relative_index } => format!("DUP dyn[rel={relative_index}]"),
}
}
pub(in crate::headers::qpack) fn render_field_line(i: &FieldLineInstruction<'_>) -> String {
use FieldLineInstruction::*;
match i {
IndexedStatic { index } => format!("IDX_STATIC static[{index}]"),
IndexedDynamic { relative_index } => {
format!("IDX_DYN_PRE_BASE dyn[rel={relative_index}]")
}
IndexedPostBase { post_base_index } => {
format!("IDX_DYN_POST_BASE dyn[post+{post_base_index}]")
}
LiteralStaticNameRef {
name_index, value, ..
} => format!(
"LIT_STATIC_NAME static[{name_index}] = {}",
render_value(value.as_bytes()),
),
LiteralDynamicNameRef {
relative_index,
value,
..
} => format!(
"LIT_DYN_PRE_BASE dyn[rel={relative_index}] = {}",
render_value(value.as_bytes()),
),
LiteralPostBaseNameRef {
post_base_index,
value,
..
} => format!(
"LIT_DYN_POST_BASE dyn[post+{post_base_index}] = {}",
render_value(value.as_bytes()),
),
LiteralLiteralName { name, value, .. } => format!(
"LIT_LITERAL_NAME {:<30} = {}",
render_name(name),
render_value(value.as_bytes()),
),
}
}
fn render_name(n: &crate::headers::entry_name::EntryName<'_>) -> String {
render_value(n.as_bytes())
}
fn render_value(bytes: &[u8]) -> String {
const LIMIT: usize = 60;
let mut out = String::with_capacity(bytes.len().min(LIMIT) + 2);
out.push('"');
for (i, &b) in bytes.iter().enumerate() {
if i >= LIMIT {
out.push_str("...");
break;
}
match b {
0x20..=0x7e if b != b'"' && b != b'\\' => out.push(b as char),
b'\\' => out.push_str("\\\\"),
b'"' => out.push_str("\\\""),
b'\n' => out.push_str("\\n"),
b'\r' => out.push_str("\\r"),
b'\t' => out.push_str("\\t"),
_ => out.push_str(&format!("\\x{b:02x}")),
}
}
out.push('"');
out
}
pub(in crate::headers::qpack) fn parse_encoder_stream_for_dump(
bytes: &[u8],
) -> Vec<EncoderInstruction> {
let mut stream = bytes;
let mut out = Vec::new();
while let Ok(Some(instr)) = future::block_on(parse_encoder_instr(usize::MAX, &mut stream)) {
out.push(instr);
}
out
}
pub(in crate::headers::qpack) fn parse_header_block_for_dump(
bytes: &[u8],
) -> Option<(FieldSectionPrefix, Vec<FieldLineInstruction<'_>>)> {
let (prefix, mut rest) = FieldSectionPrefix::parse(bytes).ok()?;
let mut lines = Vec::new();
while !rest.is_empty() {
match FieldLineInstruction::parse(rest) {
Ok((instr, remaining)) => {
lines.push(instr);
rest = remaining;
}
Err(_) => break,
}
}
Some((prefix, lines))
}