use crate::{
error::ClaimFault::{self},
protocol::{
constants::{
addr_mgmt_pgns::{CLAIM_PGN_60928, REQUEST_PGN_59904, REQUEST_PGN_LEN},
address::{self, GLOBAL, NULL_ADDR_254},
iso_delay::{CANNOT_CLAIM_RETRY_DELAY_MS, CLAIM_DELAY_MS},
},
management::{
address_claiming::{
build_address_claim_frame, classify_claim, AddressClaimIterator,
AddressClaimStrategy, ClaimRelation,
},
iso_name::IsoName,
},
transport::can_frame::CanFrame,
},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ClaimOutput {
pub tx: Option<CanFrame>,
pub status: ClaimStatus,
pub wake_at_ms: Option<u64>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClaimStatus {
Claiming(u8),
Claimed(u8),
CannotClaim,
}
impl ClaimStatus {
pub fn claimed_address(&self) -> Option<u8> {
match self {
Self::Claimed(address) => Some(*address),
_ => None,
}
}
}
#[derive(PartialEq, Eq, Debug)]
enum State {
Unclaimed,
Claiming { frame: CanFrame, deadline_ms: u64 },
Claimed { frame: CanFrame },
CannotClaim { retry_at_ms: u64 },
}
pub struct AddressClaimEngine<'a> {
my_name: IsoName,
addr_iterator: Option<AddressClaimIterator<'a>>,
strategy: AddressClaimStrategy<'a>,
state: State,
tx_pending: bool,
}
impl<'a> AddressClaimEngine<'a> {
pub fn new(my_name: IsoName, strategy: AddressClaimStrategy<'a>) -> Result<Self, ClaimFault> {
if !IsoName::is_addr_capable_and_isoname_match(&my_name, strategy) {
return Err(ClaimFault::InconsistentStrategy);
}
Ok(Self {
my_name,
addr_iterator: None,
strategy,
state: State::Unclaimed,
tx_pending: false,
})
}
fn output(&self, tx: Option<CanFrame>) -> ClaimOutput {
let (status, wake_at_ms) = match self.state {
State::Unclaimed => unreachable!("output() called before the first campaign"),
State::Claiming { frame, deadline_ms } => {
let wake_at_ms = (!self.tx_pending).then_some(deadline_ms);
(ClaimStatus::Claiming(frame.id.source_address()), wake_at_ms)
}
State::Claimed { frame } => (ClaimStatus::Claimed(frame.id.source_address()), None),
State::CannotClaim { retry_at_ms } => {
let wake_at_ms = (!self.tx_pending).then_some(retry_at_ms);
(ClaimStatus::CannotClaim, wake_at_ms)
}
};
ClaimOutput {
tx,
status,
wake_at_ms,
}
}
fn start_claim(&mut self, now_ms: u64) -> ClaimOutput {
let iterator = self
.addr_iterator
.insert(AddressClaimIterator::new(self.strategy));
let addr_to_claim = iterator.try_next_addr();
let claim_frame = build_address_claim_frame(self.my_name, addr_to_claim);
if addr_to_claim == NULL_ADDR_254 {
self.state = State::CannotClaim {
retry_at_ms: now_ms.saturating_add(CANNOT_CLAIM_RETRY_DELAY_MS as u64),
};
} else {
self.state = State::Claiming {
frame: claim_frame,
deadline_ms: now_ms.saturating_add(CLAIM_DELAY_MS as u64),
};
}
self.tx_pending = true;
self.output(Some(claim_frame))
}
pub fn tx_sent(&mut self, now_ms: u64) -> ClaimOutput {
if self.tx_pending {
match &mut self.state {
State::Claiming { deadline_ms, .. } => {
*deadline_ms = now_ms.saturating_add(CLAIM_DELAY_MS as u64);
}
State::CannotClaim { retry_at_ms } => {
*retry_at_ms = now_ms.saturating_add(CANNOT_CLAIM_RETRY_DELAY_MS as u64);
}
_ => {}
}
self.tx_pending = false;
}
self.output(None)
}
pub fn poll(&mut self, now_ms: u64, rx: Option<&CanFrame>) -> ClaimOutput {
if self.tx_pending {
let tx = match self.state {
State::Claiming { frame, .. } => frame,
State::CannotClaim { .. } => build_address_claim_frame(self.my_name, NULL_ADDR_254),
_ => unreachable!("pending send without a frame"),
};
return self.output(Some(tx));
}
match self.state {
State::Unclaimed => self.start_claim(now_ms),
State::Claiming { frame, deadline_ms } => {
let mut tx = None;
if let Some(recv_frame) = rx {
if is_addressed_claim_request_message(recv_frame, frame.id.source_address()) {
tx = Some(frame);
} else {
match classify_claim(recv_frame, self.my_name, frame.id.source_address()) {
ClaimRelation::WeLose => return self.handle_losing_conflict(now_ms),
ClaimRelation::WeWin => tx = Some(frame),
_ => {}
}
}
}
if deadline_ms.saturating_sub(now_ms) == 0 {
self.state = State::Claimed { frame };
}
self.output(tx)
}
State::Claimed { frame } => {
let mut tx = None;
if let Some(recv_frame) = rx {
if is_addressed_claim_request_message(recv_frame, frame.id.source_address()) {
tx = Some(frame);
} else {
match classify_claim(recv_frame, self.my_name, frame.id.source_address()) {
ClaimRelation::WeLose => return self.handle_losing_conflict(now_ms),
ClaimRelation::WeWin => tx = Some(frame),
_ => {}
}
}
}
self.output(tx)
}
State::CannotClaim { retry_at_ms } => {
if retry_at_ms.saturating_sub(now_ms) == 0 {
return self.start_claim(now_ms);
}
let tx = match rx {
Some(recv_frame)
if is_addressed_claim_request_message(recv_frame, NULL_ADDR_254) =>
{
Some(build_address_claim_frame(self.my_name, NULL_ADDR_254))
}
_ => None,
};
self.output(tx)
}
}
}
fn handle_losing_conflict(&mut self, now_ms: u64) -> ClaimOutput {
let addr_to_claim: u8 = {
if let Some(addr_it) = &mut self.addr_iterator {
addr_it.try_next_addr()
} else {
address::NULL_ADDR_254
}
};
let claim_frame = build_address_claim_frame(self.my_name, addr_to_claim);
if addr_to_claim == address::NULL_ADDR_254 {
self.state = State::CannotClaim {
retry_at_ms: now_ms.saturating_add(CANNOT_CLAIM_RETRY_DELAY_MS as u64),
};
} else {
self.state = State::Claiming {
frame: claim_frame,
deadline_ms: now_ms.saturating_add(CLAIM_DELAY_MS as u64),
};
}
self.tx_pending = true;
self.output(Some(claim_frame))
}
pub fn claimed_address(&self) -> Option<u8> {
match self.state {
State::Claimed { frame } => Some(frame.id.source_address()),
_ => None,
}
}
}
fn is_addressed_claim_request_message(frame: &CanFrame, curr_addr: u8) -> bool {
if frame.id.pgn() != REQUEST_PGN_59904 || frame.len < REQUEST_PGN_LEN {
return false;
}
let requested_pgn = u32::from_le_bytes([frame.data[0], frame.data[1], frame.data[2], 0]);
requested_pgn == CLAIM_PGN_60928
&& frame
.id
.destination()
.is_some_and(|request_addr| request_addr == curr_addr || request_addr == GLOBAL)
}
#[cfg(test)]
pub mod tests;