leyline-http 0.1.3

HTTP client that mimics browsers on the wire
Documentation
use std::time::Duration;

use crate::h2::config::{PseudoOrder, parse_pseudo_order};
use crate::profile::{BrowserProfile, H3Profile, TlsProfile};
use crate::{Error, Kind};

const SETTINGS_MAX_FIELD_SECTION_SIZE: u64 = 0x06;

const DEFAULT_PSEUDO_ORDER: [PseudoOrder; 4] = [
    PseudoOrder::Method,
    PseudoOrder::Scheme,
    PseudoOrder::Authority,
    PseudoOrder::Path,
];

#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct H3Config {
    pub initial_max_data: u64,
    pub initial_max_stream_data_bidi_local: u64,
    pub initial_max_stream_data_bidi_remote: u64,
    pub initial_max_stream_data_uni: u64,
    pub initial_max_streams_bidi: u64,
    pub initial_max_streams_uni: u64,
    pub max_idle_timeout: Duration,
    pub max_udp_payload_size: u16,
    pub active_connection_id_limit: u64,

    pub max_response_body_bytes: u64,

    pub(crate) wire: H3Profile,
    pub(crate) tls: TlsProfile,
    pub(crate) pseudo_order: [PseudoOrder; 4],
    pub(crate) max_header_list_bytes: u64,
}

impl H3Config {
    pub fn from_profile(profile: &BrowserProfile) -> Result<Self, Error> {
        let h3 = profile.h3.as_ref().ok_or_else(|| {
            Error::new(Kind::Config).with_message(format!(
                "profile {} has no [h3] table, so it has no HTTP/3 transport",
                profile.meta.name
            ))
        })?;
        let pseudo_order = match &h3.pseudo_order {
            Some(tokens) => parse_pseudo_order(tokens, "H3")?,
            None => DEFAULT_PSEUDO_ORDER,
        };
        Ok(Self {
            initial_max_data: h3.initial_max_data,
            initial_max_stream_data_bidi_local: h3.initial_max_stream_data_bidi_local,
            initial_max_stream_data_bidi_remote: h3.initial_max_stream_data_bidi_remote,
            initial_max_stream_data_uni: h3.initial_max_stream_data_uni,
            initial_max_streams_bidi: h3.initial_max_streams_bidi,
            initial_max_streams_uni: h3.initial_max_streams_uni,
            max_idle_timeout: Duration::from_secs(h3.max_idle_timeout_secs),
            max_udp_payload_size: h3.max_udp_payload_size,
            active_connection_id_limit: h3.active_connection_id_limit,
            max_response_body_bytes: crate::core::DEFAULT_MAX_BODY_SIZE as u64,
            tls: h3.tls.clone().unwrap_or_else(|| profile.tls.clone()),
            wire: h3.clone(),
            pseudo_order,
            max_header_list_bytes: advertised_field_section_size(h3)
                .unwrap_or(crate::core::DEFAULT_MAX_HEADER_LIST_BYTES as u64),
        })
    }
}

fn advertised_field_section_size(h3: &H3Profile) -> Option<u64> {
    h3.settings
        .iter()
        .flatten()
        .find(|setting| setting.id == Some(SETTINGS_MAX_FIELD_SECTION_SIZE))
        .and_then(|setting| setting.value)
        .or(h3.max_field_section_size)
}