http2: use a reference counter for headers

Ticket: 6892

As HTTP hpack header compression allows one single byte to
express a previously seen arbitrary-size header block (name+value)
we should avoid to copy the vectors data, but just point
to the same data, while reamining memory safe, even in the case
of later headers eviction from the dybnamic table.

Rust std solution is Rc, and the use of clone, so long as the
data is accessed by only one thread.
pull/10934/head
Philippe Antoine 1 year ago committed by Victor Julien
parent fd47e67dc6
commit 390f09692e

@ -23,6 +23,7 @@ use crate::core::Direction;
use crate::detect::uint::{detect_match_uint, DetectUintData}; use crate::detect::uint::{detect_match_uint, DetectUintData};
use std::ffi::CStr; use std::ffi::CStr;
use std::str::FromStr; use std::str::FromStr;
use std::rc::Rc;
fn http2_tx_has_frametype( fn http2_tx_has_frametype(
tx: &mut HTTP2Transaction, direction: Direction, value: u8, tx: &mut HTTP2Transaction, direction: Direction, value: u8,
@ -404,7 +405,7 @@ fn http2_frames_get_header_firstvalue<'a>(
for frame in frames { for frame in frames {
if let Some(blocks) = http2_header_blocks(frame) { if let Some(blocks) = http2_header_blocks(frame) {
for block in blocks.iter() { for block in blocks.iter() {
if block.name == name.as_bytes() { if block.name.as_ref() == name.as_bytes() {
return Ok(&block.value); return Ok(&block.value);
} }
} }
@ -428,7 +429,7 @@ pub fn http2_frames_get_header_value_vec(
for frame in frames { for frame in frames {
if let Some(blocks) = http2_header_blocks(frame) { if let Some(blocks) = http2_header_blocks(frame) {
for block in blocks.iter() { for block in blocks.iter() {
if block.name == name.as_bytes() { if block.name.as_ref() == name.as_bytes() {
if found == 0 { if found == 0 {
vec.extend_from_slice(&block.value); vec.extend_from_slice(&block.value);
found = 1; found = 1;
@ -465,7 +466,7 @@ fn http2_frames_get_header_value<'a>(
for frame in frames { for frame in frames {
if let Some(blocks) = http2_header_blocks(frame) { if let Some(blocks) = http2_header_blocks(frame) {
for block in blocks.iter() { for block in blocks.iter() {
if block.name == name.as_bytes() { if block.name.as_ref() == name.as_bytes() {
if found == 0 { if found == 0 {
single = Ok(&block.value); single = Ok(&block.value);
found = 1; found = 1;
@ -920,8 +921,8 @@ fn http2_tx_set_header(state: &mut HTTP2State, name: &[u8], input: &[u8]) {
}; };
let mut blocks = Vec::new(); let mut blocks = Vec::new();
let b = parser::HTTP2FrameHeaderBlock { let b = parser::HTTP2FrameHeaderBlock {
name: name.to_vec(), name: Rc::new(name.to_vec()),
value: input.to_vec(), value: Rc::new(input.to_vec()),
error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
sizeupdate: 0, sizeupdate: 0,
}; };
@ -1063,15 +1064,15 @@ mod tests {
}; };
let mut blocks = Vec::new(); let mut blocks = Vec::new();
let b = parser::HTTP2FrameHeaderBlock { let b = parser::HTTP2FrameHeaderBlock {
name: "Host".as_bytes().to_vec(), name: "Host".as_bytes().to_vec().into(),
value: "abc.com".as_bytes().to_vec(), value: "abc.com".as_bytes().to_vec().into(),
error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
sizeupdate: 0, sizeupdate: 0,
}; };
blocks.push(b); blocks.push(b);
let b2 = parser::HTTP2FrameHeaderBlock { let b2 = parser::HTTP2FrameHeaderBlock {
name: "Host".as_bytes().to_vec(), name: "Host".as_bytes().to_vec().into(),
value: "efg.net".as_bytes().to_vec(), value: "efg.net".as_bytes().to_vec().into(),
error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
sizeupdate: 0, sizeupdate: 0,
}; };

@ -208,7 +208,7 @@ impl HTTP2Transaction {
let mut authority = None; let mut authority = None;
let mut host = None; let mut host = None;
for block in blocks { for block in blocks {
if block.name == b"content-encoding" { if block.name.as_ref() == b"content-encoding" {
self.decoder.http2_encoding_fromvec(&block.value, dir); self.decoder.http2_encoding_fromvec(&block.value, dir);
} else if block.name.eq_ignore_ascii_case(b":authority") { } else if block.name.eq_ignore_ascii_case(b":authority") {
authority = Some(&block.value); authority = Some(&block.value);

@ -30,6 +30,7 @@ use nom7::sequence::tuple;
use nom7::{Err, IResult}; use nom7::{Err, IResult};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::rc::Rc;
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] #[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)]
@ -295,8 +296,8 @@ fn http2_frame_header_static(n: u64, dyn_headers: &HTTP2DynTable) -> Option<HTTP
}; };
if !name.is_empty() { if !name.is_empty() {
return Some(HTTP2FrameHeaderBlock { return Some(HTTP2FrameHeaderBlock {
name: name.as_bytes().to_vec(), name: Rc::new(name.as_bytes().to_vec()),
value: value.as_bytes().to_vec(), value: Rc::new(value.as_bytes().to_vec()),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
sizeupdate: 0, sizeupdate: 0,
}); });
@ -304,23 +305,23 @@ fn http2_frame_header_static(n: u64, dyn_headers: &HTTP2DynTable) -> Option<HTTP
//use dynamic table //use dynamic table
if n == 0 { if n == 0 {
return Some(HTTP2FrameHeaderBlock { return Some(HTTP2FrameHeaderBlock {
name: Vec::new(), name: Rc::new(Vec::new()),
value: Vec::new(), value: Rc::new(Vec::new()),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0,
sizeupdate: 0, sizeupdate: 0,
}); });
} else if dyn_headers.table.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize { } else if dyn_headers.table.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize {
return Some(HTTP2FrameHeaderBlock { return Some(HTTP2FrameHeaderBlock {
name: Vec::new(), name: Rc::new(Vec::new()),
value: Vec::new(), value: Rc::new(Vec::new()),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed,
sizeupdate: 0, sizeupdate: 0,
}); });
} else { } else {
let indyn = dyn_headers.table.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER); let indyn = dyn_headers.table.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER);
let headcopy = HTTP2FrameHeaderBlock { let headcopy = HTTP2FrameHeaderBlock {
name: dyn_headers.table[indyn].name.to_vec(), name: dyn_headers.table[indyn].name.clone(),
value: dyn_headers.table[indyn].value.to_vec(), value: dyn_headers.table[indyn].value.clone(),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
sizeupdate: 0, sizeupdate: 0,
}; };
@ -348,8 +349,10 @@ impl fmt::Display for HTTP2HeaderDecodeStatus {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct HTTP2FrameHeaderBlock { pub struct HTTP2FrameHeaderBlock {
pub name: Vec<u8>, // Use Rc reference counted so that indexed headers do not get copied.
pub value: Vec<u8>, // Otherwise, this leads to quadratic complexity in memory occupation.
pub name: Rc<Vec<u8>>,
pub value: Rc<Vec<u8>>,
pub error: HTTP2HeaderDecodeStatus, pub error: HTTP2HeaderDecodeStatus,
pub sizeupdate: u64, pub sizeupdate: u64,
} }
@ -391,7 +394,7 @@ fn http2_parse_headers_block_literal_common<'a>(
) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
let (i3, name, error) = if index == 0 { let (i3, name, error) = if index == 0 {
match http2_parse_headers_block_string(input) { match http2_parse_headers_block_string(input) {
Ok((r, n)) => Ok((r, n, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)), Ok((r, n)) => Ok((r, Rc::new(n), HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)),
Err(e) => Err(e), Err(e) => Err(e),
} }
} else { } else {
@ -403,7 +406,7 @@ fn http2_parse_headers_block_literal_common<'a>(
)), )),
None => Ok(( None => Ok((
input, input,
Vec::new(), Rc::new(Vec::new()),
HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed,
)), )),
} }
@ -413,7 +416,7 @@ fn http2_parse_headers_block_literal_common<'a>(
i4, i4,
HTTP2FrameHeaderBlock { HTTP2FrameHeaderBlock {
name, name,
value, value: Rc::new(value),
error, error,
sizeupdate: 0, sizeupdate: 0,
}, },
@ -435,8 +438,8 @@ fn http2_parse_headers_block_literal_incindex<'a>(
match r { match r {
Ok((r, head)) => { Ok((r, head)) => {
let headcopy = HTTP2FrameHeaderBlock { let headcopy = HTTP2FrameHeaderBlock {
name: head.name.to_vec(), name: head.name.clone(),
value: head.value.to_vec(), value: head.value.clone(),
error: head.error, error: head.error,
sizeupdate: 0, sizeupdate: 0,
}; };
@ -556,8 +559,8 @@ fn http2_parse_headers_block_dynamic_size<'a>(
return Ok(( return Ok((
i3, i3,
HTTP2FrameHeaderBlock { HTTP2FrameHeaderBlock {
name: Vec::new(), name: Rc::new(Vec::new()),
value: Vec::new(), value: Rc::new(Vec::new()),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate,
sizeupdate: maxsize2, sizeupdate: maxsize2,
}, },
@ -614,8 +617,8 @@ fn http2_parse_headers_blocks<'a>(
// if we error from http2_parse_var_uint, we keep the first parsed headers // if we error from http2_parse_var_uint, we keep the first parsed headers
if err.code == ErrorKind::LengthValue { if err.code == ErrorKind::LengthValue {
blocks.push(HTTP2FrameHeaderBlock { blocks.push(HTTP2FrameHeaderBlock {
name: Vec::new(), name: Rc::new(Vec::new()),
value: Vec::new(), value: Rc::new(Vec::new()),
error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow, error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow,
sizeupdate: 0, sizeupdate: 0,
}); });
@ -769,8 +772,8 @@ mod tests {
match r0 { match r0 {
Ok((remainder, hd)) => { Ok((remainder, hd)) => {
// Check the first message. // Check the first message.
assert_eq!(hd.name, ":method".as_bytes().to_vec()); assert_eq!(hd.name, ":method".as_bytes().to_vec().into());
assert_eq!(hd.value, "GET".as_bytes().to_vec()); assert_eq!(hd.value, "GET".as_bytes().to_vec().into());
// And we should have no bytes left. // And we should have no bytes left.
assert_eq!(remainder.len(), 0); assert_eq!(remainder.len(), 0);
} }
@ -786,8 +789,8 @@ mod tests {
match r1 { match r1 {
Ok((remainder, hd)) => { Ok((remainder, hd)) => {
// Check the first message. // Check the first message.
assert_eq!(hd.name, "accept".as_bytes().to_vec()); assert_eq!(hd.name, "accept".as_bytes().to_vec().into());
assert_eq!(hd.value, "*/*".as_bytes().to_vec()); assert_eq!(hd.value, "*/*".as_bytes().to_vec().into());
// And we should have no bytes left. // And we should have no bytes left.
assert_eq!(remainder.len(), 0); assert_eq!(remainder.len(), 0);
assert_eq!(dynh.table.len(), 1); assert_eq!(dynh.table.len(), 1);
@ -806,8 +809,8 @@ mod tests {
match result { match result {
Ok((remainder, hd)) => { Ok((remainder, hd)) => {
// Check the first message. // Check the first message.
assert_eq!(hd.name, ":authority".as_bytes().to_vec()); assert_eq!(hd.name, ":authority".as_bytes().to_vec().into());
assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec().into());
// And we should have no bytes left. // And we should have no bytes left.
assert_eq!(remainder.len(), 0); assert_eq!(remainder.len(), 0);
assert_eq!(dynh.table.len(), 2); assert_eq!(dynh.table.len(), 2);
@ -824,8 +827,8 @@ mod tests {
match r3 { match r3 {
Ok((remainder, hd)) => { Ok((remainder, hd)) => {
// same as before // same as before
assert_eq!(hd.name, ":authority".as_bytes().to_vec()); assert_eq!(hd.name, ":authority".as_bytes().to_vec().into());
assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec().into());
// And we should have no bytes left. // And we should have no bytes left.
assert_eq!(remainder.len(), 0); assert_eq!(remainder.len(), 0);
assert_eq!(dynh.table.len(), 2); assert_eq!(dynh.table.len(), 2);
@ -860,8 +863,8 @@ mod tests {
match r2 { match r2 {
Ok((remainder, hd)) => { Ok((remainder, hd)) => {
// Check the first message. // Check the first message.
assert_eq!(hd.name, ":path".as_bytes().to_vec()); assert_eq!(hd.name, ":path".as_bytes().to_vec().into());
assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec()); assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec().into());
// And we should have no bytes left. // And we should have no bytes left.
assert_eq!(remainder.len(), 0); assert_eq!(remainder.len(), 0);
assert_eq!(dynh.table.len(), 2); assert_eq!(dynh.table.len(), 2);

Loading…
Cancel
Save