use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction};
use super::parser;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use std;
use std::collections::HashMap;
#[derive(Hash, PartialEq, Eq, Debug)]
enum HeaderName {
Method,
Path,
Host,
UserAgent,
Status,
ContentLength,
}
fn log_http2_headers<'a>(
blocks: &'a Vec<parser::HTTP2FrameHeaderBlock>, js: &mut JsonBuilder,
common: &mut HashMap<HeaderName, &'a Vec<u8>>,
) -> Result<(), JsonError> {
for j in 0..blocks.len() {
js.start_object()?;
match blocks[j].error {
parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess => {
js.set_string_from_bytes("name", &blocks[j].name)?;
js.set_string_from_bytes("value", &blocks[j].value)?;
if let Ok(name) = std::str::from_utf8(&blocks[j].name) {
match name.to_lowercase().as_ref() {
":method" => {
common.insert(HeaderName::Method, &blocks[j].value);
}
":path" => {
common.insert(HeaderName::Path, &blocks[j].value);
}
":status" => {
common.insert(HeaderName::Status, &blocks[j].value);
}
"user-agent" => {
common.insert(HeaderName::UserAgent, &blocks[j].value);
}
"host" => {
common.insert(HeaderName::Host, &blocks[j].value);
}
"content-length" => {
common.insert(HeaderName::ContentLength, &blocks[j].value);
}
_ => {}
}
}
}
parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate => {
js.set_uint("table_size_update", blocks[j].sizeupdate)?;
}
_ => {
js.set_string("error", &blocks[j].error.to_string())?;
}
}
js.close()?;
}
return Ok(());
}
fn log_headers<'a>(
frames: &'a Vec<HTTP2Frame>, js: &mut JsonBuilder,
common: &mut HashMap<HeaderName, &'a Vec<u8>>,
) -> Result<bool, JsonError> {
let mut has_headers = false;
for frame in frames {
match &frame.data {
HTTP2FrameTypeData::HEADERS(hd) => {
log_http2_headers(&hd.blocks, js, common)?;
has_headers = true;
}
HTTP2FrameTypeData::PUSHPROMISE(hd) => {
log_http2_headers(&hd.blocks, js, common)?;
has_headers = true;
}
HTTP2FrameTypeData::CONTINUATION(hd) => {
log_http2_headers(&hd.blocks, js, common)?;
has_headers = true;
}
_ => {}
}
}
Ok(has_headers)
}
fn log_http2_frames(frames: &Vec<HTTP2Frame>, js: &mut JsonBuilder) -> Result<bool, JsonError> {
let mut has_settings = false;
for i in 0..frames.len() {
if let HTTP2FrameTypeData::SETTINGS(set) = &frames[i].data {
if !has_settings {
js.open_array("settings")?;
has_settings = true;
}
for j in 0..set.len() {
js.start_object()?;
js.set_string("settings_id", &set[j].id.to_string())?;
js.set_uint("settings_value", set[j].value as u64)?;
js.close()?;
}
}
}
if has_settings {
js.close()?;
}
let mut has_error_code = false;
let mut has_priority = false;
let mut has_multiple = false;
for i in 0..frames.len() {
match &frames[i].data {
HTTP2FrameTypeData::GOAWAY(goaway) => {
if !has_error_code {
let errcode: Option<parser::HTTP2ErrorCode> =
num::FromPrimitive::from_u32(goaway.errorcode);
match errcode {
Some(errstr) => {
js.set_string("error_code", &errstr.to_string())?;
}
None => {
js.set_string("error_code", &goaway.errorcode.to_string())?;
}
}
has_error_code = true;
} else if !has_multiple {
js.set_string("has_multiple", "error_code")?;
has_multiple = true;
}
}
HTTP2FrameTypeData::RSTSTREAM(rst) => {
if !has_error_code {
let errcode: Option<parser::HTTP2ErrorCode> =
num::FromPrimitive::from_u32(rst.errorcode);
match errcode {
Some(errstr) => {
js.set_string("error_code", &errstr.to_string())?;
}
None => {
js.set_string("error_code", &rst.errorcode.to_string())?;
}
}
has_error_code = true;
} else if !has_multiple {
js.set_string("has_multiple", "error_code")?;
has_multiple = true;
}
}
HTTP2FrameTypeData::PRIORITY(priority) => {
if !has_priority {
js.set_uint("priority", priority.weight as u64)?;
has_priority = true;
} else if !has_multiple {
js.set_string("has_multiple", "priority")?;
has_multiple = true;
}
}
HTTP2FrameTypeData::HEADERS(hd) => {
if let Some(ref priority) = hd.priority {
if !has_priority {
js.set_uint("priority", priority.weight as u64)?;
has_priority = true;
} else if !has_multiple {
js.set_string("has_multiple", "priority")?;
has_multiple = true;
}
}
}
_ => {}
}
}
return Ok(has_settings || has_error_code || has_priority);
}
fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result<bool, JsonError> {
js.set_string("version", "2")?;
let mut common: HashMap<HeaderName, &Vec<u8>> = HashMap::new();
let mut has_headers = false;
let mark = js.get_mark();
js.open_array("request_headers")?;
if log_headers(&tx.frames_ts, js, &mut common)? {
js.close()?;
has_headers = true;
} else {
js.restore_mark(&mark)?;
}
let mark = js.get_mark();
js.open_array("response_headers")?;
if log_headers(&tx.frames_tc, js, &mut common)? {
js.close()?;
has_headers = true;
} else {
js.restore_mark(&mark)?;
}
for (name, value) in common {
match name {
HeaderName::Method => {
js.set_string_from_bytes("http_method", value)?;
}
HeaderName::Path => {
js.set_string_from_bytes("url", value)?;
}
HeaderName::Host => {
js.set_string_from_bytes("hostname", value)?;
}
HeaderName::UserAgent => {
js.set_string_from_bytes("http_user_agent", value)?;
}
HeaderName::ContentLength => {
if let Ok(value) = std::str::from_utf8(value) {
if let Ok(value) = value.parse::<u64>() {
js.set_uint("length", value)?;
}
}
}
HeaderName::Status => {
if let Ok(value) = std::str::from_utf8(value) {
if let Ok(value) = value.parse::<u64>() {
js.set_uint("status", value)?;
}
}
}
}
}
js.open_object("http2")?;
js.set_uint("stream_id", tx.stream_id as u64)?;
js.open_object("request")?;
let has_request = log_http2_frames(&tx.frames_ts, js)?;
js.close()?;
js.open_object("response")?;
let has_response = log_http2_frames(&tx.frames_tc, js)?;
js.close()?;
js.close()?;
return Ok(has_request || has_response || has_headers);
}
#[no_mangle]
pub extern "C" fn rs_http2_log_json(tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool {
let tx = cast_pointer!(tx, HTTP2Transaction);
if let Ok(x) = log_http2(tx, js) {
return x;
}
return false;
}