1use crate::clock::Clock;
8use crate::command::Command;
9use crate::error::Error;
10use crate::packet::{Address, ControlByte, CtrlFlags, PacketBuilder, ParsedPacket, Sqn};
11use crate::reply::{Reply, ReplyCode};
12use crate::transport::Transport;
13use alloc::vec::Vec;
14
15const RX_CHUNK_LEN: usize = 64;
19
20const MAX_EMPTY_READS: u8 = 4;
25
26#[derive(Debug, Clone)]
28pub struct PdState {
29 pub next_sqn: Sqn,
31 pub use_crc: bool,
33 pub last_seen_ms: u64,
35 seen_at_least_once: bool,
37}
38
39impl Default for PdState {
40 fn default() -> Self {
41 Self {
42 next_sqn: Sqn::ZERO,
43 use_crc: true,
44 last_seen_ms: 0,
45 seen_at_least_once: false,
46 }
47 }
48}
49
50impl PdState {
51 pub fn bump_sqn(&mut self) {
53 self.next_sqn = self.next_sqn.next();
54 }
55
56 pub fn mark_seen(&mut self, now_ms: u64) {
58 self.last_seen_ms = now_ms;
59 self.seen_at_least_once = true;
60 }
61
62 pub fn is_offline(&self, now_ms: u64) -> bool {
66 self.seen_at_least_once
67 && now_ms.saturating_sub(self.last_seen_ms) >= crate::OFFLINE_THRESHOLD_MS as u64
68 }
69
70 pub fn reset(&mut self) {
73 *self = Self::default();
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79#[non_exhaustive]
80pub enum ExchangeOutcome {
81 Reply(Reply),
83 Busy,
85 Timeout,
87 Offline,
89}
90
91#[derive(Debug, Clone, Copy)]
93pub struct RetryConfig {
94 pub max_retries: u8,
96 pub overall_budget_ms: u32,
100}
101
102impl Default for RetryConfig {
103 fn default() -> Self {
104 Self {
105 max_retries: 2,
106 overall_budget_ms: 0,
107 }
108 }
109}
110
111pub struct Acu<T: Transport, C: Clock> {
113 transport: T,
114 clock: C,
115 pub reply_delay_ms: u32,
117 pub retry: RetryConfig,
119 rx_buf: Vec<u8>,
120}
121
122impl<T: Transport, C: Clock> Acu<T, C> {
123 pub fn new(transport: T, clock: C) -> Self {
125 Self {
126 transport,
127 clock,
128 reply_delay_ms: crate::REPLY_DELAY_MS,
129 retry: RetryConfig::default(),
130 rx_buf: Vec::with_capacity(crate::MAX_BUS_PACKET),
131 }
132 }
133
134 pub fn transport(&mut self) -> &mut T {
136 &mut self.transport
137 }
138
139 pub fn clock(&self) -> &C {
141 &self.clock
142 }
143
144 pub fn send_to(
146 &mut self,
147 pd_addr: u8,
148 pd: &mut PdState,
149 command: &Command,
150 ) -> Result<Vec<u8>, Error> {
151 self.send_with_sqn(pd_addr, pd.use_crc, pd.next_sqn, command)
152 }
153
154 fn send_with_sqn(
155 &mut self,
156 pd_addr: u8,
157 use_crc: bool,
158 sqn: Sqn,
159 command: &Command,
160 ) -> Result<Vec<u8>, Error> {
161 let addr = Address::pd(pd_addr)?;
162 let mut flags = CtrlFlags::empty();
163 if use_crc {
164 flags |= CtrlFlags::USE_CRC;
165 }
166 let ctrl = ControlByte::new(sqn, flags);
167 let data = command.encode_data()?;
168 let bytes = PacketBuilder::plain(addr, ctrl, command.code().as_byte(), data).encode()?;
169 self.transport.write_all(&bytes)?;
170 Ok(bytes)
171 }
172
173 pub fn receive(&mut self, pd: &mut PdState) -> Result<Reply, Error> {
183 let (reply_code, _sqn, data) = self.recv_loop()?;
184 let now = self.clock.now_ms();
185 pd.mark_seen(now);
186 pd.bump_sqn();
187 Reply::decode(reply_code, &data)
188 }
189
190 fn recv_loop(&mut self) -> Result<(ReplyCode, Sqn, Vec<u8>), Error> {
198 let start = self.clock.now_ms();
199 let mut empty_reads = 0u8;
200 loop {
201 if let Some(packet) = self.try_parse_packet()? {
202 return Ok(packet);
203 }
204 let mut tmp = [0u8; RX_CHUNK_LEN];
205 let n = self.transport.read(&mut tmp)?;
206 if n > 0 {
207 self.rx_buf.extend_from_slice(&tmp[..n]);
208 empty_reads = 0;
209 continue;
210 }
211 if self.clock.now_ms().saturating_sub(start) >= self.reply_delay_ms as u64 {
212 return Err(Error::Timeout);
213 }
214 empty_reads = empty_reads.saturating_add(1);
215 if empty_reads >= MAX_EMPTY_READS {
216 return Err(Error::Timeout);
217 }
218 }
219 }
220
221 #[cfg_attr(feature = "_docs", aquamarine::aquamarine)]
234 pub fn exchange(
248 &mut self,
249 pd_addr: u8,
250 pd: &mut PdState,
251 command: &Command,
252 ) -> Result<ExchangeOutcome, Error> {
253 let now = self.clock.now_ms();
254 if pd.is_offline(now) {
255 return Ok(ExchangeOutcome::Offline);
256 }
257
258 let started = now;
259 let sqn = pd.next_sqn;
260 let mut attempts: u8 = 0;
261 let max = self.retry.max_retries;
262 let budget = self.retry.overall_budget_ms;
263
264 loop {
265 self.send_with_sqn(pd_addr, pd.use_crc, sqn, command)?;
266 match self.recv_one_with_sqn(sqn) {
267 Ok(reply) => {
268 let now = self.clock.now_ms();
269 pd.mark_seen(now);
270 if matches!(reply, Reply::Busy(_)) {
271 return Ok(ExchangeOutcome::Busy);
273 }
274 pd.bump_sqn();
275 return Ok(ExchangeOutcome::Reply(reply));
276 }
277 Err(Error::Timeout) => {
278 attempts += 1;
279 let now = self.clock.now_ms();
280 if pd.is_offline(now) {
281 return Ok(ExchangeOutcome::Offline);
282 }
283 if attempts > max {
284 return Ok(ExchangeOutcome::Timeout);
285 }
286 if budget != 0 && now.saturating_sub(started) >= budget as u64 {
287 return Ok(ExchangeOutcome::Timeout);
288 }
289 continue;
291 }
292 Err(other) => return Err(other),
293 }
294 }
295 }
296
297 fn recv_one_with_sqn(&mut self, expected_sqn: Sqn) -> Result<Reply, Error> {
304 let (reply_code, parsed_sqn, data) = self.recv_loop()?;
305 if reply_code != ReplyCode::Busy && parsed_sqn != expected_sqn {
306 return Err(Error::SqnMismatch {
307 expected: expected_sqn.value(),
308 got: parsed_sqn.value(),
309 });
310 }
311 Reply::decode(reply_code, &data)
312 }
313
314 fn try_parse_packet(&mut self) -> Result<Option<(ReplyCode, Sqn, Vec<u8>)>, Error> {
315 while let Some(som_pos) = self.rx_buf.iter().position(|&b| b == crate::SOM) {
316 self.rx_buf.drain(..som_pos);
317 match ParsedPacket::parse(&self.rx_buf) {
318 Ok((parsed, used)) => {
319 let code = ReplyCode::from_byte(parsed.code)?;
320 let sqn = parsed.ctrl.sqn;
321 let data = parsed.data.to_vec();
322 self.rx_buf.drain(..used);
323 return Ok(Some((code, sqn, data)));
324 }
325 Err(Error::Truncated { .. }) => return Ok(None),
326 Err(Error::BadSom(_)) => {
327 self.rx_buf.remove(0);
328 continue;
329 }
330 Err(Error::BadCrc { .. }) | Err(Error::BadChecksum { .. }) => {
332 self.rx_buf.remove(0);
333 continue;
334 }
335 Err(other) => return Err(other),
336 }
337 }
338 Ok(None)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use crate::clock::MockClock;
346 use crate::command::Poll;
347 use crate::transport::VecTransport;
348
349 #[test]
350 fn send_poll_emits_correct_bytes() {
351 let clock = MockClock::new();
352 let transport = VecTransport::new();
353 let mut acu = Acu::new(transport, clock);
354 let mut pd = PdState::default();
355 let bytes = acu.send_to(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
356 assert_eq!(bytes[0], crate::SOM);
357 assert_eq!(bytes[1], 0x05);
358 assert_eq!(bytes[4] & 0x0F, 0x04); }
360
361 #[test]
362 fn exchange_offline_when_silent() {
363 let clock = MockClock::new();
364 let transport = VecTransport::new();
365 let mut acu = Acu::new(transport, clock.clone());
366 acu.retry = RetryConfig {
367 max_retries: 0,
368 overall_budget_ms: 0,
369 };
370 let mut pd = PdState::default();
371 pd.mark_seen(0);
373 clock.set(crate::OFFLINE_THRESHOLD_MS as u64 + 1);
374 let outcome = acu.exchange(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
375 assert_eq!(outcome, ExchangeOutcome::Offline);
376 }
377
378 #[test]
379 fn timeout_then_no_more_retries_returns_timeout() {
380 let clock = MockClock::new();
381 let transport = VecTransport::new();
382 let mut acu = Acu::new(transport, clock.clone());
383 acu.retry = RetryConfig {
384 max_retries: 0,
385 overall_budget_ms: 0,
386 };
387 let mut pd = PdState::default();
388 pd.mark_seen(0);
389 clock.set(crate::REPLY_DELAY_MS as u64 + 1);
392 let outcome = acu.exchange(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
393 assert_eq!(outcome, ExchangeOutcome::Timeout);
394 }
395
396 #[test]
397 fn exchange_rejects_reply_with_wrong_sqn() {
398 let clock = MockClock::new();
401 let mut transport = VecTransport::new();
402 let stale = PacketBuilder::plain(
403 Address::reply(0x05).unwrap(),
404 ControlByte::new(Sqn::new(2).unwrap(), CtrlFlags::USE_CRC),
405 crate::reply::ReplyCode::Ack.as_byte(),
406 alloc::vec::Vec::new(),
407 )
408 .encode()
409 .unwrap();
410 transport.feed(&stale);
411
412 let mut acu = Acu::new(transport, clock);
413 acu.retry = RetryConfig {
414 max_retries: 0,
415 overall_budget_ms: 0,
416 };
417 let mut pd = PdState {
418 next_sqn: Sqn::new(1).unwrap(),
419 ..Default::default()
420 };
421 pd.mark_seen(0);
422
423 let err = acu
424 .exchange(0x05, &mut pd, &Command::Poll(Poll))
425 .unwrap_err();
426 assert!(matches!(
427 err,
428 Error::SqnMismatch {
429 expected: 1,
430 got: 2
431 }
432 ));
433 }
434
435 #[test]
436 fn exchange_accepts_busy_with_sqn_zero() {
437 let clock = MockClock::new();
440 let mut transport = VecTransport::new();
441 let busy = PacketBuilder::plain(
442 Address::reply(0x05).unwrap(),
443 ControlByte::new(Sqn::ZERO, CtrlFlags::USE_CRC),
444 crate::reply::ReplyCode::Busy.as_byte(),
445 alloc::vec::Vec::new(),
446 )
447 .encode()
448 .unwrap();
449 transport.feed(&busy);
450
451 let mut acu = Acu::new(transport, clock);
452 acu.retry = RetryConfig {
453 max_retries: 0,
454 overall_budget_ms: 0,
455 };
456 let mut pd = PdState {
457 next_sqn: Sqn::new(2).unwrap(),
458 ..Default::default()
459 };
460 pd.mark_seen(0);
461
462 let outcome = acu.exchange(0x05, &mut pd, &Command::Poll(Poll)).unwrap();
463 assert_eq!(outcome, ExchangeOutcome::Busy);
464 }
465}