Skip to main content

android_usb_serial/
fake.rs

1//! In-memory transport for golden parity and on-device harness.
2//!
3//! Enable with Cargo feature `fake-transport`. Script control/bulk responses, then open a port
4//! with [`crate::open_port`] exactly as on hardware.
5
6use crate::error::{ReadOutcome, Result, UsbSerialError};
7use crate::transport::{BulkIn, BulkOut, ControlRequest, EndpointInfo, InterfaceInfo, Transport};
8use std::collections::VecDeque;
9use std::sync::{Arc, Mutex};
10
11/// Captured control transfer (for golden parity assertions).
12#[derive(Debug, Clone)]
13pub struct RecordedControl {
14    pub request_type: u8,
15    pub request: u8,
16    pub value: u16,
17    pub index: u16,
18    pub data: Vec<u8>,
19}
20
21/// Captured bulk OUT payload.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct RecordedBulkOut {
24    pub endpoint: u8,
25    pub data: Vec<u8>,
26}
27
28#[derive(Debug, Default)]
29struct FakeState {
30    device_descriptor: [u8; 18],
31    raw_descriptors: Vec<u8>,
32    interfaces: Vec<InterfaceInfo>,
33    endpoints: Vec<(u8, EndpointInfo)>,
34    recorded: Vec<RecordedControl>,
35    recorded_bulk_out: Vec<RecordedBulkOut>,
36    control_in_responses: Vec<Vec<u8>>,
37    interrupt_in_queue: VecDeque<u8>,
38    rx_queue: VecDeque<u8>,
39    tx_log: Vec<u8>,
40    bulk_read_error: Option<String>,
41    claimed: Vec<u8>,
42    /// Mimic nusb: endpoint address may only be opened once until dropped.
43    open_endpoints: Vec<u8>,
44    /// When set, bulk OUT is parsed as AT lines (`…\r`) and auto-responses go to `rx_queue`.
45    at_modem: bool,
46    at_line_buf: Vec<u8>,
47}
48
49/// Thread-safe fake USB device for tests ([`crate::Transport`] implementor).
50#[derive(Debug, Clone)]
51pub struct FakeTransport {
52    inner: Arc<Mutex<FakeState>>,
53}
54
55impl Default for FakeTransport {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl FakeTransport {
62    /// Same as [`Self::cdc_single_iface`].
63    pub fn new() -> Self {
64        Self::cdc_single_iface()
65    }
66
67    /// Single CDC ACM interface with bulk IN/OUT endpoints (castrated single-iface layout).
68    pub fn cdc_single_iface() -> Self {
69        let t = Self {
70            inner: Arc::new(Mutex::new(FakeState::default())),
71        };
72        {
73            let mut s = t.inner.lock().unwrap();
74            s.device_descriptor = [
75                18, 1, 0x00, 0x02, 0x02, 0x00, 0x00, 64, 0x34, 0x12, 0x78, 0x56, 0x00, 0x01, 0x01,
76                0x02, 0x00, 1,
77            ];
78            s.interfaces = vec![InterfaceInfo {
79                id: 0,
80                class: 2,
81                subclass: 2,
82                protocol: 0,
83            }];
84            s.endpoints = vec![
85                (
86                    0,
87                    EndpointInfo {
88                        address: 0x81,
89                        attributes: 2,
90                        max_packet_size: 64,
91                        interval: 0,
92                    },
93                ),
94                (
95                    0,
96                    EndpointInfo {
97                        address: 0x02,
98                        attributes: 2,
99                        max_packet_size: 64,
100                        interval: 0,
101                    },
102                ),
103            ];
104        }
105        t
106    }
107
108    /// FT232R-shaped single-interface layout (`0403:6001`).
109    pub fn ftdi_ft232r() -> Self {
110        let t = Self::cdc_single_iface();
111        t.set_vendor_product(0x0403, 0x6001);
112        t.set_interfaces(vec![InterfaceInfo {
113            id: 0,
114            class: 255,
115            subclass: 0,
116            protocol: 0,
117        }]);
118        t.configure_endpoints(&[(
119            0,
120            vec![
121                EndpointInfo {
122                    address: 0x81,
123                    attributes: 2,
124                    max_packet_size: 64,
125                    interval: 0,
126                },
127                EndpointInfo {
128                    address: 0x02,
129                    attributes: 2,
130                    max_packet_size: 64,
131                    interval: 0,
132                },
133            ],
134        )]);
135        t
136    }
137
138    pub fn ftdi_ft2232() -> Self {
139        let t = Self::ftdi_ft232r();
140        t.set_vendor_product(0x0403, 0x6010);
141        t.set_interfaces(vec![
142            InterfaceInfo {
143                id: 0,
144                class: 255,
145                subclass: 0,
146                protocol: 0,
147            },
148            InterfaceInfo {
149                id: 1,
150                class: 255,
151                subclass: 0,
152                protocol: 0,
153            },
154        ]);
155        t.configure_endpoints(&[
156            (
157                0,
158                vec![
159                    EndpointInfo {
160                        address: 0x81,
161                        attributes: 2,
162                        max_packet_size: 64,
163                        interval: 0,
164                    },
165                    EndpointInfo {
166                        address: 0x02,
167                        attributes: 2,
168                        max_packet_size: 64,
169                        interval: 0,
170                    },
171                ],
172            ),
173            (
174                1,
175                vec![
176                    EndpointInfo {
177                        address: 0x83,
178                        attributes: 2,
179                        max_packet_size: 64,
180                        interval: 0,
181                    },
182                    EndpointInfo {
183                        address: 0x04,
184                        attributes: 2,
185                        max_packet_size: 64,
186                        interval: 0,
187                    },
188                ],
189            ),
190        ]);
191        t.patch_device_descriptor(|d| d[13] = 7);
192        t
193    }
194
195    pub fn cp2102() -> Self {
196        Self::ftdi_ft232r().also(|t| t.set_vendor_product(0x10C4, 0xEA60))
197    }
198
199    pub fn cp2105() -> Self {
200        let t = Self::cp2102();
201        t.set_vendor_product(0x10C4, 0xEA70);
202        t.set_interfaces(vec![
203            InterfaceInfo {
204                id: 0,
205                class: 255,
206                subclass: 0,
207                protocol: 0,
208            },
209            InterfaceInfo {
210                id: 1,
211                class: 255,
212                subclass: 0,
213                protocol: 0,
214            },
215        ]);
216        t.configure_endpoints(&[
217            (
218                0,
219                vec![
220                    EndpointInfo {
221                        address: 0x81,
222                        attributes: 2,
223                        max_packet_size: 64,
224                        interval: 0,
225                    },
226                    EndpointInfo {
227                        address: 0x02,
228                        attributes: 2,
229                        max_packet_size: 64,
230                        interval: 0,
231                    },
232                ],
233            ),
234            (
235                1,
236                vec![
237                    EndpointInfo {
238                        address: 0x83,
239                        attributes: 2,
240                        max_packet_size: 64,
241                        interval: 0,
242                    },
243                    EndpointInfo {
244                        address: 0x04,
245                        attributes: 2,
246                        max_packet_size: 64,
247                        interval: 0,
248                    },
249                ],
250            ),
251        ]);
252        t
253    }
254
255    pub fn ch340_dual_iface() -> Self {
256        let t = Self::cdc_single_iface();
257        t.set_vendor_product(0x1A86, 0x7523);
258        t.set_interfaces(vec![
259            InterfaceInfo {
260                id: 0,
261                class: 255,
262                subclass: 0,
263                protocol: 0,
264            },
265            InterfaceInfo {
266                id: 1,
267                class: 255,
268                subclass: 0,
269                protocol: 0,
270            },
271        ]);
272        t.configure_endpoints(&[(
273            1,
274            vec![
275                EndpointInfo {
276                    address: 0x82,
277                    attributes: 2,
278                    max_packet_size: 64,
279                    interval: 0,
280                },
281                EndpointInfo {
282                    address: 0x03,
283                    attributes: 2,
284                    max_packet_size: 64,
285                    interval: 0,
286                },
287            ],
288        )]);
289        t
290    }
291
292    pub fn pl2303_hx() -> Self {
293        let t = Self::cdc_single_iface();
294        t.set_vendor_product(0x067B, 0x2303);
295        t.patch_device_descriptor(|d| d[4] = 0);
296        t.configure_endpoints(&[(
297            0,
298            vec![
299                EndpointInfo {
300                    address: 0x81,
301                    attributes: 3,
302                    max_packet_size: 64,
303                    interval: 1,
304                },
305                EndpointInfo {
306                    address: 0x02,
307                    attributes: 2,
308                    max_packet_size: 64,
309                    interval: 0,
310                },
311                EndpointInfo {
312                    address: 0x83,
313                    attributes: 2,
314                    max_packet_size: 64,
315                    interval: 0,
316                },
317            ],
318        )]);
319        t.set_interfaces(vec![InterfaceInfo {
320            id: 0,
321            class: 255,
322            subclass: 0,
323            protocol: 0,
324        }]);
325        t
326    }
327
328    pub fn pl2303_hxn() -> Self {
329        let t = Self::pl2303_hx();
330        t.patch_device_descriptor(|d| {
331            d[4] = 0x00;
332            d[7] = 64;
333            d[2] = 0x00;
334            d[3] = 0x02;
335        });
336        t
337    }
338
339    pub fn pl2303_type01() -> Self {
340        let t = Self::pl2303_hx();
341        t.patch_device_descriptor(|d| d[4] = 0x02);
342        t
343    }
344
345    pub fn pl2303_ta() -> Self {
346        let t = Self::pl2303_hx();
347        t.patch_device_descriptor(|d| {
348            d[2] = 0x00;
349            d[3] = 0x02;
350            d[12] = 0x00;
351            d[13] = 0x03;
352        });
353        t
354    }
355
356    pub fn cdc_iad() -> Self {
357        let t = Self::cdc_single_iface();
358        t.set_vendor_product(0x2341, 0x0043);
359        t.patch_device_descriptor(|d| {
360            d[4] = 0xEF;
361            d[5] = 0x02;
362            d[6] = 0x01;
363        });
364        let i0 = InterfaceInfo {
365            id: 0,
366            class: 2,
367            subclass: 2,
368            protocol: 0,
369        };
370        let i1 = InterfaceInfo {
371            id: 1,
372            class: 10,
373            subclass: 0,
374            protocol: 0,
375        };
376        t.set_interfaces(vec![i0, i1]);
377        t.configure_endpoints(&[
378            (
379                0,
380                vec![EndpointInfo {
381                    address: 0x81,
382                    attributes: 3,
383                    max_packet_size: 64,
384                    interval: 1,
385                }],
386            ),
387            (
388                1,
389                vec![
390                    EndpointInfo {
391                        address: 0x82,
392                        attributes: 2,
393                        max_packet_size: 64,
394                        interval: 0,
395                    },
396                    EndpointInfo {
397                        address: 0x03,
398                        attributes: 2,
399                        max_packet_size: 64,
400                        interval: 0,
401                    },
402                ],
403            ),
404        ]);
405        // minimal IAD descriptor in raw config
406        t.set_raw_descriptors(vec![
407            9, 4, 0, 0, 1, 2, 2, 0, 0, 7, 5, 0x81, 3, 64, 0, 1, 9, 4, 1, 0, 2, 10, 0, 0, 7, 5,
408            0x82, 2, 64, 0, 7, 5, 0x03, 2, 64, 0, 8, 11, 0, 2, 2, 2, 0, 0,
409        ]);
410        t
411    }
412
413    pub fn cdc_multi() -> Self {
414        let t = Self::cdc_iad();
415        t.set_interfaces(vec![
416            InterfaceInfo {
417                id: 0,
418                class: 2,
419                subclass: 2,
420                protocol: 0,
421            },
422            InterfaceInfo {
423                id: 1,
424                class: 10,
425                subclass: 0,
426                protocol: 0,
427            },
428            InterfaceInfo {
429                id: 2,
430                class: 2,
431                subclass: 2,
432                protocol: 0,
433            },
434            InterfaceInfo {
435                id: 3,
436                class: 10,
437                subclass: 0,
438                protocol: 0,
439            },
440        ]);
441        t.configure_endpoints(&[
442            (
443                1,
444                vec![
445                    EndpointInfo {
446                        address: 0x82,
447                        attributes: 2,
448                        max_packet_size: 64,
449                        interval: 0,
450                    },
451                    EndpointInfo {
452                        address: 0x03,
453                        attributes: 2,
454                        max_packet_size: 64,
455                        interval: 0,
456                    },
457                ],
458            ),
459            (
460                3,
461                vec![
462                    EndpointInfo {
463                        address: 0x84,
464                        attributes: 2,
465                        max_packet_size: 64,
466                        interval: 0,
467                    },
468                    EndpointInfo {
469                        address: 0x05,
470                        attributes: 2,
471                        max_packet_size: 64,
472                        interval: 0,
473                    },
474                ],
475            ),
476        ]);
477        t
478    }
479
480    pub fn gsm_modem() -> Self {
481        let t = Self::cdc_single_iface();
482        t.set_vendor_product(0x1782, 0x4D10);
483        t.set_interfaces(vec![InterfaceInfo {
484            id: 0,
485            class: 255,
486            subclass: 0,
487            protocol: 0,
488        }]);
489        t.configure_endpoints(&[(
490            0,
491            vec![
492                EndpointInfo {
493                    address: 0x81,
494                    attributes: 2,
495                    max_packet_size: 64,
496                    interval: 0,
497                },
498                EndpointInfo {
499                    address: 0x02,
500                    attributes: 2,
501                    max_packet_size: 64,
502                    interval: 0,
503                },
504            ],
505        )]);
506        t
507    }
508
509    pub fn chrome_ccd_3port() -> Self {
510        let t = Self::cdc_single_iface();
511        t.set_vendor_product(0x18D1, 0x5014);
512        let mut ifaces = Vec::new();
513        let mut eps = Vec::new();
514        for n in 0..3u8 {
515            ifaces.push(InterfaceInfo {
516                id: n,
517                class: 255,
518                subclass: 0,
519                protocol: 0,
520            });
521            eps.push((
522                n,
523                vec![
524                    EndpointInfo {
525                        address: 0x81 + n * 2,
526                        attributes: 2,
527                        max_packet_size: 64,
528                        interval: 0,
529                    },
530                    EndpointInfo {
531                        address: 0x02 + n * 2,
532                        attributes: 2,
533                        max_packet_size: 64,
534                        interval: 0,
535                    },
536                ],
537            ));
538        }
539        t.set_interfaces(ifaces);
540        t.configure_endpoints(&eps);
541        t
542    }
543
544    fn also<F: FnOnce(&Self)>(self, f: F) -> Self {
545        f(&self);
546        self
547    }
548
549    pub fn recorded_bulk_out(&self) -> Vec<RecordedBulkOut> {
550        self.inner.lock().unwrap().recorded_bulk_out.clone()
551    }
552
553    pub fn push_rx(&self, data: &[u8]) {
554        let mut s = self.inner.lock().unwrap();
555        s.rx_queue.extend(data);
556    }
557
558    pub fn push_interrupt_in(&self, data: &[u8]) {
559        let mut s = self.inner.lock().unwrap();
560        s.interrupt_in_queue.extend(data);
561    }
562
563    /// Drain and return bytes written to bulk OUT (test asserts).
564    pub fn take_tx(&self) -> Vec<u8> {
565        let mut s = self.inner.lock().unwrap();
566        std::mem::take(&mut s.tx_log)
567    }
568
569    /// Snapshot recorded control transfers.
570    pub fn recorded_controls(&self) -> Vec<RecordedControl> {
571        self.inner.lock().unwrap().recorded.clone()
572    }
573
574    pub fn clear_recorded(&self) {
575        let mut s = self.inner.lock().unwrap();
576        s.recorded.clear();
577        s.recorded_bulk_out.clear();
578    }
579
580    pub fn claimed_interfaces(&self) -> Vec<u8> {
581        self.inner.lock().unwrap().claimed.clone()
582    }
583
584    /// Queue the next `control_in` response payload (FIFO).
585    pub fn script_control_in_response(&self, data: Vec<u8>) {
586        self.inner.lock().unwrap().control_in_responses.push(data);
587    }
588
589    pub fn inject_bulk_read_error(&self, msg: impl Into<String>) {
590        self.inner.lock().unwrap().bulk_read_error = Some(msg.into());
591    }
592
593    /// Enable a scripted Quectel-like AT responder on bulk OUT (`AT\r` → `OK`, etc.).
594    pub fn enable_at_modem(&self) {
595        let mut s = self.inner.lock().unwrap();
596        s.at_modem = true;
597        s.at_line_buf.clear();
598    }
599
600    pub fn disable_at_modem(&self) {
601        let mut s = self.inner.lock().unwrap();
602        s.at_modem = false;
603        s.at_line_buf.clear();
604    }
605
606    pub fn set_interfaces(&self, interfaces: Vec<InterfaceInfo>) {
607        self.inner.lock().unwrap().interfaces = interfaces;
608    }
609
610    pub fn configure_endpoints(&self, layout: &[(u8, Vec<EndpointInfo>)]) {
611        let mut s = self.inner.lock().unwrap();
612        s.endpoints = layout
613            .iter()
614            .flat_map(|(iface, eps)| eps.iter().map(move |ep| (*iface, *ep)))
615            .collect();
616    }
617
618    pub fn set_raw_descriptors(&self, raw: Vec<u8>) {
619        self.inner.lock().unwrap().raw_descriptors = raw;
620    }
621
622    pub fn set_vendor_product(&self, vendor_id: u16, product_id: u16) {
623        let mut s = self.inner.lock().unwrap();
624        s.device_descriptor[8] = (vendor_id & 0xff) as u8;
625        s.device_descriptor[9] = (vendor_id >> 8) as u8;
626        s.device_descriptor[10] = (product_id & 0xff) as u8;
627        s.device_descriptor[11] = (product_id >> 8) as u8;
628    }
629
630    pub fn patch_device_descriptor(&self, mut patch: impl FnMut(&mut [u8; 18])) {
631        let mut s = self.inner.lock().unwrap();
632        patch(&mut s.device_descriptor);
633    }
634}
635
636struct FakeBulkIn {
637    inner: Arc<Mutex<FakeState>>,
638    interrupt: bool,
639    endpoint: u8,
640}
641
642impl Drop for FakeBulkIn {
643    fn drop(&mut self) {
644        if let Ok(mut s) = self.inner.lock() {
645            s.open_endpoints.retain(|&e| e != self.endpoint);
646        }
647    }
648}
649
650impl BulkIn for FakeBulkIn {
651    fn read(&mut self, buf: &mut [u8], _timeout_ms: u32) -> Result<ReadOutcome> {
652        let mut s = self.inner.lock().unwrap();
653        if let Some(err) = s.bulk_read_error.take() {
654            return Err(UsbSerialError::Io(err));
655        }
656        let queue = if self.interrupt {
657            &mut s.interrupt_in_queue
658        } else {
659            &mut s.rx_queue
660        };
661        if queue.is_empty() {
662            return Ok(ReadOutcome::TimedOut);
663        }
664        let n = buf.len().min(queue.len());
665        for (i, byte) in queue.drain(..n).enumerate() {
666            buf[i] = byte;
667        }
668        Ok(ReadOutcome::Data(buf[..n].to_vec()))
669    }
670
671    fn cancel_all(&mut self) {}
672
673    fn clear_halt(&mut self) -> Result<()> {
674        Ok(())
675    }
676}
677
678struct FakeBulkOut {
679    inner: Arc<Mutex<FakeState>>,
680    endpoint: u8,
681}
682
683impl Drop for FakeBulkOut {
684    fn drop(&mut self) {
685        if let Ok(mut s) = self.inner.lock() {
686            s.open_endpoints.retain(|&e| e != self.endpoint);
687        }
688    }
689}
690
691impl BulkOut for FakeBulkOut {
692    fn write(&mut self, data: &[u8], _timeout_ms: u32) -> Result<usize> {
693        let mut s = self.inner.lock().unwrap();
694        s.tx_log.extend_from_slice(data);
695        s.recorded_bulk_out.push(RecordedBulkOut {
696            endpoint: self.endpoint,
697            data: data.to_vec(),
698        });
699        if s.at_modem {
700            feed_at_modem(&mut s, data);
701        }
702        Ok(data.len())
703    }
704
705    fn clear_halt(&mut self) -> Result<()> {
706        Ok(())
707    }
708}
709
710/// Parse AT lines from host TX and enqueue modem-style replies (Quectel M26-ish).
711fn feed_at_modem(s: &mut FakeState, data: &[u8]) {
712    s.at_line_buf.extend_from_slice(data);
713    while let Some(idx) = s.at_line_buf.iter().position(|&b| b == b'\r') {
714        let raw: Vec<u8> = s.at_line_buf.drain(..=idx).collect();
715        let line = std::str::from_utf8(&raw[..raw.len().saturating_sub(1)])
716            .unwrap_or("")
717            .trim()
718            .to_ascii_uppercase();
719        let reply = at_modem_reply(&line);
720        s.rx_queue.extend(reply.as_bytes());
721    }
722}
723
724fn at_modem_reply(cmd: &str) -> String {
725    match cmd {
726        "" => "\r\nOK\r\n".to_string(),
727        "AT" => "\r\nOK\r\n".to_string(),
728        "ATI" => {
729            "\r\nQuectel_Ltd\r\nQuectel_M26\r\nRevision: M26FBR03A06-TTS\r\n\r\nOK\r\n".to_string()
730        }
731        "AT+GMR" => "\r\nRevision: M26FBR03A06-TTS\r\n\r\nOK\r\n".to_string(),
732        "AT+CSQ" => {
733            // Leading URC then solicited response (exchange tests).
734            "\r\n+CREG: 0,1\r\n\r\n+CSQ: 20,99\r\n\r\nOK\r\n".to_string()
735        }
736        c if c.starts_with("AT") => "\r\nERROR\r\n".to_string(),
737        // Non-AT / binary garbage — no auto reply (caller can still push_rx).
738        _ => String::new(),
739    }
740}
741
742impl Transport for FakeTransport {
743    fn raw_device_descriptor(&self) -> [u8; 18] {
744        self.inner.lock().unwrap().device_descriptor
745    }
746
747    fn raw_descriptors(&self) -> Vec<u8> {
748        self.inner.lock().unwrap().raw_descriptors.clone()
749    }
750
751    fn device_class(&self) -> u8 {
752        self.raw_device_descriptor()[4]
753    }
754
755    fn interfaces(&self) -> Vec<InterfaceInfo> {
756        self.inner.lock().unwrap().interfaces.clone()
757    }
758
759    fn endpoints(&self, interface: u8) -> Vec<EndpointInfo> {
760        self.inner
761            .lock()
762            .unwrap()
763            .endpoints
764            .iter()
765            .filter(|(iface, _)| *iface == interface)
766            .map(|(_, ep)| *ep)
767            .collect()
768    }
769
770    fn claim_interface(&self, interface: u8) -> Result<()> {
771        self.inner.lock().unwrap().claimed.push(interface);
772        Ok(())
773    }
774
775    fn release_interface(&self, interface: u8) -> Result<()> {
776        let mut s = self.inner.lock().unwrap();
777        s.claimed.retain(|&i| i != interface);
778        Ok(())
779    }
780
781    fn control_out(&self, req: &ControlRequest) -> Result<usize> {
782        let mut s = self.inner.lock().unwrap();
783        s.recorded.push(RecordedControl {
784            request_type: req.request_type,
785            request: req.request,
786            value: req.value,
787            index: req.index,
788            data: req.data.clone(),
789        });
790        Ok(req.data.len())
791    }
792
793    fn control_in(&self, req: &ControlRequest) -> Result<Vec<u8>> {
794        let mut s = self.inner.lock().unwrap();
795        s.recorded.push(RecordedControl {
796            request_type: req.request_type,
797            request: req.request,
798            value: req.value,
799            index: req.index,
800            data: req.data.clone(),
801        });
802        if let Some(resp) = s.control_in_responses.pop() {
803            return Ok(resp);
804        }
805        Ok(req.data.clone())
806    }
807
808    fn open_bulk_in(&self, endpoint: u8, _max_packet_size: u16) -> Result<Box<dyn BulkIn>> {
809        self.mark_endpoint_open(endpoint)?;
810        Ok(Box::new(FakeBulkIn {
811            inner: self.inner.clone(),
812            interrupt: false,
813            endpoint,
814        }))
815    }
816
817    fn open_bulk_out(&self, endpoint: u8, _max_packet_size: u16) -> Result<Box<dyn BulkOut>> {
818        self.mark_endpoint_open(endpoint)?;
819        Ok(Box::new(FakeBulkOut {
820            inner: self.inner.clone(),
821            endpoint,
822        }))
823    }
824
825    fn open_interrupt_in(&self, endpoint: u8, _max_packet_size: u16) -> Result<Box<dyn BulkIn>> {
826        self.mark_endpoint_open(endpoint)?;
827        Ok(Box::new(FakeBulkIn {
828            inner: self.inner.clone(),
829            interrupt: true,
830            endpoint,
831        }))
832    }
833}
834
835impl FakeTransport {
836    fn mark_endpoint_open(&self, endpoint: u8) -> Result<()> {
837        let mut s = self.inner.lock().unwrap();
838        if s.open_endpoints.contains(&endpoint) {
839            return Err(UsbSerialError::Io("endpoint already in use".into()));
840        }
841        s.open_endpoints.push(endpoint);
842        Ok(())
843    }
844}
845
846#[cfg(test)]
847mod at_modem_tests {
848    use super::*;
849
850    #[test]
851    fn at_modem_replies_ok_and_csq_with_urc() {
852        let fake = FakeTransport::cdc_single_iface();
853        fake.enable_at_modem();
854        let mut out = fake.open_bulk_out(0x02, 64).unwrap();
855        out.write(b"AT\r", 1000).unwrap();
856        out.write(b"AT+CSQ\r", 1000).unwrap();
857        let mut s = fake.inner.lock().unwrap();
858        let rx: Vec<u8> = s.rx_queue.drain(..).collect();
859        let text = String::from_utf8_lossy(&rx);
860        assert!(text.contains("OK"), "{text}");
861        assert!(text.contains("+CREG:"), "{text}");
862        assert!(text.contains("+CSQ: 20,99"), "{text}");
863    }
864
865    #[test]
866    fn at_modem_unknown_at_returns_error() {
867        let fake = FakeTransport::cdc_single_iface();
868        fake.enable_at_modem();
869        let mut out = fake.open_bulk_out(0x02, 64).unwrap();
870        out.write(b"AT+FOO\r", 1000).unwrap();
871        let mut s = fake.inner.lock().unwrap();
872        let rx: Vec<u8> = s.rx_queue.drain(..).collect();
873        assert_eq!(String::from_utf8_lossy(&rx), "\r\nERROR\r\n");
874    }
875}