Skip to main content

hidpp/
channel.rs

1//! Implements basic messaging across HID and HID++ channels.
2//!
3//! This includes mapping incoming messages to previously sent requests.
4
5use std::{
6    collections::{HashMap, VecDeque},
7    error::Error,
8    sync::{
9        Arc, Mutex, Weak,
10        atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering},
11    },
12    thread::{self, JoinHandle},
13    time::Duration,
14};
15
16use async_trait::async_trait;
17use futures::{FutureExt, channel::oneshot, select};
18use hidreport::{Field, Report, ReportDescriptor, Usage, UsageId, UsagePage};
19use rand::Rng;
20use thiserror::Error;
21use tracing::trace;
22
23use crate::nibble::U4;
24
25/// hidapi defines this as the maximum EXPECTED size of report descriptors.
26/// We will trust this for now, but a workaround may be required if devices do
27/// in fact return longer descriptors.
28const MAX_REPORT_DESCRIPTOR_LENGTH: usize = 4096;
29
30/// This is the size of the buffer incoming reports are read into.
31/// As we only care about HID++ reports, this equals to [`LONG_REPORT_LENGTH`].
32const MAX_REPORT_LENGTH: usize = LONG_REPORT_LENGTH;
33
34/// Largest output report accepted by [`HidppChannel::write_raw_report`].
35/// Logitech's very-long HID++ lighting report (`0x12`) is 64 bytes.
36const MAX_RAW_REPORT_LENGTH: usize = 64;
37
38/// The default time budget for a [`HidppChannel::send`] request: the report
39/// write plus the wait for a matching response. Callers that need a different
40/// budget can use [`HidppChannel::send_with_timeout`].
41pub const SEND_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
42
43/// The ID of the HID report that is used to transmit short HID++ messages.
44pub const SHORT_REPORT_ID: u8 = 0x10;
45
46/// The HID usage page ID of short HID++ message reports.
47pub const SHORT_REPORT_USAGE_PAGE: u16 = 0xff00;
48
49/// The HID usage ID of short HID++ message reports.
50pub const SHORT_REPORT_USAGE: u16 = 0x0001;
51
52/// The length of short HID++ message reports (including report ID).
53pub const SHORT_REPORT_LENGTH: usize = 7;
54
55/// The ID of the HID report that is used to transmit long HID++ messages.
56pub const LONG_REPORT_ID: u8 = 0x11;
57
58/// The HID usage page ID of long HID++ message reports.
59pub const LONG_REPORT_USAGE_PAGE: u16 = 0xff00;
60
61/// The HID usage ID of long HID++ message reports.
62pub const LONG_REPORT_USAGE: u16 = 0x0002;
63
64/// The length of long HID++ message reports (including report ID).
65pub const LONG_REPORT_LENGTH: usize = 20;
66
67/// Represents an arbitrary HID communication channel that is both readable and
68/// writable. It has to support async I/O.
69///
70/// Any type this trait is implemented for can be used for HID(++)
71/// communication. If a specific channel supports HID++ is determined at a later
72/// stage and is not directly related to potential implementations of this
73/// trait.
74#[async_trait]
75pub trait RawHidChannel: Sync + Send + 'static {
76    /// Provides the vendor ID of the connected HID device.
77    fn vendor_id(&self) -> u16;
78
79    /// Provides the product ID of the connected HID device.
80    fn product_id(&self) -> u16;
81
82    /// Writes a raw report to the channel.
83    ///
84    /// Returns the exact amount of written bytes on success.
85    async fn write_report(&self, src: &[u8]) -> Result<usize, Box<dyn Error + Sync + Send>>;
86
87    /// Reads a raw report from the channel.
88    ///
89    /// If the buffer is not large enough to fit the whole report, its remainder
90    /// should be discarded and must not be returned by any succeeding call to
91    /// [`Self::read_report`].
92    ///
93    /// Returns the exact amount or read bytes on success. An `Err` is treated
94    /// as transient: the [`HidppChannel`] read loop logs it and retries, so an
95    /// implementation must not surface a condition that will never clear (it
96    /// would busy-spin the loop). For a *permanent* failure — the device is
97    /// gone and no report will ever arrive — the future may instead park
98    /// forever. That is sound because the read loop always races this future
99    /// against the channel's close signal in a `select!`; any other caller
100    /// must do the same and must not await `read_report` bare.
101    async fn read_report(&self, buf: &mut [u8]) -> Result<usize, Box<dyn Error + Sync + Send>>;
102
103    /// Whether the underlying device connection is still usable.
104    ///
105    /// Implementations that can detect a permanent disconnect should override
106    /// this. The default preserves the behavior of transports that cannot
107    /// report connection state.
108    fn is_connected(&self) -> bool {
109        true
110    }
111
112    /// If the implementation already knows whether the underlying HID channel
113    /// supports HID++ messages, it should return `Some((supports_short,
114    /// supports_long))` from this method.
115    ///
116    /// In this case, the report descriptor will not be read and parsed.
117    fn supports_short_long_hidpp(&self) -> Option<(bool, bool)>;
118
119    /// Retrieves the raw HID report descriptor from the channel.
120    ///
121    /// This is used to determine whether the channel supports HID++.
122    ///
123    /// Returns the exact size of the report descriptor on success.
124    async fn get_report_descriptor(
125        &self,
126        buf: &mut [u8],
127    ) -> Result<usize, Box<dyn Error + Sync + Send>>;
128}
129
130/// Checks whether a raw channel supports short or long HID++ messages.
131async fn supports_short_long_hidpp(
132    chan: &impl RawHidChannel,
133) -> Result<(bool, bool), ChannelError> {
134    if let Some((supports_short, supports_long)) = chan.supports_short_long_hidpp() {
135        return Ok((supports_short, supports_long));
136    }
137
138    let mut raw_descriptor = vec![0u8; MAX_REPORT_DESCRIPTOR_LENGTH];
139    let descriptor_size = chan.get_report_descriptor(&mut raw_descriptor).await?;
140
141    let descriptor = match ReportDescriptor::try_from(&raw_descriptor[..descriptor_size]) {
142        Ok(val) => val,
143        Err(err) => return Err(ChannelError::ReportDescriptor(err)),
144    };
145
146    let supports_short = descriptor
147        .find_input_report(&[SHORT_REPORT_ID])
148        .and_then(|report| report.fields().first())
149        .and_then(|field| match field {
150            Field::Array(arr) => Some(arr.usage_range()),
151            _ => None,
152        })
153        .is_some_and(|range| {
154            range
155                .lookup_usage(&Usage::from_page_and_id(
156                    UsagePage::from(SHORT_REPORT_USAGE_PAGE),
157                    UsageId::from(SHORT_REPORT_USAGE),
158                ))
159                .is_some()
160        });
161
162    let supports_long = descriptor
163        .find_input_report(&[LONG_REPORT_ID])
164        .and_then(|report| report.fields().first())
165        .and_then(|field| match field {
166            Field::Array(arr) => Some(arr.usage_range()),
167            _ => None,
168        })
169        .is_some_and(|range| {
170            range
171                .lookup_usage(&Usage::from_page_and_id(
172                    UsagePage::from(LONG_REPORT_USAGE_PAGE),
173                    UsageId::from(LONG_REPORT_USAGE),
174                ))
175                .is_some()
176        });
177
178    Ok((supports_short, supports_long))
179}
180
181/// Represents an unversioned HID++ message.
182#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
183pub enum HidppMessage {
184    /// Represents a short HID++ message.
185    ///
186    /// Please check [`HidppChannel::supports_short`] before sending this kind
187    /// of message.
188    Short([u8; SHORT_REPORT_LENGTH - 1]),
189
190    /// Represents a long HID++ message.
191    ///
192    /// Please check [`HidppChannel::supports_long`] before sending this kind of
193    /// message.
194    Long([u8; LONG_REPORT_LENGTH - 1]),
195}
196
197impl HidppMessage {
198    /// Tries to read a HID++ message from raw data.
199    pub fn read_raw(data: &[u8]) -> Option<Self> {
200        let (&report_id, rest) = data.split_first()?;
201
202        // The empty-remainder patterns enforce the exact report lengths.
203        if report_id == SHORT_REPORT_ID
204            && let Some((&payload, [])) = rest.split_first_chunk()
205        {
206            Some(HidppMessage::Short(payload))
207        } else if report_id == LONG_REPORT_ID
208            && let Some((&payload, [])) = rest.split_first_chunk()
209        {
210            Some(HidppMessage::Long(payload))
211        } else {
212            None
213        }
214    }
215
216    /// Writes a HID++ message in its raw byte form into a buffer.
217    ///
218    /// Returns the amount of written bytes.
219    pub fn write_raw(&self, buf: &mut [u8]) -> usize {
220        match self {
221            Self::Short(payload) => {
222                buf[0] = SHORT_REPORT_ID;
223                buf[1..SHORT_REPORT_LENGTH].copy_from_slice(payload);
224                SHORT_REPORT_LENGTH
225            }
226            Self::Long(payload) => {
227                buf[0] = LONG_REPORT_ID;
228                buf[1..LONG_REPORT_LENGTH].copy_from_slice(payload);
229                LONG_REPORT_LENGTH
230            }
231        }
232    }
233
234    /// The HID++ addressing header `(device_index, feature_index, function)` —
235    /// the first three payload bytes, present on both report kinds. Used only
236    /// for wire tracing (OpenLogi-specific; not in upstream hidpp).
237    fn header(&self) -> (u8, u8, u8) {
238        let payload: &[u8] = match self {
239            Self::Short(payload) => payload,
240            Self::Long(payload) => payload,
241        };
242        (payload[0], payload[1], payload[2])
243    }
244}
245
246type MessageListener = Arc<dyn Fn(HidppMessage, bool) + Send + Sync + 'static>;
247
248/// Removes a HID++ message listener when dropped.
249pub struct MessageListenerGuard {
250    message_listeners: Weak<Mutex<HashMap<u32, MessageListener>>>,
251    hdl: u32,
252}
253
254impl Drop for MessageListenerGuard {
255    fn drop(&mut self) {
256        if let Some(message_listeners) = self.message_listeners.upgrade() {
257            message_listeners.lock().unwrap().remove(&self.hdl);
258        }
259    }
260}
261
262/// Represents a HID communication channel supporting HID++.
263pub struct HidppChannel {
264    /// Whether the channel supports short (7 bytes) HID++ messages.
265    pub supports_short: bool,
266
267    /// Whether the channel supports long (20 bytes) HID++ messages.
268    pub supports_long: bool,
269
270    /// The vendor ID of the connected HID device.
271    pub vendor_id: u16,
272
273    /// The product ID of the connected HID device.
274    pub product_id: u16,
275
276    /// The underlying raw HID channel.
277    raw_channel: Arc<dyn RawHidChannel>,
278
279    /// Whether to rotate the [`Self::software_id`].
280    rotate_software_id: AtomicBool,
281
282    /// The software ID to provide at the next call to [`Self::get_sw_id`].
283    software_id: AtomicU8,
284
285    /// All sent messages that are waiting for a response.
286    pending_messages: Arc<Mutex<VecDeque<PendingMessage>>>,
287
288    /// The request ID assigned to the next pending message.
289    pending_message_id: AtomicU64,
290
291    /// Registered listeners that will receive notifications about incoming
292    /// messages.
293    message_listeners: Arc<Mutex<HashMap<u32, MessageListener>>>,
294
295    /// The sender signaling the read thread to stop.
296    read_thread_close: Option<oneshot::Sender<()>>,
297
298    /// The handle to the read thread. Should be joined after signaling
299    /// [`Self::read_thread_close`].
300    read_thread_hdl: Option<JoinHandle<()>>,
301
302    /// Optional process-wide software-id lease: `(id, free)` run on drop.
303    ///
304    /// OpenLogi leases a unique HID++ software id per open so concurrent
305    /// channels on the same physical HID node never share a correlation id
306    /// (software id `0` is reserved for device notifications). Local addition.
307    sw_id_lease: Option<(u8, fn(u8))>,
308}
309
310impl Drop for HidppChannel {
311    fn drop(&mut self) {
312        if let Some((id, free)) = self.sw_id_lease.take() {
313            free(id);
314        }
315
316        if let Some(read_thread_close) = self.read_thread_close.take() {
317            // This only fails if the receiving end, which is owned by the read thread in
318            // this case, is dropped.
319            // This just means that the read thread is already stopped, so we can ignore the
320            // error here.
321            let _ = read_thread_close.send(());
322        }
323
324        if let Some(read_thread_hdl) = self.read_thread_hdl.take() {
325            read_thread_hdl.join().unwrap();
326        }
327    }
328}
329
330/// Represents a message that was sent and is waiting for a response.
331struct PendingMessage {
332    /// Unique ID used to remove this request if it times out.
333    id: u64,
334
335    /// The predicate that has to match for an incoming message to be classified
336    /// as the response.
337    response_predicate: Box<dyn Fn(&HidppMessage) -> bool + Send>,
338
339    /// The oneshot sender used to provide the response message to the receiving
340    /// end.
341    sender: oneshot::Sender<HidppMessage>,
342}
343
344impl HidppChannel {
345    /// Tries to construct a HID++ channel from a raw HID channel.
346    ///
347    /// If the given HID channel does not support HID++,
348    /// [`ChannelError::HidppNotSupported`] will be returned.
349    pub async fn from_raw_channel(raw: impl RawHidChannel) -> Result<Self, ChannelError> {
350        let (supports_short, supports_long) = supports_short_long_hidpp(&raw).await?;
351
352        if !supports_short && !supports_long {
353            return Err(ChannelError::HidppNotSupported);
354        }
355
356        let raw_channel_rc = Arc::new(raw);
357        let pending_messages_rc = Arc::new(Mutex::new(VecDeque::<PendingMessage>::new()));
358        let message_listeners_rc = Arc::new(Mutex::new(HashMap::<u32, MessageListener>::new()));
359
360        let (close_sender, mut close_receiver) = oneshot::channel::<()>();
361
362        let read_thread_hdl = thread::spawn({
363            let raw_channel = Arc::clone(&raw_channel_rc);
364            let pending_messages = Arc::clone(&pending_messages_rc);
365            let message_listeners = Arc::clone(&message_listeners_rc);
366
367            move || {
368                futures::executor::block_on(async {
369                    let mut buf = [0u8; MAX_REPORT_LENGTH];
370
371                    loop {
372                        let res = select! {
373                            _ = close_receiver => {
374                                break;
375                            },
376                            res = raw_channel.read_report(&mut buf).fuse() => res
377                        };
378
379                        let len = match res {
380                            Ok(len) => len,
381                            Err(error) => {
382                                // A silently erroring handle is indistinguishable
383                                // from a deaf one without this line.
384                                trace!(?error, "read_report error");
385                                continue;
386                            }
387                        };
388
389                        let Some(msg) = HidppMessage::read_raw(&buf[..len]) else {
390                            trace!(len, "report not HID++ — dropped");
391                            continue;
392                        };
393
394                        let mut matched = false;
395                        let pending_count;
396                        {
397                            let mut msgs = pending_messages.lock().unwrap();
398                            pending_count = msgs.len();
399                            if let Some(pos) =
400                                msgs.iter().position(|elem| (elem.response_predicate)(&msg))
401                            {
402                                let waiting = msgs.remove(pos).unwrap();
403                                let _ = waiting.sender.send(msg);
404                                matched = true;
405                            }
406                        }
407
408                        trace!(
409                            len,
410                            matched,
411                            pending_count,
412                            payload = format!("{:02x?}", &buf[..len.min(16)]),
413                            "raw report received"
414                        );
415
416                        let listeners: Vec<_> = message_listeners
417                            .lock()
418                            .unwrap()
419                            .values()
420                            .cloned()
421                            .collect();
422                        for listener in listeners {
423                            listener(msg, matched);
424                        }
425                    }
426                });
427            }
428        });
429
430        Ok(Self {
431            supports_short,
432            supports_long,
433            vendor_id: raw_channel_rc.vendor_id(),
434            product_id: raw_channel_rc.product_id(),
435            raw_channel: raw_channel_rc,
436            rotate_software_id: AtomicBool::new(false),
437            software_id: AtomicU8::new(0x01),
438            pending_messages: pending_messages_rc,
439            pending_message_id: AtomicU64::new(1),
440            message_listeners: message_listeners_rc,
441            read_thread_close: Some(close_sender),
442            read_thread_hdl: Some(read_thread_hdl),
443            sw_id_lease: None,
444        })
445    }
446
447    /// Whether the underlying HID transport still reports a live connection.
448    pub fn is_connected(&self) -> bool {
449        self.raw_channel.is_connected()
450    }
451
452    /// Sets the software ID that should be returned by the next call to
453    /// [`Self::get_sw_id`].
454    ///
455    /// Using software ID `0` is highly discouraged as it is used for device
456    /// notifications.
457    pub fn set_sw_id(&self, sw_id: U4) {
458        self.software_id.store(sw_id.to_lo(), Ordering::SeqCst);
459    }
460
461    /// Sets whether the software ID returned by a call to [`Self::get_sw_id`]
462    /// should increment (and potentially wrap around) after each call.
463    ///
464    /// This comes in handy when trying to map responses to requests
465    /// consistently.
466    ///
467    /// Software ID `0` will be skipped in the rotation process as it is
468    /// reserved for device notifications.
469    pub fn set_rotating_sw_id(&self, enable: bool) {
470        self.rotate_software_id.store(enable, Ordering::SeqCst);
471    }
472
473    /// Lease software id `id` until this channel is dropped, then call `free(id)`.
474    ///
475    /// Replaces any previous lease. Used by OpenLogi so concurrent opens of the
476    /// same HID node hold distinct correlation ids for their full lifetime.
477    ///
478    /// OpenLogi local addition.
479    pub fn set_sw_id_lease(&mut self, id: u8, free: fn(u8)) {
480        self.sw_id_lease = Some((id, free));
481    }
482
483    /// Provides a software ID that can be used to send a HID++ message across
484    /// the channel.
485    ///
486    /// This method should be called separately for every message to send as it
487    /// may rotate (as indicated by [`Self::set_rotating_sw_id`]).
488    pub fn get_sw_id(&self) -> U4 {
489        if self.rotate_software_id.load(Ordering::SeqCst) {
490            U4::from_lo(
491                self.software_id
492                    .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| {
493                        Some(if old & 0x0f == 0x0f {
494                            0x01
495                        } else {
496                            old.wrapping_add(1)
497                        })
498                    })
499                    .unwrap(),
500            )
501        } else {
502            U4::from_lo(self.software_id.load(Ordering::SeqCst))
503        }
504    }
505
506    /// Checks whether the channel supports the given HID++ message.
507    pub fn supports_msg(&self, msg: &HidppMessage) -> bool {
508        match msg {
509            HidppMessage::Short(_) => self.supports_short,
510            HidppMessage::Long(_) => self.supports_long,
511        }
512    }
513
514    /// Re-frames a short message as long on a long-only channel — a device that
515    /// exposes only the long HID++ report (e.g. a Bluetooth-LE-direct mouse on
516    /// macOS, where `IOHIDDeviceSetReport` rejects the short report). The HID++
517    /// header bytes sit at the same offsets in both widths, so the only change
518    /// is the report id plus zero-padding the extra payload; the device answers
519    /// with a long report, which still matches the request by header. A no-op on
520    /// channels that advertise short support.
521    ///
522    /// (OpenLogi local addition — candidate for upstreaming.)
523    fn normalize_outgoing(&self, msg: HidppMessage) -> HidppMessage {
524        match msg {
525            HidppMessage::Short(payload) if !self.supports_short && self.supports_long => {
526                HidppMessage::Long(short_payload_as_long(&payload))
527            }
528            other => other,
529        }
530    }
531
532    /// Sends a HID++ message across the channel and waits for a response.
533    ///
534    /// If no response is expected/required, use [`Self::send_and_forget`].
535    ///
536    /// The whole request — the report write plus the wait for a matching
537    /// response — is bounded by [`SEND_RESPONSE_TIMEOUT`]; the future resolves
538    /// to [`ChannelError::Timeout`] on elapse. Use [`Self::send_with_timeout`]
539    /// to choose a different budget.
540    pub async fn send(
541        &self,
542        msg: HidppMessage,
543        response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
544    ) -> Result<HidppMessage, ChannelError> {
545        self.send_with_timeout(msg, response_predicate, SEND_RESPONSE_TIMEOUT)
546            .await
547    }
548
549    /// Sends a HID++ message across the channel and waits for a response,
550    /// bounding the whole request — the report write plus the wait for a
551    /// matching response — by `timeout`.
552    ///
553    /// On elapse the request's pending entry is removed (concurrent in-flight
554    /// requests are unaffected) and [`ChannelError::Timeout`] is returned; a
555    /// response that still arrives later reaches message listeners as an
556    /// unmatched message.
557    ///
558    /// [`Self::send`] uses this with [`SEND_RESPONSE_TIMEOUT`], which suits
559    /// requests to a device that may be asleep. Requests that should fail
560    /// faster — e.g. probing a receiver that answers immediately or not at
561    /// all — can pass a tighter budget.
562    pub async fn send_with_timeout(
563        &self,
564        msg: HidppMessage,
565        response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
566        timeout: Duration,
567    ) -> Result<HidppMessage, ChannelError> {
568        let msg = self.normalize_outgoing(msg);
569        if !self.supports_msg(&msg) {
570            return Err(ChannelError::MessageTypeNotSupported);
571        }
572
573        // Wire trace (off by default; `OPENLOGI_LOG=hidpp=trace`). Capture the
574        // header before `msg` is moved into the send future so the outcome line
575        // below can name the same request.
576        let (dev, feat, func) = msg.header();
577        trace!(dev, feat, func, "hidpp request");
578
579        let (sender, receiver) = oneshot::channel::<HidppMessage>();
580        let pending_id = self.pending_message_id.fetch_add(1, Ordering::SeqCst);
581
582        {
583            let mut pending = self.pending_messages.lock().unwrap();
584            // Drop abandoned requests before queuing this one. Timeouts and
585            // write failures remove their entry eagerly below, but a caller
586            // cancelled mid-flight (an outer `timeout(..)` dropping the whole
587            // future) still leaves its `PendingMessage` behind. On a channel
588            // reused across inventory ticks those would accumulate unboundedly
589            // — and a late response could be mis-delivered to a recycled
590            // software id. `is_canceled()` is true once the receiver is gone,
591            // so this prunes exactly the give-ups.
592            pending.retain(|m| !m.sender.is_canceled());
593            pending.push_back(PendingMessage {
594                id: pending_id,
595                response_predicate: Box::new(response_predicate),
596                sender,
597            });
598        }
599
600        // The deadline covers the write as well: `write_report` has no
601        // bounded-time contract of its own, so a wedged device could otherwise
602        // park `send` forever before the response wait even starts.
603        let mut request = std::pin::pin!(
604            async {
605                self.send_and_forget(msg).await?;
606                receiver.await.map_err(|_| ChannelError::NoResponse)
607            }
608            .fuse()
609        );
610
611        let result = select! {
612            result = request => result,
613            _ = futures_timer::Delay::new(timeout).fuse() => Err(ChannelError::Timeout),
614        };
615
616        match &result {
617            Ok(_) => trace!(dev, feat, "hidpp response"),
618            Err(e) => trace!(dev, feat, error = ?e, "hidpp no response"),
619        }
620
621        if result.is_err() {
622            // A timeout or write failure leaves the entry queued — remove it
623            // eagerly. After a matched response the read thread has already
624            // taken it, so this is a no-op then.
625            self.remove_pending_message(pending_id);
626        }
627
628        result
629    }
630
631    fn remove_pending_message(&self, id: u64) {
632        let mut pending = self.pending_messages.lock().unwrap();
633        if let Some(pos) = pending.iter().position(|msg| msg.id == id) {
634            pending.remove(pos);
635        }
636    }
637
638    /// Sends a HID++ message across the channel and does not wait for a
639    /// response.
640    ///
641    /// If a response is expected, use [`Self::send`],
642    pub async fn send_and_forget(&self, msg: HidppMessage) -> Result<(), ChannelError> {
643        let msg = self.normalize_outgoing(msg);
644        if !self.supports_msg(&msg) {
645            return Err(ChannelError::MessageTypeNotSupported);
646        }
647
648        let mut buf = [0u8; LONG_REPORT_LENGTH];
649        let len = msg.write_raw(&mut buf);
650        self.raw_channel
651            .write_report(&buf[..len])
652            .await
653            .map(|_| ())
654            .map_err(ChannelError::Implementation)
655    }
656
657    /// Write one raw HID report through this channel's already-owned transport.
658    ///
659    /// Reports must contain `1..=64` bytes, including their report ID. The
660    /// operation is bounded by [`SEND_RESPONSE_TIMEOUT`] and returns the exact
661    /// byte count reported by the transport. This is intended for HID++ report
662    /// widths such as the 64-byte `0x12` lighting frame that [`HidppMessage`]
663    /// cannot represent.
664    pub async fn write_raw_report(&self, report: &[u8]) -> Result<usize, ChannelError> {
665        self.write_raw_report_with_timeout(report, SEND_RESPONSE_TIMEOUT)
666            .await
667    }
668
669    async fn write_raw_report_with_timeout(
670        &self,
671        report: &[u8],
672        timeout: Duration,
673    ) -> Result<usize, ChannelError> {
674        if !(1..=MAX_RAW_REPORT_LENGTH).contains(&report.len()) {
675            return Err(ChannelError::InvalidRawReportLength(report.len()));
676        }
677
678        let mut write = std::pin::pin!(self.raw_channel.write_report(report).fuse());
679        select! {
680            result = write => result.map_err(ChannelError::Implementation),
681            _ = futures_timer::Delay::new(timeout).fuse() => Err(ChannelError::Timeout),
682        }
683    }
684
685    /// Registers a listener that will be called for every incoming message.
686    ///
687    /// Returns a handle that can be used to remove the listener using a call to
688    /// [`Self::remove_msg_listener`].
689    pub fn add_msg_listener(
690        &self,
691        listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
692    ) -> u32 {
693        let mut listeners = self.message_listeners.lock().unwrap();
694
695        let mut rng = rand::rng();
696        let mut hdl = rng.random::<u32>();
697        while listeners.contains_key(&hdl) {
698            hdl = rng.random::<u32>();
699        }
700
701        listeners.insert(hdl, Arc::new(listener));
702        hdl
703    }
704
705    /// Registers a listener that is automatically removed when the returned
706    /// guard is dropped.
707    pub fn add_msg_listener_guarded(
708        &self,
709        listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
710    ) -> MessageListenerGuard {
711        let hdl = self.add_msg_listener(listener);
712        MessageListenerGuard {
713            message_listeners: Arc::downgrade(&self.message_listeners),
714            hdl,
715        }
716    }
717
718    /// Removes a previously registered message listener.
719    ///
720    /// Returns whether a listener was found using the given handle.
721    pub fn remove_msg_listener(&self, hdl: u32) -> bool {
722        self.message_listeners
723            .lock()
724            .unwrap()
725            .remove(&hdl)
726            .is_some()
727    }
728}
729
730/// Represents an error that occurred when creating or interacting with a HID or
731/// HID++ communication channel.
732#[derive(Debug, Error)]
733#[non_exhaustive]
734pub enum ChannelError {
735    /// Indicates that the concrete implementation of [`RawHidChannel`] returned
736    /// an error.
737    #[error("the HID channel implementation returned an error")]
738    Implementation(#[from] Box<dyn Error + Sync + Send>),
739
740    /// Indicates that the HID report descriptor could not be parsed.
741    #[error("the report descriptor could not be parsed")]
742    ReportDescriptor(hidreport::ParserError),
743
744    /// Indicates that the channel in question does not support HID++.
745    #[error("the HID channel does not support HID++")]
746    HidppNotSupported,
747
748    /// Indicates that the HID++ channel does not support messages of the given
749    /// type (short/long).
750    #[error("the channel does not support the given HID++ message type")]
751    MessageTypeNotSupported,
752
753    /// Indicates that a raw output report was empty or exceeded 64 bytes.
754    #[error("raw HID reports must contain 1..=64 bytes, got {0}")]
755    InvalidRawReportLength(usize),
756
757    /// Indicates that no response was received following a request.
758    #[error("the device did not respond to the request")]
759    NoResponse,
760
761    /// Indicates that a bounded channel operation did not complete — typically
762    /// because the device is asleep, out of range, connected to another host,
763    /// or its transport write is wedged. See
764    /// [`HidppChannel::send_with_timeout`] and
765    /// [`HidppChannel::write_raw_report`].
766    #[error("the HID channel operation timed out")]
767    Timeout,
768}
769
770/// Widen a short HID++ payload (6 bytes) to a long one (19 bytes): the HID++
771/// header bytes (device / feature / function|sw) sit at the same offsets in
772/// both widths, so the only change is zero-padding the trailing payload. Used
773/// to re-frame short messages as long on a long-only channel — see
774/// [`HidppChannel::normalize_outgoing`]. (OpenLogi local addition.)
775fn short_payload_as_long(payload: &[u8; SHORT_REPORT_LENGTH - 1]) -> [u8; LONG_REPORT_LENGTH - 1] {
776    let mut long = [0u8; LONG_REPORT_LENGTH - 1];
777    long[..payload.len()].copy_from_slice(payload);
778    long
779}
780
781#[cfg(test)]
782pub(crate) mod tests {
783    use super::*;
784    use std::{
785        io,
786        sync::{
787            Arc, Mutex,
788            atomic::{AtomicBool, AtomicUsize, Ordering},
789        },
790        time::{Duration, Instant},
791    };
792
793    use crate::{
794        nibble,
795        protocol::v20::{self, ErrorType, Hidpp20Error},
796    };
797
798    #[test]
799    fn short_payload_widens_preserving_header_and_padding() {
800        // [device, feature, function|sw, p0, p1, p2]
801        let short = [0xff, 0x05, 0x1e, 0xaa, 0xbb, 0xcc];
802        let long = short_payload_as_long(&short);
803        assert_eq!(&long[..short.len()], &short[..]); // header + payload copied verbatim
804        assert!(long[short.len()..].iter().all(|&b| b == 0)); // remainder zero-padded
805        assert_eq!(long.len(), LONG_REPORT_LENGTH - 1);
806    }
807
808    #[test]
809    fn send_returns_response_before_timeout() {
810        futures::executor::block_on(async {
811            let (raw, handle) = MockRawHidChannel::new();
812            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
813
814            let request = short_msg(0x10);
815            let response = short_msg(0x20);
816            handle.queue_response(response);
817
818            let actual = channel
819                .send_with_timeout(
820                    request,
821                    move |candidate| *candidate == response,
822                    Duration::from_secs(1),
823                )
824                .await
825                .unwrap();
826
827            assert_eq!(actual, response);
828            assert_eq!(handle.written_reports().len(), 1);
829            assert_pending_empty(&channel);
830        });
831    }
832
833    #[test]
834    fn send_times_out_and_removes_pending_message() {
835        futures::executor::block_on(async {
836            let (raw, handle) = MockRawHidChannel::new();
837            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
838            let request = short_msg(0x10);
839            let response = short_msg(0x20);
840
841            let started = Instant::now();
842            let err = channel
843                .send_with_timeout(
844                    request,
845                    move |candidate| *candidate == response,
846                    Duration::from_millis(25),
847                )
848                .await
849                .unwrap_err();
850
851            assert!(matches!(err, ChannelError::Timeout));
852            assert!(started.elapsed() < Duration::from_secs(1));
853            assert_eq!(handle.written_reports().len(), 1);
854            assert_pending_empty(&channel);
855        });
856    }
857
858    #[test]
859    fn timeout_removes_only_its_own_pending_message() {
860        futures::executor::block_on(async {
861            let (raw, handle) = MockRawHidChannel::new();
862            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
863
864            let never_answered = short_msg(0x20);
865            let slow_response = short_msg(0x21);
866
867            let timed_out = channel.send_with_timeout(
868                short_msg(0x10),
869                move |candidate| *candidate == never_answered,
870                Duration::from_millis(25),
871            );
872            let answered = channel.send_with_timeout(
873                short_msg(0x11),
874                move |candidate| *candidate == slow_response,
875                Duration::from_secs(1),
876            );
877            // Answer the second request only after the first has timed out, so
878            // a removal that took the wrong entry would fail this test.
879            let respond_late = async {
880                futures_timer::Delay::new(Duration::from_millis(100)).await;
881                handle.send_incoming(slow_response).await;
882            };
883
884            let (timed_out, answered, ()) = futures::join!(timed_out, answered, respond_late);
885
886            assert!(matches!(timed_out.unwrap_err(), ChannelError::Timeout));
887            assert_eq!(answered.unwrap(), slow_response);
888            assert_pending_empty(&channel);
889        });
890    }
891
892    #[test]
893    fn late_response_after_timeout_is_ignored() {
894        futures::executor::block_on(async {
895            let (raw, handle) = MockRawHidChannel::new();
896            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
897            let events = Arc::new(Mutex::new(Vec::new()));
898            let listener_events = Arc::clone(&events);
899            channel.add_msg_listener(move |msg, matched| {
900                listener_events.lock().unwrap().push((msg, matched));
901            });
902
903            let request = short_msg(0x10);
904            let late_response = short_msg(0x20);
905            let err = channel
906                .send_with_timeout(
907                    request,
908                    move |candidate| *candidate == late_response,
909                    Duration::from_millis(25),
910                )
911                .await
912                .unwrap_err();
913
914            assert!(matches!(err, ChannelError::Timeout));
915            assert_pending_empty(&channel);
916
917            handle.send_incoming(late_response).await;
918            wait_for_event_count(&events, 1).await;
919            assert_eq!(events.lock().unwrap()[0], (late_response, false));
920            assert_pending_empty(&channel);
921
922            let later_request = short_msg(0x30);
923            let later_response = short_msg(0x40);
924            handle.queue_response(later_response);
925            let actual = channel
926                .send_with_timeout(
927                    later_request,
928                    move |candidate| *candidate == later_response,
929                    Duration::from_secs(1),
930                )
931                .await
932                .unwrap();
933
934            assert_eq!(actual, later_response);
935            wait_for_event_count(&events, 2).await;
936            assert_eq!(events.lock().unwrap()[1], (later_response, true));
937            assert_pending_empty(&channel);
938        });
939    }
940
941    #[test]
942    fn send_and_forget_writes_without_pending_message() {
943        futures::executor::block_on(async {
944            let (raw, handle) = MockRawHidChannel::new();
945            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
946
947            channel.send_and_forget(short_msg(0x10)).await.unwrap();
948
949            assert_eq!(handle.written_reports().len(), 1);
950            assert_pending_empty(&channel);
951        });
952    }
953
954    #[test]
955    fn raw_report_write_forwards_exact_bytes_and_length() {
956        futures::executor::block_on(async {
957            let (raw, handle) = MockRawHidChannel::new();
958            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
959            let report = [0x12; MAX_RAW_REPORT_LENGTH];
960
961            let written = channel.write_raw_report(&report).await.unwrap();
962
963            assert_eq!(written, report.len());
964            assert_eq!(handle.written_reports(), [report.to_vec()]);
965        });
966    }
967
968    #[test]
969    fn raw_report_write_rejects_empty_and_oversized_inputs_without_io() {
970        futures::executor::block_on(async {
971            let (raw, handle) = MockRawHidChannel::new();
972            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
973
974            let empty = channel.write_raw_report(&[]).await.unwrap_err();
975            let oversized = channel
976                .write_raw_report(&[0; MAX_RAW_REPORT_LENGTH + 1])
977                .await
978                .unwrap_err();
979
980            assert!(matches!(empty, ChannelError::InvalidRawReportLength(0)));
981            assert!(matches!(
982                oversized,
983                ChannelError::InvalidRawReportLength(65)
984            ));
985            assert!(handle.written_reports().is_empty());
986        });
987    }
988
989    #[test]
990    fn raw_report_write_times_out_when_the_transport_parks() {
991        futures::executor::block_on(async {
992            let (raw, handle) = MockRawHidChannel::new();
993            handle.park_writes();
994            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
995            let started = Instant::now();
996
997            let error = channel
998                .write_raw_report_with_timeout(&[LONG_REPORT_ID], Duration::from_millis(25))
999                .await
1000                .unwrap_err();
1001
1002            assert!(matches!(error, ChannelError::Timeout));
1003            assert!(started.elapsed() < Duration::from_secs(1));
1004        });
1005    }
1006
1007    #[test]
1008    fn listener_can_remove_another_listener_during_dispatch() {
1009        futures::executor::block_on(async {
1010            let (raw, handle) = MockRawHidChannel::new();
1011            let channel = Arc::new(HidppChannel::from_raw_channel(raw).await.unwrap());
1012            let removed_listener_calls = Arc::new(AtomicUsize::new(0));
1013            let removing_listener_calls = Arc::new(AtomicUsize::new(0));
1014
1015            let removed_listener_calls_for_listener = Arc::clone(&removed_listener_calls);
1016            let removed_hdl = channel.add_msg_listener(move |_, _| {
1017                removed_listener_calls_for_listener.fetch_add(1, Ordering::SeqCst);
1018            });
1019
1020            let channel_for_listener = Arc::clone(&channel);
1021            let removing_listener_calls_for_listener = Arc::clone(&removing_listener_calls);
1022            channel.add_msg_listener(move |_, _| {
1023                removing_listener_calls_for_listener.fetch_add(1, Ordering::SeqCst);
1024                channel_for_listener.remove_msg_listener(removed_hdl);
1025            });
1026
1027            handle.send_incoming(short_msg(0x20)).await;
1028            wait_for_atomic_count(&removing_listener_calls, 1).await;
1029            wait_for_atomic_count(&removed_listener_calls, 1).await;
1030
1031            handle.send_incoming(short_msg(0x21)).await;
1032            wait_for_atomic_count(&removing_listener_calls, 2).await;
1033
1034            assert_eq!(removed_listener_calls.load(Ordering::SeqCst), 1);
1035        });
1036    }
1037
1038    // --- HID++2.0 (v20) send/matcher characterization tests -----------------
1039    //
1040    // `HidppChannel::send`/`send_with_timeout` above are protocol-agnostic:
1041    // they match on an arbitrary predicate over raw `HidppMessage`s. The
1042    // v20-specific correlation logic (matching by header, splitting out error
1043    // frames) lives in `protocol::v20::HidppChannel::send_v20`, which is built
1044    // directly on top of `send`. These tests pin that logic's current
1045    // behaviour using the same mock transport as the tests above.
1046
1047    #[test]
1048    fn send_v20_matches_response_by_header_ignoring_unrelated_messages() {
1049        futures::executor::block_on(async {
1050            let (raw, handle) = MockRawHidChannel::new();
1051            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1052
1053            let header = v20::MessageHeader {
1054                device_index: 0x01,
1055                feature_index: 0x05,
1056                function_id: U4::from_lo(0x2),
1057                software_id: U4::from_lo(0x3),
1058            };
1059            let request = v20::Message::Short(header, [0x00, 0x00, 0x00]);
1060            let response = v20::Message::Short(header, [0xaa, 0xbb, 0xcc]);
1061
1062            // Each decoy differs from the request in exactly one header field, so
1063            // none of them may be mistaken for its response.
1064            let wrong_device = v20::Message::Short(
1065                v20::MessageHeader {
1066                    device_index: 0x02,
1067                    ..header
1068                },
1069                [0, 0, 0],
1070            );
1071            let wrong_feature = v20::Message::Short(
1072                v20::MessageHeader {
1073                    feature_index: 0x06,
1074                    ..header
1075                },
1076                [0, 0, 0],
1077            );
1078            let wrong_sw_id = v20::Message::Short(
1079                v20::MessageHeader {
1080                    software_id: U4::from_lo(0x4),
1081                    ..header
1082                },
1083                [0, 0, 0],
1084            );
1085
1086            let send_fut = channel.send_v20(request);
1087            let feed_fut = async {
1088                handle.send_incoming(wrong_device.into()).await;
1089                handle.send_incoming(wrong_feature.into()).await;
1090                handle.send_incoming(wrong_sw_id.into()).await;
1091                handle.send_incoming(response.into()).await;
1092            };
1093
1094            let (result, ()) = futures::join!(send_fut, feed_fut);
1095
1096            assert_eq!(result.unwrap(), response);
1097            assert_pending_empty(&channel);
1098        });
1099    }
1100
1101    #[test]
1102    fn send_v20_broadcast_event_does_not_resolve_pending_request() {
1103        futures::executor::block_on(async {
1104            let (raw, handle) = MockRawHidChannel::new();
1105            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1106            let events = Arc::new(Mutex::new(Vec::new()));
1107            let listener_events = Arc::clone(&events);
1108            channel.add_msg_listener(move |msg, matched| {
1109                listener_events.lock().unwrap().push((msg, matched));
1110            });
1111
1112            let header = v20::MessageHeader {
1113                device_index: 0x01,
1114                feature_index: 0x05,
1115                function_id: U4::from_lo(0x2),
1116                software_id: U4::from_lo(0x3),
1117            };
1118            let request = v20::Message::Short(header, [0, 0, 0]);
1119            let response = v20::Message::Short(header, [0xaa, 0xbb, 0xcc]);
1120
1121            // Software ID 0 is reserved for unsolicited device notifications
1122            // (see `feature::event_payload`). The request above uses a non-zero
1123            // ID, so an incoming broadcast sharing device/feature but using ID 0
1124            // must be routed to listeners, not consumed as this request's
1125            // response.
1126            let event = v20::Message::Short(
1127                v20::MessageHeader {
1128                    software_id: U4::from_lo(0x0),
1129                    ..header
1130                },
1131                [0x01, 0x02, 0x03],
1132            );
1133
1134            let send_fut = channel.send_v20(request);
1135            let feed_fut = async {
1136                handle.send_incoming(event.into()).await;
1137                wait_for_event_count(&events, 1).await;
1138                handle.send_incoming(response.into()).await;
1139            };
1140
1141            let (result, ()) = futures::join!(send_fut, feed_fut);
1142
1143            assert_eq!(result.unwrap(), response);
1144            // The oneshot resolves before the listener loop runs on the read
1145            // thread; wait for both deliveries before asserting on them.
1146            wait_for_event_count(&events, 2).await;
1147            let recorded = events.lock().unwrap().clone();
1148            assert_eq!(
1149                recorded,
1150                vec![
1151                    (HidppMessage::from(event), false),
1152                    (HidppMessage::from(response), true),
1153                ]
1154            );
1155            assert_pending_empty(&channel);
1156        });
1157    }
1158
1159    #[test]
1160    fn send_v20_response_may_arrive_as_a_different_report_width() {
1161        futures::executor::block_on(async {
1162            let (raw, handle) = MockRawHidChannel::new();
1163            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1164
1165            let header = v20::MessageHeader {
1166                device_index: 0x01,
1167                feature_index: 0x05,
1168                function_id: U4::from_lo(0x2),
1169                software_id: U4::from_lo(0x3),
1170            };
1171            let request = v20::Message::Short(header, [0, 0, 0]);
1172            // Quirk: `send_v20`'s response predicate compares only the parsed
1173            // v20 header, not the underlying report width. A device replying
1174            // with a long report to a short request — same header, wider
1175            // payload — is still accepted as the response.
1176            let response = v20::Message::Long(header, [0xaa; 16]);
1177            handle.queue_response(response.into());
1178
1179            let result = channel.send_v20(request).await.unwrap();
1180
1181            assert_eq!(result, response);
1182            assert_pending_empty(&channel);
1183        });
1184    }
1185
1186    #[test]
1187    fn send_v20_error_frame_resolves_to_feature_error() {
1188        futures::executor::block_on(async {
1189            let (raw, handle) = MockRawHidChannel::new();
1190            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1191
1192            let header = v20::MessageHeader {
1193                device_index: 0x01,
1194                feature_index: 0x05,
1195                function_id: U4::from_lo(0x2),
1196                software_id: U4::from_lo(0x3),
1197            };
1198            let request = v20::Message::Short(header, [0, 0, 0]);
1199            let error_response = v20_error_frame(header, ErrorType::InvalidArgument.into());
1200            handle.queue_response(error_response.into());
1201
1202            let err = channel.send_v20(request).await.unwrap_err();
1203
1204            assert!(matches!(
1205                err,
1206                Hidpp20Error::Feature(ErrorType::InvalidArgument)
1207            ));
1208            assert_pending_empty(&channel);
1209        });
1210    }
1211
1212    #[test]
1213    fn send_v20_error_frame_with_unmapped_code_is_unsupported_response() {
1214        futures::executor::block_on(async {
1215            let (raw, handle) = MockRawHidChannel::new();
1216            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1217
1218            let header = v20::MessageHeader {
1219                device_index: 0x01,
1220                feature_index: 0x05,
1221                function_id: U4::from_lo(0x2),
1222                software_id: U4::from_lo(0x3),
1223            };
1224            let request = v20::Message::Short(header, [0, 0, 0]);
1225            // 0xfe is not a defined `ErrorType` variant.
1226            let error_response = v20_error_frame(header, 0xfe);
1227            handle.queue_response(error_response.into());
1228
1229            let err = channel.send_v20(request).await.unwrap_err();
1230
1231            assert!(matches!(err, Hidpp20Error::UnsupportedResponse));
1232            assert_pending_empty(&channel);
1233        });
1234    }
1235
1236    /// Builds the HID++2.0 error-frame encoding for `request_header`: feature
1237    /// index 0xFF, with the original feature index and function|software byte
1238    /// shifted one byte to the right (see `v20::HidppChannel::send_v20`'s
1239    /// `is_error` predicate for the reverse mapping).
1240    fn v20_error_frame(request_header: v20::MessageHeader, error_code: u8) -> v20::Message {
1241        let error_header = v20::MessageHeader {
1242            device_index: request_header.device_index,
1243            feature_index: 0xff,
1244            function_id: U4::from_hi(request_header.feature_index),
1245            software_id: U4::from_lo(request_header.feature_index),
1246        };
1247        let mut payload = [0u8; 3];
1248        payload[0] = nibble::combine(request_header.function_id, request_header.software_id);
1249        payload[1] = error_code;
1250        v20::Message::Short(error_header, payload)
1251    }
1252
1253    #[derive(Clone)]
1254    pub(crate) struct MockRawHidHandle {
1255        incoming_tx: async_channel::Sender<Vec<u8>>,
1256        written_reports: Arc<Mutex<Vec<Vec<u8>>>>,
1257        responses_on_write: Arc<Mutex<VecDeque<Vec<u8>>>>,
1258        park_writes: Arc<AtomicBool>,
1259    }
1260
1261    impl MockRawHidHandle {
1262        pub(crate) fn queue_response(&self, msg: HidppMessage) {
1263            self.responses_on_write
1264                .lock()
1265                .unwrap()
1266                .push_back(raw_report(msg));
1267        }
1268
1269        async fn send_incoming(&self, msg: HidppMessage) {
1270            self.incoming_tx.send(raw_report(msg)).await.unwrap();
1271        }
1272
1273        pub(crate) fn written_reports(&self) -> Vec<Vec<u8>> {
1274            self.written_reports.lock().unwrap().clone()
1275        }
1276
1277        fn park_writes(&self) {
1278            self.park_writes.store(true, Ordering::SeqCst);
1279        }
1280    }
1281
1282    pub(crate) struct MockRawHidChannel {
1283        incoming_tx: async_channel::Sender<Vec<u8>>,
1284        incoming_rx: async_channel::Receiver<Vec<u8>>,
1285        written_reports: Arc<Mutex<Vec<Vec<u8>>>>,
1286        responses_on_write: Arc<Mutex<VecDeque<Vec<u8>>>>,
1287        park_writes: Arc<AtomicBool>,
1288    }
1289
1290    impl MockRawHidChannel {
1291        pub(crate) fn new() -> (Self, MockRawHidHandle) {
1292            let (incoming_tx, incoming_rx) = async_channel::unbounded();
1293            let written_reports = Arc::new(Mutex::new(Vec::new()));
1294            let responses_on_write = Arc::new(Mutex::new(VecDeque::new()));
1295            let park_writes = Arc::new(AtomicBool::new(false));
1296
1297            let handle = MockRawHidHandle {
1298                incoming_tx: incoming_tx.clone(),
1299                written_reports: Arc::clone(&written_reports),
1300                responses_on_write: Arc::clone(&responses_on_write),
1301                park_writes: Arc::clone(&park_writes),
1302            };
1303
1304            (
1305                Self {
1306                    incoming_tx,
1307                    incoming_rx,
1308                    written_reports,
1309                    responses_on_write,
1310                    park_writes,
1311                },
1312                handle,
1313            )
1314        }
1315    }
1316
1317    #[async_trait]
1318    impl RawHidChannel for MockRawHidChannel {
1319        fn vendor_id(&self) -> u16 {
1320            0x046d
1321        }
1322
1323        fn product_id(&self) -> u16 {
1324            0xc539
1325        }
1326
1327        async fn write_report(&self, src: &[u8]) -> Result<usize, Box<dyn Error + Sync + Send>> {
1328            self.written_reports.lock().unwrap().push(src.to_vec());
1329            if self.park_writes.load(Ordering::SeqCst) {
1330                return std::future::pending().await;
1331            }
1332            let response = self.responses_on_write.lock().unwrap().pop_front();
1333            if let Some(response) = response {
1334                self.incoming_tx.send(response).await.unwrap();
1335            }
1336
1337            Ok(src.len())
1338        }
1339
1340        async fn read_report(&self, buf: &mut [u8]) -> Result<usize, Box<dyn Error + Sync + Send>> {
1341            let report = self.incoming_rx.recv().await.map_err(|_| mock_error())?;
1342            let len = report.len().min(buf.len());
1343            buf[..len].copy_from_slice(&report[..len]);
1344            Ok(len)
1345        }
1346
1347        fn supports_short_long_hidpp(&self) -> Option<(bool, bool)> {
1348            Some((true, true))
1349        }
1350
1351        async fn get_report_descriptor(
1352            &self,
1353            _buf: &mut [u8],
1354        ) -> Result<usize, Box<dyn Error + Sync + Send>> {
1355            unreachable!("mock declares HID++ support")
1356        }
1357    }
1358
1359    fn short_msg(marker: u8) -> HidppMessage {
1360        HidppMessage::Short([0xff, marker, 0x10, marker, marker, marker])
1361    }
1362
1363    fn raw_report(msg: HidppMessage) -> Vec<u8> {
1364        let mut buf = [0u8; LONG_REPORT_LENGTH];
1365        let len = msg.write_raw(&mut buf);
1366        buf[..len].to_vec()
1367    }
1368
1369    fn assert_pending_empty(channel: &HidppChannel) {
1370        assert!(channel.pending_messages.lock().unwrap().is_empty());
1371    }
1372
1373    async fn wait_for_event_count(events: &Arc<Mutex<Vec<(HidppMessage, bool)>>>, count: usize) {
1374        let started = Instant::now();
1375        while started.elapsed() < Duration::from_secs(1) {
1376            if events.lock().unwrap().len() >= count {
1377                return;
1378            }
1379            futures_timer::Delay::new(Duration::from_millis(10)).await;
1380        }
1381
1382        panic!("timed out waiting for {count} listener events");
1383    }
1384
1385    async fn wait_for_atomic_count(count: &AtomicUsize, expected: usize) {
1386        let started = Instant::now();
1387        while started.elapsed() < Duration::from_secs(1) {
1388            if count.load(Ordering::SeqCst) >= expected {
1389                return;
1390            }
1391            futures_timer::Delay::new(Duration::from_millis(10)).await;
1392        }
1393
1394        panic!("timed out waiting for atomic count {expected}");
1395    }
1396
1397    fn mock_error() -> Box<dyn Error + Sync + Send> {
1398        Box::new(io::Error::new(
1399            io::ErrorKind::BrokenPipe,
1400            "mock channel closed",
1401        ))
1402    }
1403}