#[derive(Debug, Clone)]
pub struct MrpConfig {
pub idle_retry_interval_ms: u64,
pub active_retry_interval_ms: u64,
pub max_retransmit_count: u8,
pub ack_timeout_ms: u64,
}
impl Default for MrpConfig {
fn default() -> Self {
Self {
idle_retry_interval_ms: 5_000,
active_retry_interval_ms: 300,
max_retransmit_count: 4,
ack_timeout_ms: 200,
}
}
}
pub struct MrpExchange {
pub exchange_id: u16,
pub message_counter: u32,
pub retransmit_count: u8,
pub config: MrpConfig,
}
impl MrpExchange {
pub fn new(exchange_id: u16, message_counter: u32) -> Self {
Self {
exchange_id,
message_counter,
retransmit_count: 0,
config: MrpConfig::default(),
}
}
pub fn with_config(exchange_id: u16, message_counter: u32, config: MrpConfig) -> Self {
Self {
exchange_id,
message_counter,
retransmit_count: 0,
config,
}
}
pub fn retry_delay_ms(&self) -> u64 {
let base = self.config.active_retry_interval_ms;
let delay = base.saturating_mul(1u64 << self.retransmit_count.min(10));
delay.min(self.config.idle_retry_interval_ms)
}
pub fn record_retry(&mut self) -> bool {
if self.retransmit_count >= self.config.max_retransmit_count {
return false;
}
self.retransmit_count += 1;
true
}
pub fn build_ack_payload(ack_counter: u32) -> Vec<u8> {
const TLV_CONTEXT_UINT32: u8 = 0x25;
const TAG_ACK_COUNTER: u8 = 0x01;
let mut payload = Vec::with_capacity(6);
payload.push(TLV_CONTEXT_UINT32);
payload.push(TAG_ACK_COUNTER);
payload.extend_from_slice(&ack_counter.to_le_bytes());
payload
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mrp_retry_delay_uses_active_interval() {
let ex = MrpExchange::new(1, 100);
assert_eq!(ex.retry_delay_ms(), 300);
}
#[test]
fn mrp_max_retries_returns_false() {
let mut ex = MrpExchange::new(2, 200);
assert!(ex.record_retry()); assert!(ex.record_retry()); assert!(ex.record_retry()); assert!(ex.record_retry()); assert!(!ex.record_retry());
}
#[test]
fn mrp_ack_payload_contains_counter() {
let counter: u32 = 0xDEAD_BEEF;
let payload = MrpExchange::build_ack_payload(counter);
assert_eq!(payload.len(), 6);
assert_eq!(payload[0], 0x25);
assert_eq!(payload[1], 0x01);
let decoded = u32::from_le_bytes([payload[2], payload[3], payload[4], payload[5]]);
assert_eq!(decoded, counter);
}
#[test]
fn mrp_back_off_caps_at_idle_interval() {
let mut config = MrpConfig::default();
config.active_retry_interval_ms = 300;
config.idle_retry_interval_ms = 5_000;
config.max_retransmit_count = 10;
let mut ex = MrpExchange::with_config(3, 0, config);
for _ in 0..8 {
ex.record_retry();
}
assert!(ex.retry_delay_ms() <= 5_000);
}
}