Skip to main content

prns_interfaces_embassy/usb_auto/
mod.rs

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}