use alloc::string::String;
use crate::dds_bridge::{DispositionMapper, DispositionState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkRole {
Sender,
Receiver,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettlementMode {
Settled,
Unsettled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TerminusDurability {
None,
Configuration,
UnsettledState,
}
impl TerminusDurability {
#[must_use]
pub const fn from_wire(v: u32) -> Option<Self> {
match v {
0 => Some(Self::None),
1 => Some(Self::Configuration),
2 => Some(Self::UnsettledState),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttachDurabilityCheck {
Accept,
RejectNotImplemented,
}
#[must_use]
pub const fn check_attach_durability(durable: TerminusDurability) -> AttachDurabilityCheck {
match durable {
TerminusDurability::None | TerminusDurability::Configuration => {
AttachDurabilityCheck::Accept
}
TerminusDurability::UnsettledState => AttachDurabilityCheck::RejectNotImplemented,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinkSession {
pub name: String,
pub handle: u32,
pub role: LinkRole,
pub settlement: SettlementMode,
pub pending_settlements: u32,
pub delivered: u64,
pub credit: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeliverError {
NoCredit,
}
impl LinkSession {
#[must_use]
pub fn new(name: String, handle: u32, role: LinkRole, settlement: SettlementMode) -> Self {
Self {
name,
handle,
role,
settlement,
pending_settlements: 0,
delivered: 0,
credit: 0,
}
}
pub fn grant_credit(&mut self, additional: u32) {
self.credit = self.credit.saturating_add(additional);
}
pub fn deliver(&mut self) -> Result<(), DeliverError> {
if self.credit == 0 {
return Err(DeliverError::NoCredit);
}
self.credit -= 1;
self.delivered = self.delivered.saturating_add(1);
if self.settlement == SettlementMode::Unsettled {
self.pending_settlements = self.pending_settlements.saturating_add(1);
}
Ok(())
}
pub fn settle(&mut self) {
if self.pending_settlements > 0 {
self.pending_settlements -= 1;
}
}
pub fn settle_with_mapper<M: DispositionMapper>(
&mut self,
mapper: &M,
sample_handle: [u8; 16],
state: DispositionState,
) {
mapper.apply(sample_handle, state);
if self.pending_settlements > 0 {
self.pending_settlements -= 1;
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use alloc::string::ToString;
fn link(role: LinkRole, mode: SettlementMode) -> LinkSession {
LinkSession::new("L1".to_string(), 0, role, mode)
}
#[test]
fn new_link_starts_with_zero_credit_and_zero_delivered() {
let l = link(LinkRole::Sender, SettlementMode::Unsettled);
assert_eq!(l.credit, 0);
assert_eq!(l.delivered, 0);
assert_eq!(l.pending_settlements, 0);
}
#[test]
fn grant_credit_accumulates() {
let mut l = link(LinkRole::Sender, SettlementMode::Settled);
l.grant_credit(10);
l.grant_credit(5);
assert_eq!(l.credit, 15);
}
#[test]
fn deliver_consumes_credit_and_increments_delivered() {
let mut l = link(LinkRole::Sender, SettlementMode::Settled);
l.grant_credit(2);
assert!(l.deliver().is_ok());
assert_eq!(l.credit, 1);
assert_eq!(l.delivered, 1);
assert_eq!(l.pending_settlements, 0); }
#[test]
fn deliver_without_credit_yields_error() {
let mut l = link(LinkRole::Sender, SettlementMode::Settled);
assert!(l.deliver().is_err());
}
#[test]
fn unsettled_deliver_increments_pending() {
let mut l = link(LinkRole::Sender, SettlementMode::Unsettled);
l.grant_credit(3);
l.deliver().expect("ok");
l.deliver().expect("ok");
assert_eq!(l.pending_settlements, 2);
}
#[test]
fn settle_decrements_pending() {
let mut l = link(LinkRole::Sender, SettlementMode::Unsettled);
l.grant_credit(3);
l.deliver().expect("ok");
l.deliver().expect("ok");
l.settle();
assert_eq!(l.pending_settlements, 1);
}
#[test]
fn settle_at_zero_does_not_underflow() {
let mut l = link(LinkRole::Sender, SettlementMode::Settled);
l.settle();
assert_eq!(l.pending_settlements, 0);
}
#[test]
fn settle_with_mapper_calls_apply_and_decrements_pending() {
use core::cell::RefCell;
struct RecordingMapper {
calls: RefCell<alloc::vec::Vec<([u8; 16], DispositionState)>>,
}
impl DispositionMapper for RecordingMapper {
fn apply(&self, sample_handle: [u8; 16], state: DispositionState) {
self.calls.borrow_mut().push((sample_handle, state));
}
}
let mapper = RecordingMapper {
calls: RefCell::new(alloc::vec::Vec::new()),
};
let mut l = link(LinkRole::Sender, SettlementMode::Unsettled);
l.grant_credit(3);
l.deliver().expect("ok");
l.deliver().expect("ok");
assert_eq!(l.pending_settlements, 2);
let h1 = [0x11u8; 16];
let h2 = [0x22u8; 16];
l.settle_with_mapper(&mapper, h1, DispositionState::Accepted);
l.settle_with_mapper(&mapper, h2, DispositionState::Rejected);
assert_eq!(l.pending_settlements, 0);
let calls = mapper.calls.borrow();
assert_eq!(calls.len(), 2);
assert_eq!(calls[0], (h1, DispositionState::Accepted));
assert_eq!(calls[1], (h2, DispositionState::Rejected));
}
#[test]
fn settle_with_mapper_underflow_safe_at_zero() {
use core::cell::Cell;
struct CountingMapper {
count: Cell<u32>,
}
impl DispositionMapper for CountingMapper {
fn apply(&self, _: [u8; 16], _: DispositionState) {
self.count.set(self.count.get() + 1);
}
}
let mapper = CountingMapper {
count: Cell::new(0),
};
let mut l = link(LinkRole::Sender, SettlementMode::Settled);
l.settle_with_mapper(&mapper, [0u8; 16], DispositionState::Accepted);
assert_eq!(l.pending_settlements, 0);
assert_eq!(mapper.count.get(), 1);
}
#[test]
fn terminus_durability_from_wire() {
assert_eq!(
TerminusDurability::from_wire(0),
Some(TerminusDurability::None)
);
assert_eq!(
TerminusDurability::from_wire(1),
Some(TerminusDurability::Configuration)
);
assert_eq!(
TerminusDurability::from_wire(2),
Some(TerminusDurability::UnsettledState)
);
assert_eq!(TerminusDurability::from_wire(3), None);
}
#[test]
fn attach_durability_none_accepted() {
assert_eq!(
check_attach_durability(TerminusDurability::None),
AttachDurabilityCheck::Accept
);
}
#[test]
fn attach_durability_configuration_accepted() {
assert_eq!(
check_attach_durability(TerminusDurability::Configuration),
AttachDurabilityCheck::Accept
);
}
#[test]
fn attach_durability_unsettled_state_rejected_not_implemented() {
assert_eq!(
check_attach_durability(TerminusDurability::UnsettledState),
AttachDurabilityCheck::RejectNotImplemented
);
}
}