use core::{cell::Cell, fmt, marker::PhantomData, ops::Range};
use crate::observe::core::TapEvent;
use crate::runtime::consts::{DefaultLabelUniverse, LANE_DOMAIN_SIZE, 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>,
pub(crate) endpoint_slots: usize,
universe_marker: PhantomData<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("endpoint_slots", &self.endpoint_slots)
.field("universe", &core::any::type_name::<U>())
.field("clock", &core::any::type_name::<C>())
.field("liveness_policy", &self.liveness_policy)
.finish()
}
}
impl<'a, U: LabelUniverse, C: Clock> Config<'a, U, C> {
pub fn new(
tap_buf: &'a mut [TapEvent; RING_EVENTS],
slab: &'a mut [u8],
lane_range: Range<u16>,
endpoint_slots: usize,
clock: C,
) -> Self {
Self::validate_lane_range(&lane_range);
Self::validate_endpoint_slots(endpoint_slots);
Self {
tap_buf,
slab,
lane_range,
endpoint_slots,
universe_marker: PhantomData,
clock,
liveness_policy: LivenessPolicy::default(),
}
}
fn validate_lane_range(range: &Range<u16>) {
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
);
}
fn validate_endpoint_slots(endpoint_slots: usize) {
assert!(
endpoint_slots > 0,
"endpoint slot capacity must materialize at least one projected role"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
#[test]
fn new_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<'_, DefaultLabelUniverse, _> =
Config::new(&mut tap_buf, &mut slab, 32..35, 1, CounterClock::new());
}));
assert!(
panic.is_err(),
"public SessionKit config must reject lane windows that exclude reserved control lane 0"
);
}
#[test]
fn new_accepts_zero_based_high_lane_windows() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let config: Config<'_, DefaultLabelUniverse, _> =
Config::new(&mut tap_buf, &mut slab, 0..35, 1, CounterClock::new());
assert_eq!(
config.lane_range,
0..35,
"public SessionKit config must keep accepting zero-based high-lane windows"
);
}
#[test]
fn new_accepts_full_wire_lane_domain() {
let mut tap_buf = [TapEvent::zero(); RING_EVENTS];
let mut slab = [0u8; 256];
let config: Config<'_, DefaultLabelUniverse, _> = Config::new(
&mut tap_buf,
&mut slab,
0..LANE_DOMAIN_SIZE,
1,
CounterClock::new(),
);
assert!(
config.lane_range.contains(&u16::from(u8::MAX)),
"public SessionKit config must be able to include lane 255"
);
}
#[test]
fn new_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<'_, DefaultLabelUniverse, _> = Config::new(
&mut tap_buf,
&mut slab,
0..(LANE_DOMAIN_SIZE + 1),
1,
CounterClock::new(),
);
}));
assert!(
panic.is_err(),
"public SessionKit config must reject lanes outside the u8 wire domain"
);
}
}