1mod device;
2
3pub use device::{
4 WebUsbAutoClass, WebUsbAutoError, WebUsbAutoRx, WebUsbAutoState, WebUsbAutoTx,
5 WEBUSB_AUTO_PACKET_SIZE,
6};
7
8use embassy_futures::select::{select, select3, Either, Either3};
9use embassy_time::{with_timeout, Duration, Instant, Timer};
10use embedded_io_async::{Error, ErrorKind, Read, Write};
11
12use prns_core::interfaces::usb_auto::{
13 self as contract, Capabilities, InboundReaction, Message, NodeTag,
14};
15use prns_core::interfaces::{
16 ConnectionState, InterfaceDescriptor, InterfaceId, InterfaceKind, InterfaceStatus,
17};
18use prns_runtime::manifold::driver::EmbassyInterfaceStatus;
19use prns_runtime::manifold::interface_seam::{
20 Interface, InterfaceSeam, OutboundDisposition, OutboundDropReason,
21};
22
23const WRITE_TIMEOUT: Duration = Duration::from_millis(200);
24const IO_RETRY_DELAY: Duration = Duration::from_millis(100);
25const PRESENCE_PROBE_INTERVAL: Duration = Duration::from_secs(2);
26const PRESENCE_STRIKES_TO_DORMANT: u8 = 2;
27
28#[derive(Debug, PartialEq, Eq)]
29enum UsbLifecycle {
30 AwaitingHost,
31 Linked,
32 Degraded,
33 Failed,
34}
35
36impl UsbLifecycle {
37 fn is_linked(&self) -> bool {
38 match self {
39 Self::Linked | Self::Degraded => true,
40 Self::AwaitingHost | Self::Failed => false,
41 }
42 }
43
44 fn publish(&self, status: &EmbassyInterfaceStatus) {
45 let connection = match self {
46 Self::AwaitingHost => ConnectionState::Disconnected,
47 Self::Linked => ConnectionState::Connected,
48 Self::Degraded => ConnectionState::Degraded,
49 Self::Failed => ConnectionState::Failed,
50 };
51 status.set_connection(connection);
52 }
53
54 fn connect(&mut self, status: &EmbassyInterfaceStatus) {
55 *self = Self::Linked;
56 self.publish(status);
57 }
58
59 fn recover(&mut self, status: &EmbassyInterfaceStatus) {
60 if matches!(self, Self::Degraded) {
61 self.connect(status);
62 }
63 }
64
65 fn degrade(&mut self, status: &EmbassyInterfaceStatus) {
66 match self {
67 Self::Linked | Self::Degraded => {
68 *self = Self::Degraded;
69 self.publish(status);
70 }
71 Self::AwaitingHost | Self::Failed => {}
72 }
73 }
74
75 fn disconnect(&mut self, status: &EmbassyInterfaceStatus) {
76 if matches!(self, Self::Failed) {
77 return;
78 }
79 *self = Self::AwaitingHost;
80 self.publish(status);
81 }
82
83 fn fail(&mut self, status: &EmbassyInterfaceStatus) {
84 *self = Self::Failed;
85 self.publish(status);
86 }
87
88 fn disable(&mut self) {
89 if !matches!(self, Self::Failed) {
90 *self = Self::AwaitingHost;
91 }
92 }
93}
94
95#[derive(Debug, PartialEq, Eq)]
96enum ReadOutcome {
97 Bytes(usize),
98 RetryReady,
99 EndOfStream,
100 TransientFailure,
101 Disconnected,
102 Failed,
103}
104
105#[derive(Debug, PartialEq, Eq)]
106enum WriteOutcome {
107 Sent(usize),
108 TimedOut,
109 Disabled,
110 Disconnected,
111 TransientFailure,
112 Failed,
113 Rejected,
114}
115
116enum IoEvent<'a> {
117 Read(ReadOutcome),
118 Outbound(&'a [u8]),
119}
120
121enum IoPriority {
122 Read,
123 Outbound,
124}
125
126impl IoPriority {
127 fn alternate(&mut self) {
128 *self = match self {
129 Self::Read => Self::Outbound,
130 Self::Outbound => Self::Read,
131 };
132 }
133}
134
135#[derive(Debug, PartialEq, Eq)]
136enum PresenceVerdict {
137 Present,
138 SuspectedAbsent,
139 Absent,
140}
141
142pub struct UsbAutoDeviceInput<'a, R, W, P> {
143 pub rx: R,
144 pub tx: W,
145 pub status: &'a EmbassyInterfaceStatus,
146 pub host_present: P,
147}
148
149pub struct UsbAutoDevice<'a, R, W, P> {
150 id: InterfaceId,
151 rx: R,
152 tx: W,
153 node_tag: NodeTag,
154 status: &'a EmbassyInterfaceStatus,
155 host_present: P,
156}
157
158impl<'a, R, W, P> UsbAutoDevice<'a, R, W, P> {
159 #[must_use]
160 pub fn new(input: UsbAutoDeviceInput<'a, R, W, P>) -> Self {
161 let UsbAutoDeviceInput {
162 rx,
163 tx,
164 status,
165 host_present,
166 } = input;
167 let id = status.id();
168 Self {
169 id,
170 rx,
171 tx,
172 node_tag: contract::node_tag_for(id),
173 status,
174 host_present,
175 }
176 }
177}
178
179impl<R, W, P> Interface for UsbAutoDevice<'_, R, W, P>
180where
181 R: Read,
182 W: Write,
183 P: FnMut() -> bool,
184{
185 const HW_MTU: usize = prns_core::interfaces::usb_auto::DEVICE_USB_HW_MTU;
186 const KIND: InterfaceKind = InterfaceKind::UsbAutoDevice;
187
188 fn descriptor(&self) -> InterfaceDescriptor {
189 contract::device_descriptor(self.id)
190 }
191
192 fn channel_tag(&self) -> &[u8] {
193 self.id.as_bytes()
194 }
195
196 async fn run<Seam: InterfaceSeam>(self, mut seam: Seam) {
197 let UsbAutoDevice {
198 id: _,
199 mut rx,
200 mut tx,
201 node_tag,
202 status,
203 mut host_present,
204 } = self;
205 let mut decoder = contract::Decoder::new();
206 let mut read_buf = [0u8; contract::READ_CHUNK_BYTES];
207 let mut frame_buf = [0u8; contract::MAX_FRAMED_BYTES];
208 let mut lifecycle = UsbLifecycle::AwaitingHost;
209 let mut absent_probes = 0u8;
210 let mut read_retry_at = None;
211 let mut presence_probe_at = Instant::now() + PRESENCE_PROBE_INTERVAL;
212 let mut io_priority = IoPriority::Read;
213
214 lifecycle.publish(status);
215
216 loop {
217 if !status.is_enabled() {
218 lifecycle.disable();
219 decoder = contract::Decoder::new();
220 read_retry_at = None;
221 absent_probes = 0;
222 status.wait_until_enabled().await;
223 lifecycle.publish(status);
224 presence_probe_at = Instant::now() + PRESENCE_PROBE_INTERVAL;
225 }
226 match select3(
227 status.wait_until_disabled(),
228 Timer::at(presence_probe_at),
229 next_io(
230 &mut rx,
231 &mut read_buf,
232 read_retry_at,
233 &mut seam,
234 &io_priority,
235 ),
236 )
237 .await
238 {
239 Either3::First(()) => {}
240 Either3::Second(()) => {
241 presence_probe_at = Instant::now() + PRESENCE_PROBE_INTERVAL;
242 match presence_verdict(host_present(), &mut absent_probes) {
243 PresenceVerdict::Present | PresenceVerdict::SuspectedAbsent => {}
244 PresenceVerdict::Absent => {
245 decoder = contract::Decoder::new();
246 lifecycle.disconnect(status);
247 }
248 }
249 }
250 Either3::Third(event) => {
251 io_priority.alternate();
252 match event {
253 IoEvent::Read(ReadOutcome::Bytes(n)) => {
254 read_retry_at = None;
255 absent_probes = 0;
256 status.add_rx(n as u64);
257 lifecycle.recover(status);
258 for &byte in &read_buf[..n] {
259 let Ok(Some(frame)) = decoder.feed(byte) else {
260 continue;
261 };
262 if frame.is_empty() {
263 continue;
264 }
265 match contract::react_to(contract::decode_message(frame)) {
266 InboundReaction::AnswerHandshake => {
267 let ack = Message::HelloAck {
268 tag: node_tag,
269 capabilities: Capabilities::none(),
270 };
271 match write_message(&mut tx, &ack, &mut frame_buf, status)
272 .await
273 {
274 WriteOutcome::Sent(n) => {
275 status.add_tx(n as u64);
276 lifecycle.connect(status);
277 }
278 WriteOutcome::TimedOut
279 | WriteOutcome::TransientFailure => {
280 lifecycle.degrade(status);
281 }
282 WriteOutcome::Disconnected => {
283 decoder = contract::Decoder::new();
284 lifecycle.disconnect(status);
285 }
286 WriteOutcome::Failed | WriteOutcome::Rejected => {
287 decoder = contract::Decoder::new();
288 lifecycle.fail(status);
289 }
290 WriteOutcome::Disabled => {}
291 }
292 }
293 InboundReaction::Deliver(packet) => {
294 if lifecycle.is_linked() && !packet.is_empty() {
295 seam.next_inbound(packet).await;
296 }
297 }
298 InboundReaction::Ignore => {}
299 }
300 }
301 }
302 IoEvent::Read(ReadOutcome::RetryReady) => {
303 read_retry_at = None;
304 }
305 IoEvent::Read(ReadOutcome::EndOfStream | ReadOutcome::Disconnected) => {
306 decoder = contract::Decoder::new();
307 lifecycle.disconnect(status);
308 read_retry_at = Some(Instant::now() + IO_RETRY_DELAY);
309 }
310 IoEvent::Read(ReadOutcome::TransientFailure) => {
311 lifecycle.degrade(status);
312 read_retry_at = Some(Instant::now() + IO_RETRY_DELAY);
313 }
314 IoEvent::Read(ReadOutcome::Failed) => {
315 decoder = contract::Decoder::new();
316 lifecycle.fail(status);
317 read_retry_at = Some(Instant::now() + IO_RETRY_DELAY);
318 }
319 IoEvent::Outbound(out) => {
320 let disposition = if !lifecycle.is_linked() {
321 OutboundDisposition::Dropped(OutboundDropReason::Disconnected)
322 } else {
323 let data = Message::Data(out);
324 match write_message(&mut tx, &data, &mut frame_buf, status).await {
325 WriteOutcome::Sent(n) => {
326 status.add_tx(n as u64);
327 lifecycle.recover(status);
328 OutboundDisposition::Sent
329 }
330 WriteOutcome::TimedOut => {
331 lifecycle.degrade(status);
332 OutboundDisposition::Dropped(OutboundDropReason::TimedOut)
333 }
334 WriteOutcome::Disabled => {
335 OutboundDisposition::Dropped(OutboundDropReason::Disabled)
336 }
337 WriteOutcome::Disconnected => {
338 decoder = contract::Decoder::new();
339 lifecycle.disconnect(status);
340 OutboundDisposition::Dropped(
341 OutboundDropReason::Disconnected,
342 )
343 }
344 WriteOutcome::TransientFailure => {
345 lifecycle.degrade(status);
346 OutboundDisposition::Dropped(
347 OutboundDropReason::TransportFailure,
348 )
349 }
350 WriteOutcome::Failed => {
351 lifecycle.fail(status);
352 OutboundDisposition::Dropped(
353 OutboundDropReason::TransportFailure,
354 )
355 }
356 WriteOutcome::Rejected => {
357 lifecycle.fail(status);
358 OutboundDisposition::Dropped(OutboundDropReason::Rejected)
359 }
360 }
361 };
362 seam.complete_outbound(disposition);
363 }
364 }
365 }
366 }
367 }
368 }
369}
370
371async fn next_io<'a, R, Seam>(
372 rx: &'a mut R,
373 read_buf: &'a mut [u8; contract::READ_CHUNK_BYTES],
374 read_retry_at: Option<Instant>,
375 seam: &'a mut Seam,
376 priority: &IoPriority,
377) -> IoEvent<'a>
378where
379 R: Read,
380 Seam: InterfaceSeam,
381{
382 if matches!(priority, IoPriority::Outbound) {
383 return match select(seam.next_outbound(), read_once(rx, read_buf, read_retry_at)).await {
384 Either::First(out) => IoEvent::Outbound(out),
385 Either::Second(outcome) => IoEvent::Read(outcome),
386 };
387 }
388 match select(read_once(rx, read_buf, read_retry_at), seam.next_outbound()).await {
389 Either::First(outcome) => IoEvent::Read(outcome),
390 Either::Second(out) => IoEvent::Outbound(out),
391 }
392}
393
394async fn read_once<R: Read>(
395 rx: &mut R,
396 read_buf: &mut [u8; contract::READ_CHUNK_BYTES],
397 retry_at: Option<Instant>,
398) -> ReadOutcome {
399 if let Some(retry_at) = retry_at {
400 Timer::at(retry_at).await;
401 return ReadOutcome::RetryReady;
402 }
403 match rx.read(read_buf).await {
404 Ok(0) => ReadOutcome::EndOfStream,
405 Ok(n) => ReadOutcome::Bytes(n),
406 Err(error) => match classify_io_error(error.kind()) {
407 IoFailure::Transient => ReadOutcome::TransientFailure,
408 IoFailure::Disconnected => ReadOutcome::Disconnected,
409 IoFailure::Failed => ReadOutcome::Failed,
410 },
411 }
412}
413
414#[derive(Debug, PartialEq, Eq)]
415enum IoFailure {
416 Transient,
417 Disconnected,
418 Failed,
419}
420
421fn classify_io_error(kind: ErrorKind) -> IoFailure {
422 match kind {
423 ErrorKind::TimedOut | ErrorKind::Interrupted => IoFailure::Transient,
424 ErrorKind::NotFound
425 | ErrorKind::ConnectionRefused
426 | ErrorKind::ConnectionReset
427 | ErrorKind::ConnectionAborted
428 | ErrorKind::NotConnected
429 | ErrorKind::AddrNotAvailable
430 | ErrorKind::BrokenPipe => IoFailure::Disconnected,
431 _ => IoFailure::Failed,
432 }
433}
434
435fn presence_verdict(present: bool, absent_probes: &mut u8) -> PresenceVerdict {
436 if present {
437 *absent_probes = 0;
438 return PresenceVerdict::Present;
439 }
440 *absent_probes = absent_probes.saturating_add(1);
441 if *absent_probes >= PRESENCE_STRIKES_TO_DORMANT {
442 PresenceVerdict::Absent
443 } else {
444 PresenceVerdict::SuspectedAbsent
445 }
446}
447
448async fn write_message<W: Write>(
449 tx: &mut W,
450 message: &Message<'_>,
451 frame_buf: &mut [u8; contract::MAX_FRAMED_BYTES],
452 status: &EmbassyInterfaceStatus,
453) -> WriteOutcome {
454 let Ok(n) = message.write_framed(frame_buf) else {
455 return WriteOutcome::Rejected;
456 };
457 match select(
458 status.wait_until_disabled(),
459 with_timeout(WRITE_TIMEOUT, tx.write_all(&frame_buf[..n])),
460 )
461 .await
462 {
463 Either::First(()) => WriteOutcome::Disabled,
464 Either::Second(Err(_)) => WriteOutcome::TimedOut,
465 Either::Second(Ok(Ok(()))) => WriteOutcome::Sent(n),
466 Either::Second(Ok(Err(error))) => match classify_io_error(error.kind()) {
467 IoFailure::Transient => WriteOutcome::TransientFailure,
468 IoFailure::Disconnected => WriteOutcome::Disconnected,
469 IoFailure::Failed => WriteOutcome::Failed,
470 },
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use prns_core::interfaces::{FrameSink, InterfaceOriginKind, InterfaceStatus, IFAC_MAX_SIZE};
478 use prns_runtime::manifold::driver::{leaked_grant_lane, EmbassyInterfaceSeam};
479 use prns_runtime::manifold::grant::{GrantConsumer, GrantProducer};
480
481 use ::core::cell::{Cell, RefCell};
482 use ::core::convert::Infallible;
483 use ::core::future::pending;
484 use embassy_futures::block_on;
485 use embassy_futures::join::join;
486 use embassy_futures::select::{select, Either};
487 use embassy_futures::yield_now;
488 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
489 use embassy_sync::channel::Channel;
490 use embassy_time::{with_timeout, Duration};
491 use std::collections::VecDeque;
492
493 const WATCHDOG: Duration = Duration::from_secs(5);
494
495 const DEVICE_SLOT: usize = prns_core::interfaces::usb_auto::DEVICE_USB_HW_MTU + IFAC_MAX_SIZE;
496
497 struct MockStream<'a> {
498 buf: &'a RefCell<VecDeque<u8>>,
499 }
500
501 struct RecordingSeam<'a, S> {
502 inner: S,
503 dispositions: &'a RefCell<Vec<OutboundDisposition>>,
504 }
505
506 impl<S: InterfaceSeam> InterfaceSeam for RecordingSeam<'_, S> {
507 fn interface_origin(&self) -> InterfaceOriginKind {
508 self.inner.interface_origin()
509 }
510
511 fn fill_entropy(&mut self, bytes: &mut [u8]) {
512 self.inner.fill_entropy(bytes);
513 }
514
515 async fn inbound_sink(&mut self) -> &mut dyn FrameSink {
516 self.inner.inbound_sink().await
517 }
518
519 async fn commit_inbound(&mut self) {
520 self.inner.commit_inbound().await;
521 }
522
523 async fn next_inbound(&mut self, frame: &[u8]) {
524 self.inner.next_inbound(frame).await;
525 }
526
527 async fn next_outbound(&mut self) -> &[u8] {
528 self.inner.next_outbound().await
529 }
530
531 fn complete_outbound(&mut self, disposition: OutboundDisposition) {
532 self.inner.complete_outbound(disposition.clone());
533 self.dispositions.borrow_mut().push(disposition);
534 }
535 }
536
537 #[derive(Debug)]
538 struct MockIoError(ErrorKind);
539
540 impl embedded_io_async::Error for MockIoError {
541 fn kind(&self) -> ErrorKind {
542 self.0
543 }
544 }
545
546 enum MockReadAction {
547 Bytes(Vec<u8>),
548 EndOfStream,
549 Error(ErrorKind),
550 Pending,
551 }
552
553 struct ScriptedReader<'a> {
554 actions: &'a RefCell<VecDeque<MockReadAction>>,
555 calls: &'a Cell<usize>,
556 cancellations: &'a Cell<usize>,
557 }
558
559 struct CancellationGuard<'a> {
560 cancellations: &'a Cell<usize>,
561 }
562
563 impl Drop for CancellationGuard<'_> {
564 fn drop(&mut self) {
565 self.cancellations.set(self.cancellations.get() + 1);
566 }
567 }
568
569 impl embedded_io_async::ErrorType for ScriptedReader<'_> {
570 type Error = MockIoError;
571 }
572
573 impl Read for ScriptedReader<'_> {
574 async fn read(&mut self, out: &mut [u8]) -> Result<usize, Self::Error> {
575 self.calls.set(self.calls.get() + 1);
576 let action = self.actions.borrow_mut().pop_front();
577 match action {
578 Some(MockReadAction::Bytes(bytes)) => {
579 let n = bytes.len().min(out.len());
580 out[..n].copy_from_slice(&bytes[..n]);
581 Ok(n)
582 }
583 Some(MockReadAction::EndOfStream) => Ok(0),
584 Some(MockReadAction::Error(kind)) => Err(MockIoError(kind)),
585 Some(MockReadAction::Pending) | None => {
586 let _guard = CancellationGuard {
587 cancellations: self.cancellations,
588 };
589 pending().await
590 }
591 }
592 }
593 }
594
595 enum MockWriteAction {
596 Accept,
597 Error(ErrorKind),
598 Pending,
599 }
600
601 struct ScriptedWriter<'a> {
602 actions: &'a RefCell<VecDeque<MockWriteAction>>,
603 cancellations: &'a Cell<usize>,
604 }
605
606 impl embedded_io_async::ErrorType for ScriptedWriter<'_> {
607 type Error = MockIoError;
608 }
609
610 impl Write for ScriptedWriter<'_> {
611 async fn write(&mut self, data: &[u8]) -> Result<usize, Self::Error> {
612 let action = self.actions.borrow_mut().pop_front();
613 match action {
614 Some(MockWriteAction::Accept) => Ok(data.len()),
615 Some(MockWriteAction::Error(kind)) => Err(MockIoError(kind)),
616 Some(MockWriteAction::Pending) | None => {
617 let _guard = CancellationGuard {
618 cancellations: self.cancellations,
619 };
620 pending().await
621 }
622 }
623 }
624
625 async fn flush(&mut self) -> Result<(), Self::Error> {
626 Ok(())
627 }
628 }
629
630 impl embedded_io_async::ErrorType for MockStream<'_> {
631 type Error = Infallible;
632 }
633
634 impl Read for MockStream<'_> {
635 async fn read(&mut self, out: &mut [u8]) -> Result<usize, Self::Error> {
636 loop {
637 {
638 let mut queue = self.buf.borrow_mut();
639 if !queue.is_empty() {
640 let n = queue.len().min(out.len());
641 for slot in out.iter_mut().take(n) {
642 *slot = queue.pop_front().expect("non-empty");
643 }
644 return Ok(n);
645 }
646 }
647 yield_now().await;
648 }
649 }
650 }
651
652 impl Write for MockStream<'_> {
653 async fn write(&mut self, data: &[u8]) -> Result<usize, Self::Error> {
654 self.buf.borrow_mut().extend(data.iter().copied());
655 Ok(data.len())
656 }
657 }
658
659 fn device_id() -> InterfaceId {
660 InterfaceId::new([0xD0; 8])
661 }
662
663 async fn read_until<T>(
664 wire: &RefCell<VecDeque<u8>>,
665 decoder: &mut contract::Decoder,
666 mut pick: impl FnMut(Message<'_>) -> Option<T>,
667 ) -> T {
668 loop {
669 let byte = loop {
670 if let Some(byte) = wire.borrow_mut().pop_front() {
671 break byte;
672 }
673 yield_now().await;
674 };
675 if let Ok(Some(frame)) = decoder.feed(byte) {
676 if !frame.is_empty() {
677 if let Ok(message) = contract::decode_message(frame) {
678 if let Some(picked) = pick(message) {
679 return picked;
680 }
681 }
682 }
683 }
684 }
685 }
686
687 #[test]
688 fn the_device_handshakes_a_host_then_carries_data_both_ways() {
689 let host_to_device = RefCell::new(VecDeque::new());
690 let device_to_host = RefCell::new(VecDeque::new());
691 let dispositions = RefCell::new(Vec::new());
692 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Initializing);
693
694 let notify: Channel<CriticalSectionRawMutex, InterfaceId, 2> = Channel::new();
695 let (in_tx, mut in_rx) = leaked_grant_lane::<DEVICE_SLOT>(2);
696 let (mut out_tx, out_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
697
698 block_on(async {
699 let device = UsbAutoDevice::new(UsbAutoDeviceInput {
700 rx: MockStream {
701 buf: &host_to_device,
702 },
703 tx: MockStream {
704 buf: &device_to_host,
705 },
706 status: &status,
707 host_present: || true,
708 });
709 let inner =
710 EmbassyInterfaceSeam::new(device_id(), in_tx, notify.sender(), out_rx, |bytes| {
711 bytes.fill(0)
712 });
713 let seam = RecordingSeam {
714 inner,
715 dispositions: &dispositions,
716 };
717 let device_run = device.run(seam);
718
719 let driver = async {
720 let mut frame = [0u8; contract::MAX_FRAMED_BYTES];
721 let mut decoder = contract::Decoder::new();
722
723 let hello = Message::Hello(Capabilities::host());
724 let n = hello.write_framed(&mut frame).expect("frames the hello");
725 host_to_device
726 .borrow_mut()
727 .extend(frame[..n].iter().copied());
728
729 read_until(&device_to_host, &mut decoder, |message| {
730 matches!(message, Message::HelloAck { .. }).then_some(())
731 })
732 .await;
733 assert_eq!(status.connection(), ConnectionState::Connected);
734
735 let inbound_packet = [0xAAu8, 0xBB, 0xCC, 0xDD];
736 let data = Message::Data(&inbound_packet);
737 let n = data.write_framed(&mut frame).expect("frames the data");
738 host_to_device
739 .borrow_mut()
740 .extend(frame[..n].iter().copied());
741 assert_eq!(notify.receive().await, device_id());
742 let received = in_rx.peek().await;
743 assert_eq!(received.frame(), &inbound_packet);
744 in_rx.release();
745
746 let outbound_packet = [0x11u8, 0x22, 0x33];
747 out_tx.grant().await.fill_for(device_id(), &outbound_packet);
748 out_tx.commit();
749 let delivered =
750 read_until(&device_to_host, &mut decoder, |message| match message {
751 Message::Data(packet) => Some(packet.to_vec()),
752 _ => None,
753 })
754 .await;
755 assert_eq!(delivered, outbound_packet);
756 while dispositions.borrow().is_empty() {
757 yield_now().await;
758 }
759 assert_eq!(
760 dispositions.borrow().as_slice(),
761 &[OutboundDisposition::Sent]
762 );
763 let next = with_timeout(WATCHDOG, out_tx.grant())
764 .await
765 .expect("completed outbound releases its lane slot");
766 next.fill_for(device_id(), &[0x44]);
767 };
768
769 match select(device_run, with_timeout(WATCHDOG, driver)).await {
770 Either::Second(result) => result.expect("the link completes before the watchdog"),
771 Either::First(()) => unreachable!("the device loop never returns"),
772 }
773 });
774 }
775
776 #[test]
777 fn outbound_while_unlinked_is_typed_and_releases_its_lane_slot() {
778 let host_to_device = RefCell::new(VecDeque::new());
779 let device_to_host = RefCell::new(VecDeque::new());
780 let dispositions = RefCell::new(Vec::new());
781 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Initializing);
782 let notify: Channel<CriticalSectionRawMutex, InterfaceId, 1> = Channel::new();
783 let (in_tx, _in_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
784 let (mut out_tx, out_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
785
786 block_on(async {
787 let device = UsbAutoDevice::new(UsbAutoDeviceInput {
788 rx: MockStream {
789 buf: &host_to_device,
790 },
791 tx: MockStream {
792 buf: &device_to_host,
793 },
794 status: &status,
795 host_present: || true,
796 });
797 let inner =
798 EmbassyInterfaceSeam::new(device_id(), in_tx, notify.sender(), out_rx, |bytes| {
799 bytes.fill(0)
800 });
801 let seam = RecordingSeam {
802 inner,
803 dispositions: &dispositions,
804 };
805 let device_run = device.run(seam);
806
807 let driver = async {
808 out_tx.grant().await.fill_for(device_id(), &[0x55]);
809 out_tx.commit();
810 while dispositions.borrow().is_empty() {
811 yield_now().await;
812 }
813 assert_eq!(
814 dispositions.borrow().as_slice(),
815 &[OutboundDisposition::Dropped(
816 OutboundDropReason::Disconnected
817 )]
818 );
819 with_timeout(WATCHDOG, out_tx.grant())
820 .await
821 .expect("discarded outbound releases its lane slot");
822 };
823
824 match select(device_run, with_timeout(WATCHDOG, driver)).await {
825 Either::Second(result) => {
826 result.expect("the discard completes before the watchdog")
827 }
828 Either::First(()) => unreachable!("the device loop never returns"),
829 }
830 });
831 }
832
833 #[test]
834 fn read_outcomes_distinguish_bytes_eof_transient_disconnect_and_failure() {
835 let actions = RefCell::new(VecDeque::from([
836 MockReadAction::Bytes(vec![0xAA, 0xBB]),
837 MockReadAction::EndOfStream,
838 MockReadAction::Error(ErrorKind::Interrupted),
839 MockReadAction::Error(ErrorKind::NotConnected),
840 MockReadAction::Error(ErrorKind::Other),
841 ]));
842 let calls = Cell::new(0);
843 let cancellations = Cell::new(0);
844 let mut reader = ScriptedReader {
845 actions: &actions,
846 calls: &calls,
847 cancellations: &cancellations,
848 };
849 let mut read_buf = [0u8; contract::READ_CHUNK_BYTES];
850
851 block_on(async {
852 assert_eq!(
853 read_once(&mut reader, &mut read_buf, None).await,
854 ReadOutcome::Bytes(2)
855 );
856 assert_eq!(&read_buf[..2], &[0xAA, 0xBB]);
857 assert_eq!(
858 read_once(&mut reader, &mut read_buf, None).await,
859 ReadOutcome::EndOfStream
860 );
861 assert_eq!(
862 read_once(&mut reader, &mut read_buf, None).await,
863 ReadOutcome::TransientFailure
864 );
865 assert_eq!(
866 read_once(&mut reader, &mut read_buf, None).await,
867 ReadOutcome::Disconnected
868 );
869 assert_eq!(
870 read_once(&mut reader, &mut read_buf, None).await,
871 ReadOutcome::Failed
872 );
873 });
874 assert_eq!(calls.get(), 5);
875 assert_eq!(cancellations.get(), 0);
876 }
877
878 #[test]
879 fn retry_backoff_does_not_poll_an_immediately_failing_reader() {
880 let actions = RefCell::new(VecDeque::from([MockReadAction::Error(ErrorKind::Other)]));
881 let calls = Cell::new(0);
882 let cancellations = Cell::new(0);
883 let mut reader = ScriptedReader {
884 actions: &actions,
885 calls: &calls,
886 cancellations: &cancellations,
887 };
888 let mut read_buf = [0u8; contract::READ_CHUNK_BYTES];
889
890 block_on(async {
891 assert_eq!(
892 read_once(&mut reader, &mut read_buf, None).await,
893 ReadOutcome::Failed
894 );
895 assert_eq!(
896 read_once(&mut reader, &mut read_buf, Some(Instant::now())).await,
897 ReadOutcome::RetryReady
898 );
899 });
900 assert_eq!(calls.get(), 1);
901 }
902
903 #[test]
904 fn disabling_cancels_a_blocked_write_before_its_timeout() {
905 let actions = RefCell::new(VecDeque::from([MockWriteAction::Pending]));
906 let cancellations = Cell::new(0);
907 let mut writer = ScriptedWriter {
908 actions: &actions,
909 cancellations: &cancellations,
910 };
911 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Connected);
912 let mut frame_buf = [0u8; contract::MAX_FRAMED_BYTES];
913
914 block_on(async {
915 let (outcome, ()) = join(
916 write_message(
917 &mut writer,
918 &Message::Data(&[0x11]),
919 &mut frame_buf,
920 &status,
921 ),
922 async {
923 yield_now().await;
924 status.disable();
925 },
926 )
927 .await;
928 assert_eq!(outcome, WriteOutcome::Disabled);
929 });
930 assert_eq!(cancellations.get(), 1);
931 }
932
933 #[test]
934 fn write_outcomes_preserve_transport_failure_structure() {
935 let actions = RefCell::new(VecDeque::from([
936 MockWriteAction::Accept,
937 MockWriteAction::Error(ErrorKind::NotConnected),
938 MockWriteAction::Error(ErrorKind::Interrupted),
939 MockWriteAction::Error(ErrorKind::Other),
940 ]));
941 let cancellations = Cell::new(0);
942 let mut writer = ScriptedWriter {
943 actions: &actions,
944 cancellations: &cancellations,
945 };
946 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Connected);
947 let mut frame_buf = [0u8; contract::MAX_FRAMED_BYTES];
948
949 block_on(async {
950 assert!(matches!(
951 write_message(
952 &mut writer,
953 &Message::Data(&[0x11]),
954 &mut frame_buf,
955 &status
956 )
957 .await,
958 WriteOutcome::Sent(_)
959 ));
960 assert_eq!(
961 write_message(
962 &mut writer,
963 &Message::Data(&[0x22]),
964 &mut frame_buf,
965 &status
966 )
967 .await,
968 WriteOutcome::Disconnected
969 );
970 assert_eq!(
971 write_message(
972 &mut writer,
973 &Message::Data(&[0x33]),
974 &mut frame_buf,
975 &status
976 )
977 .await,
978 WriteOutcome::TransientFailure
979 );
980 assert_eq!(
981 write_message(
982 &mut writer,
983 &Message::Data(&[0x44]),
984 &mut frame_buf,
985 &status
986 )
987 .await,
988 WriteOutcome::Failed
989 );
990 });
991 }
992
993 #[test]
994 fn blocked_write_times_out_and_cancels_the_transport_future() {
995 let actions = RefCell::new(VecDeque::from([MockWriteAction::Pending]));
996 let cancellations = Cell::new(0);
997 let mut writer = ScriptedWriter {
998 actions: &actions,
999 cancellations: &cancellations,
1000 };
1001 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Connected);
1002 let mut frame_buf = [0u8; contract::MAX_FRAMED_BYTES];
1003
1004 block_on(async {
1005 assert_eq!(
1006 write_message(
1007 &mut writer,
1008 &Message::Data(&[0x77]),
1009 &mut frame_buf,
1010 &status
1011 )
1012 .await,
1013 WriteOutcome::TimedOut
1014 );
1015 });
1016 assert_eq!(cancellations.get(), 1);
1017 }
1018
1019 #[test]
1020 fn failure_state_survives_disable_and_reenable() {
1021 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Initializing);
1022 let mut lifecycle = UsbLifecycle::Failed;
1023 lifecycle.publish(&status);
1024 assert_eq!(status.connection(), ConnectionState::Failed);
1025
1026 status.disable();
1027 lifecycle.disable();
1028 assert_eq!(status.connection(), ConnectionState::Disabled);
1029
1030 status.enable();
1031 lifecycle.publish(&status);
1032 assert_eq!(status.connection(), ConnectionState::Failed);
1033 }
1034
1035 #[test]
1036 fn data_before_handshake_is_not_delivered() {
1037 let host_to_device = RefCell::new(VecDeque::new());
1038 let device_to_host = RefCell::new(VecDeque::new());
1039 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Initializing);
1040 let notify: Channel<CriticalSectionRawMutex, InterfaceId, 1> = Channel::new();
1041 let (in_tx, _in_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
1042 let (_out_tx, out_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
1043 let mut frame = [0u8; contract::MAX_FRAMED_BYTES];
1044 let n = Message::Data(&[0xAA, 0xBB])
1045 .write_framed(&mut frame)
1046 .expect("frames pre-handshake data");
1047 host_to_device
1048 .borrow_mut()
1049 .extend(frame[..n].iter().copied());
1050
1051 block_on(async {
1052 let device = UsbAutoDevice::new(UsbAutoDeviceInput {
1053 rx: MockStream {
1054 buf: &host_to_device,
1055 },
1056 tx: MockStream {
1057 buf: &device_to_host,
1058 },
1059 status: &status,
1060 host_present: || true,
1061 });
1062 let seam =
1063 EmbassyInterfaceSeam::new(device_id(), in_tx, notify.sender(), out_rx, |bytes| {
1064 bytes.fill(0)
1065 });
1066 match select(
1067 device.run(seam),
1068 with_timeout(Duration::from_millis(20), notify.receive()),
1069 )
1070 .await
1071 {
1072 Either::Second(result) => {
1073 assert!(result.is_err(), "pre-handshake data reached the manifold");
1074 }
1075 Either::First(()) => unreachable!("the device loop never returns"),
1076 }
1077 });
1078 assert_eq!(status.connection(), ConnectionState::Disconnected);
1079 }
1080
1081 #[test]
1082 fn failed_hello_ack_never_publishes_connected() {
1083 let mut frame = [0u8; contract::MAX_FRAMED_BYTES];
1084 let n = Message::Hello(Capabilities::host())
1085 .write_framed(&mut frame)
1086 .expect("frames hello");
1087 let read_actions = RefCell::new(VecDeque::from([
1088 MockReadAction::Bytes(frame[..n].to_vec()),
1089 MockReadAction::Pending,
1090 ]));
1091 let read_calls = Cell::new(0);
1092 let read_cancellations = Cell::new(0);
1093 let write_actions = RefCell::new(VecDeque::from([MockWriteAction::Error(
1094 ErrorKind::NotConnected,
1095 )]));
1096 let write_cancellations = Cell::new(0);
1097 let status = EmbassyInterfaceStatus::new(device_id(), ConnectionState::Initializing);
1098 let notify: Channel<CriticalSectionRawMutex, InterfaceId, 1> = Channel::new();
1099 let (in_tx, _in_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
1100 let (_out_tx, out_rx) = leaked_grant_lane::<DEVICE_SLOT>(1);
1101
1102 block_on(async {
1103 let device = UsbAutoDevice::new(UsbAutoDeviceInput {
1104 rx: ScriptedReader {
1105 actions: &read_actions,
1106 calls: &read_calls,
1107 cancellations: &read_cancellations,
1108 },
1109 tx: ScriptedWriter {
1110 actions: &write_actions,
1111 cancellations: &write_cancellations,
1112 },
1113 status: &status,
1114 host_present: || true,
1115 });
1116 let seam =
1117 EmbassyInterfaceSeam::new(device_id(), in_tx, notify.sender(), out_rx, |bytes| {
1118 bytes.fill(0)
1119 });
1120 match select(device.run(seam), Timer::after(Duration::from_millis(20))).await {
1121 Either::Second(()) => {}
1122 Either::First(()) => unreachable!("the device loop never returns"),
1123 }
1124 });
1125 assert_eq!(status.connection(), ConnectionState::Disconnected);
1126 assert!(write_actions.borrow().is_empty());
1127 }
1128
1129 #[test]
1130 fn cancelled_pending_read_consumes_no_scripted_data() {
1131 let actions = RefCell::new(VecDeque::from([
1132 MockReadAction::Pending,
1133 MockReadAction::Bytes(vec![0xAB]),
1134 ]));
1135 let calls = Cell::new(0);
1136 let cancellations = Cell::new(0);
1137 let mut reader = ScriptedReader {
1138 actions: &actions,
1139 calls: &calls,
1140 cancellations: &cancellations,
1141 };
1142 let mut read_buf = [0u8; contract::READ_CHUNK_BYTES];
1143
1144 block_on(async {
1145 assert!(matches!(
1146 select(read_once(&mut reader, &mut read_buf, None), async {
1147 yield_now().await
1148 })
1149 .await,
1150 Either::Second(())
1151 ));
1152 assert_eq!(cancellations.get(), 1);
1153 assert_eq!(
1154 read_once(&mut reader, &mut read_buf, None).await,
1155 ReadOutcome::Bytes(1)
1156 );
1157 assert_eq!(read_buf[0], 0xAB);
1158 });
1159 }
1160
1161 #[test]
1162 fn presence_present_clears_strikes_and_holds_the_link() {
1163 let mut absent = 0u8;
1164 assert_eq!(
1165 presence_verdict(true, &mut absent),
1166 PresenceVerdict::Present
1167 );
1168 assert_eq!(absent, 0);
1169
1170 absent = 1;
1171 assert_eq!(
1172 presence_verdict(true, &mut absent),
1173 PresenceVerdict::Present
1174 );
1175 assert_eq!(absent, 0);
1176 }
1177
1178 #[test]
1179 fn presence_absent_disconnects_only_after_the_strike_threshold() {
1180 let mut absent = 0u8;
1181 assert_eq!(
1182 presence_verdict(false, &mut absent),
1183 PresenceVerdict::SuspectedAbsent
1184 );
1185 assert_eq!(absent, 1);
1186 assert_eq!(
1187 presence_verdict(false, &mut absent),
1188 PresenceVerdict::Absent
1189 );
1190
1191 let mut recovered = 1u8;
1192 assert_eq!(
1193 presence_verdict(true, &mut recovered),
1194 PresenceVerdict::Present
1195 );
1196 assert_eq!(
1197 presence_verdict(false, &mut recovered),
1198 PresenceVerdict::SuspectedAbsent
1199 );
1200 }
1201}