use std::{net::SocketAddr, time::Duration};
use irtt_proto::Params;
use crate::{clock::saturating_ns, config::ServerConfig, fill::FillMode};
const MAX_DURATION_CLOSE_GRACE: Duration = Duration::from_secs(2);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Session {
peer: SocketAddr,
params: Params,
fill: FillMode,
receive: ReceiveState,
rate: RateState,
lifetime: LifetimeState,
}
impl Session {
pub(crate) fn new(
peer: SocketAddr,
params: Params,
fill: FillMode,
config: &ServerConfig,
now_ns: i64,
) -> Self {
Self {
rate: RateState::new(config, ¶ms, now_ns),
lifetime: LifetimeState::opened_at(now_ns),
peer,
params,
fill,
receive: ReceiveState::default(),
}
}
pub(crate) fn peer(&self) -> SocketAddr {
self.peer
}
pub(crate) fn params(&self) -> &Params {
&self.params
}
pub(crate) fn fill(&self) -> &FillMode {
&self.fill
}
pub(crate) fn receive_state(&self) -> ReceiveState {
self.receive
}
pub(crate) fn commit_receive_state(&mut self, next: ReceiveState) {
self.receive = next;
}
pub(crate) fn rate_state(&self) -> RateState {
self.rate
}
pub(crate) fn commit_rate_state(&mut self, next: RateState) {
self.rate = next;
}
pub(crate) fn lifetime_state(&self) -> LifetimeState {
self.lifetime
}
pub(crate) fn commit_lifetime_state(&mut self, next: LifetimeState) {
self.lifetime = next;
}
pub(crate) fn is_idle_expired(&self, now_ns: i64, idle_timeout_ns: i64) -> bool {
self.lifetime.is_idle_expired(now_ns, idle_timeout_ns)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) struct ReceiveState {
pub(crate) count: u32,
pub(crate) window: u64,
last_sequence: Option<u32>,
}
impl ReceiveState {
pub(crate) fn accepted(self, sequence: u32) -> Self {
let window = match self.last_sequence {
None => 0x1,
Some(last) => match sequence.wrapping_sub(last) {
0 => self.window | 1,
distance @ 1..=63 => (self.window << distance) | 1,
_ => 0x1,
},
};
Self {
count: self.count.wrapping_add(1),
window,
last_sequence: Some(sequence),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RateState {
burst: u32,
interval_ns: i64,
allowance: u32,
refilled_at_ns: i64,
}
impl RateState {
fn new(config: &ServerConfig, params: &Params, now_ns: i64) -> Self {
let configured = saturating_ns(config.min_send_interval());
let interval_ns = if configured <= 0 || params.interval_ns <= 0 {
configured
} else {
configured.min(params.interval_ns)
};
Self {
burst: config.burst_allowance(),
interval_ns,
allowance: config.burst_allowance(),
refilled_at_ns: now_ns,
}
}
pub(crate) fn charge(mut self, now_ns: i64) -> RateDecision {
if self.burst == 0 {
return RateDecision::Limited { next: self };
}
if self.interval_ns == 0 {
return RateDecision::Allowed { next: self };
}
self.replenish(now_ns);
if self.allowance == 0 {
return RateDecision::Limited { next: self };
}
self.allowance -= 1;
RateDecision::Allowed { next: self }
}
fn replenish(&mut self, now_ns: i64) {
if self.allowance >= self.burst {
self.allowance = self.burst;
self.refilled_at_ns = now_ns;
return;
}
let elapsed = now_ns.saturating_sub(self.refilled_at_ns);
if elapsed < self.interval_ns {
return;
}
let gained = elapsed / self.interval_ns;
let allowance = self
.allowance
.saturating_add(u32::try_from(gained).unwrap_or(u32::MAX))
.min(self.burst);
self.refilled_at_ns = if allowance == self.burst {
now_ns
} else {
self.refilled_at_ns
.saturating_add(gained.saturating_mul(self.interval_ns))
};
self.allowance = allowance;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RateDecision {
Allowed { next: RateState },
Limited { next: RateState },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct LifetimeState {
last_activity_ns: i64,
first_served_echo_ns: Option<i64>,
}
impl LifetimeState {
fn opened_at(now_ns: i64) -> Self {
Self {
last_activity_ns: now_ns,
first_served_echo_ns: None,
}
}
fn is_idle_expired(&self, now_ns: i64, idle_timeout_ns: i64) -> bool {
now_ns.saturating_sub(self.last_activity_ns) >= idle_timeout_ns
}
pub(crate) fn max_duration_reached(&self, now_ns: i64, max_duration_ns: i64) -> bool {
self.first_served_echo_ns.is_some_and(|first| {
let deadline = first
.saturating_add(max_duration_ns)
.saturating_add(saturating_ns(MAX_DURATION_CLOSE_GRACE));
now_ns >= deadline
})
}
pub(crate) fn refreshed(mut self, now_ns: i64) -> Self {
self.last_activity_ns = now_ns;
self
}
pub(crate) fn served(mut self, now_ns: i64) -> Self {
self.last_activity_ns = now_ns;
self.first_served_echo_ns.get_or_insert(now_ns);
self
}
}