use core::{cell::Cell, fmt, ops::Range};
use crate::observe::core::TapEvent;
use crate::runtime::consts::{
DefaultLabelUniverse, LANE_DOMAIN_SIZE, LANES_MAX, LabelUniverse, RING_EVENTS,
};
pub trait Clock {
fn now32(&self) -> u32;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct LivenessPolicy {
pub(crate) max_defer_per_offer: u8,
pub(crate) max_no_evidence_defer: u8,
pub(crate) force_poll_on_exhaustion: bool,
pub(crate) max_forced_poll_attempts: u8,
pub(crate) exhaust_reason: u16,
}
impl Default for LivenessPolicy {
fn default() -> Self {
Self {
max_defer_per_offer: 8,
max_no_evidence_defer: 1,
force_poll_on_exhaustion: true,
max_forced_poll_attempts: 1,
exhaust_reason: crate::policy_runtime::ENGINE_LIVENESS_EXHAUSTED,
}
}
}
pub struct CounterClock {
counter: Cell<u32>,
}
impl CounterClock {
pub const fn new() -> Self {
Self {
counter: Cell::new(0),
}
}
}
impl Default for CounterClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for CounterClock {
fn now32(&self) -> u32 {
let current = self.counter.get();
let next = current.saturating_add(1);
self.counter.set(next);
current
}
}
impl<C: Clock + ?Sized> Clock for &C {
fn now32(&self) -> u32 {
(**self).now32()
}
}
impl fmt::Debug for CounterClock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CounterClock").finish()
}
}
pub struct Config<'a, U: LabelUniverse = DefaultLabelUniverse, C: Clock = CounterClock> {
pub(crate) tap_buf: &'a mut [TapEvent; RING_EVENTS],
pub(crate) slab: &'a mut [u8],
pub(crate) lane_range: Range<u16>,
universe: U,
pub(crate) clock: C,
pub(crate) liveness_policy: LivenessPolicy,
}
impl<'a, U: LabelUniverse, C: Clock> fmt::Debug for Config<'a, U, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("lane_range", &self.lane_range)
.field("universe", &core::any::type_name::<U>())
.field("clock", &core::any::type_name::<C>())
.field("liveness_policy", &self.liveness_policy)
.finish()
}
}
impl<'a> Config<'a, DefaultLabelUniverse, CounterClock> {
pub fn new(tap_buf: &'a mut [TapEvent; RING_EVENTS], slab: &'a mut [u8]) -> Self {
Self {
tap_buf,
slab,
lane_range: 0..LANES_MAX,
universe: DefaultLabelUniverse,
clock: CounterClock::new(),
liveness_policy: LivenessPolicy::default(),
}
}
}
impl<'a, U: LabelUniverse, C: Clock> Config<'a, U, C> {
pub fn tap_storage(&mut self) -> &mut [TapEvent; RING_EVENTS] {
self.tap_buf
}
pub fn slab(&mut self) -> &mut [u8] {
self.slab
}
pub fn with_lane_range(mut self, range: Range<u16>) -> Self {
assert!(
range.start <= range.end,
"lane range {:?} must satisfy start <= end",
range
);
assert!(
range.start == 0 && range.end > 0,
"lane range {:?} must include reserved control lane 0 for SessionKit attach",
range
);
assert!(
range.end <= LANE_DOMAIN_SIZE,
"lane range {:?} must stay inside the u8 wire lane domain",
range
);
self.lane_range = range;
self
}
pub fn lane_range(&self) -> Range<u16> {
self.lane_range.clone()
}
pub fn with_universe<V: LabelUniverse>(self, universe: V) -> Config<'a, V, C> {
Config {
tap_buf: self.tap_buf,
slab: self.slab,
lane_range: self.lane_range,
universe,
clock: self.clock,
liveness_policy: self.liveness_policy,
}
}
pub fn universe(&self) -> &U {
&self.universe
}
pub fn with_clock<D: Clock>(self, clock: D) -> Config<'a, U, D> {
Config {
tap_buf: self.tap_buf,
slab: self.slab,
lane_range: self.lane_range,
universe: self.universe,
clock,
liveness_policy: self.liveness_policy,
}
}
pub fn clock(&self) -> &C {
&self.clock
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
#[test]
fn with_lane_range_rejects_windows_without_control_lane_zero() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let panic = catch_unwind(AssertUnwindSafe(|| {
let _ = Config::new(&mut tap_buf, &mut slab).with_lane_range(32..35);
}));
assert!(
panic.is_err(),
"public SessionKit config must reject lane windows that exclude reserved control lane 0"
);
}
#[test]
fn with_lane_range_accepts_zero_based_high_lane_windows() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let config = Config::new(&mut tap_buf, &mut slab).with_lane_range(0..35);
assert_eq!(
config.lane_range(),
0..35,
"public SessionKit config must keep accepting zero-based high-lane windows"
);
}
#[test]
fn with_lane_range_accepts_full_wire_lane_domain() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let config = Config::new(&mut tap_buf, &mut slab).with_lane_range(0..LANE_DOMAIN_SIZE);
assert!(
config.lane_range().contains(&u16::from(u8::MAX)),
"public SessionKit config must be able to include lane 255"
);
}
#[test]
fn with_lane_range_rejects_out_of_wire_domain_window() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let panic = catch_unwind(AssertUnwindSafe(|| {
let _ = Config::new(&mut tap_buf, &mut slab).with_lane_range(0..(LANE_DOMAIN_SIZE + 1));
}));
assert!(
panic.is_err(),
"public SessionKit config must reject lanes outside the u8 wire domain"
);
}
}