use crate::config::{KeepAlive, Limits};
use crate::net::Lane;
use crate::service::BodyPolicy;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ProfileId(pub u8);
impl ProfileId {
pub const MAX: ProfileId = ProfileId(255);
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Budget {
pub(crate) max_connections: Option<u32>,
pub(crate) max_in_flight: Option<u32>,
pub(crate) dedicated: Option<u16>,
}
impl Budget {
#[must_use]
pub const fn new() -> Self {
Budget {
max_connections: None,
max_in_flight: None,
dedicated: None,
}
}
#[must_use]
pub const fn max_connections(mut self, n: u32) -> Self {
self.max_connections = Some(n);
self
}
#[must_use]
pub const fn max_in_flight(mut self, n: u32) -> Self {
self.max_in_flight = Some(n);
self
}
#[must_use]
pub const fn dedicated_workers(mut self, n: u16) -> Self {
self.dedicated = Some(n);
self
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum EngineChoice {
#[default]
Http1,
Http1OrH2ByAlpn,
Custom {
alpn: &'static [&'static str],
},
}
impl EngineChoice {
pub(crate) fn admits(self, alpn: Option<&[u8]>) -> bool {
match (self, alpn) {
(_, None) => true,
(EngineChoice::Http1 | EngineChoice::Http1OrH2ByAlpn, Some(p)) => {
p == b"http/1.1" || p == b"http/1.0"
}
(EngineChoice::Custom { alpn: ids }, Some(p)) => {
ids.iter().any(|id| id.as_bytes() == p)
}
}
}
}
#[must_use]
#[derive(Clone, Debug, PartialEq)]
pub struct Profile {
pub(crate) id: ProfileId,
pub(crate) tag: u8,
pub(crate) engine: EngineChoice,
pub(crate) lane: Lane,
pub(crate) budget: Budget,
pub(crate) limits: Option<Limits>,
pub(crate) keep_alive: Option<KeepAlive>,
pub(crate) critical: bool,
pub(crate) wipe_buffers: bool,
pub(crate) body: Option<BodyPolicy>,
}
pub(crate) const DEFAULT_LANE: Lane = Lane(4);
impl Profile {
pub fn new(id: ProfileId) -> Self {
Profile {
id,
tag: 0,
engine: EngineChoice::Http1,
lane: DEFAULT_LANE,
budget: Budget::new(),
limits: None,
keep_alive: None,
critical: false,
wipe_buffers: true,
body: None,
}
}
pub fn tag(mut self, tag: u8) -> Self {
self.tag = tag;
self
}
pub fn engine(mut self, engine: EngineChoice) -> Self {
self.engine = engine;
self
}
pub fn lane(mut self, lane: Lane) -> Self {
self.lane = lane;
self
}
pub fn budget(mut self, budget: Budget) -> Self {
self.budget = budget;
self
}
pub fn limits(mut self, limits: Limits) -> Self {
self.limits = Some(limits);
self
}
pub fn keep_alive(mut self, policy: KeepAlive) -> Self {
self.keep_alive = Some(policy);
self
}
pub fn critical(mut self, on: bool) -> Self {
self.critical = on;
self
}
pub fn wipe_buffers(mut self, on: bool) -> Self {
self.wipe_buffers = on;
self
}
pub fn body(mut self, policy: BodyPolicy) -> Self {
self.body = Some(policy);
self
}
#[must_use]
pub fn id(&self) -> ProfileId {
self.id
}
}
#[derive(Debug)]
pub struct ProfileTable {
pub(crate) profiles: Box<[Compiled]>,
}
#[derive(Debug, Clone)]
pub(crate) struct Compiled {
pub(crate) profile: Profile,
pub(crate) limits: Limits,
pub(crate) keep_alive: KeepAlive,
pub(crate) body: BodyPolicy,
}
impl ProfileTable {
#[must_use]
pub fn get(&self, id: ProfileId) -> Option<&Profile> {
self.profiles.get(usize::from(id.0)).map(|c| &c.profile)
}
#[must_use]
pub fn len(&self) -> usize {
self.profiles.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.profiles.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Profile> {
self.profiles.iter().map(|c| &c.profile)
}
pub(crate) fn compiled(&self, id: ProfileId) -> Option<&Compiled> {
self.profiles.get(usize::from(id.0))
}
pub(crate) fn compile(
profiles: &[Profile],
limits: Limits,
keep_alive: KeepAlive,
) -> ProfileTable {
let mut sorted: Vec<&Profile> = profiles.iter().collect();
sorted.sort_by_key(|p| p.id);
let profiles = sorted
.into_iter()
.map(|p| {
let limits = p.limits.unwrap_or(limits);
let body = p.body.unwrap_or(BodyPolicy::Buffer {
max: limits.max_body,
});
Compiled {
profile: p.clone(),
limits,
keep_alive: p.keep_alive.unwrap_or(keep_alive),
body,
}
})
.collect();
ProfileTable { profiles }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profile_defaults() {
let p = Profile::new(ProfileId(0));
assert_eq!(p.lane, Lane(4));
assert!(p.wipe_buffers);
assert!(!p.critical);
assert_eq!(p.engine, EngineChoice::Http1);
}
#[test]
fn test_engine_admits_by_alpn() {
for choice in [EngineChoice::Http1, EngineChoice::Http1OrH2ByAlpn] {
assert!(choice.admits(None));
assert!(choice.admits(Some(b"http/1.1")));
assert!(choice.admits(Some(b"http/1.0")));
assert!(!choice.admits(Some(b"h2")));
assert!(!choice.admits(Some(b"acme-tls/1")));
assert!(
!choice.admits(Some(b"HTTP/1.1")),
"ALPN ids are exact bytes"
);
}
let custom = EngineChoice::Custom {
alpn: &["pg", "x-db/2"],
};
assert!(custom.admits(None));
assert!(custom.admits(Some(b"x-db/2")));
assert!(!custom.admits(Some(b"http/1.1")));
}
#[test]
fn test_table_compiles_server_values() {
let profiles = [
Profile::new(ProfileId(1)).limits(Limits::new().max_body(10)),
Profile::new(ProfileId(0)),
];
let t = ProfileTable::compile(&profiles, Limits::new(), KeepAlive::new());
assert_eq!(t.len(), 2);
assert_eq!(t.get(ProfileId(0)).map(Profile::id), Some(ProfileId(0)));
assert_eq!(
t.compiled(ProfileId(1)).map(|c| c.body),
Some(BodyPolicy::Buffer { max: 10 })
);
assert_eq!(
t.compiled(ProfileId(0)).map(|c| c.body),
Some(BodyPolicy::Buffer { max: 1 << 20 })
);
assert!(t.get(ProfileId(2)).is_none());
}
}