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 Ok(len) = res else {
380                            continue;
381                        };
382
383                        let Some(msg) = HidppMessage::read_raw(&buf[..len]) else {
384                            continue;
385                        };
386
387                        let mut matched = false;
388                        {
389                            let mut msgs = pending_messages.lock().unwrap();
390                            if let Some(pos) =
391                                msgs.iter().position(|elem| (elem.response_predicate)(&msg))
392                            {
393                                let waiting = msgs.remove(pos).unwrap();
394                                let _ = waiting.sender.send(msg);
395                                matched = true;
396                            }
397                        }
398
399                        let listeners: Vec<_> = message_listeners
400                            .lock()
401                            .unwrap()
402                            .values()
403                            .cloned()
404                            .collect();
405                        for listener in listeners {
406                            listener(msg, matched);
407                        }
408                    }
409                });
410            }
411        });
412
413        Ok(Self {
414            supports_short,
415            supports_long,
416            vendor_id: raw_channel_rc.vendor_id(),
417            product_id: raw_channel_rc.product_id(),
418            raw_channel: raw_channel_rc,
419            rotate_software_id: AtomicBool::new(false),
420            software_id: AtomicU8::new(0x01),
421            pending_messages: pending_messages_rc,
422            pending_message_id: AtomicU64::new(1),
423            message_listeners: message_listeners_rc,
424            read_thread_close: Some(close_sender),
425            read_thread_hdl: Some(read_thread_hdl),
426            sw_id_lease: None,
427        })
428    }
429
430    /// Whether the underlying HID transport still reports a live connection.
431    pub fn is_connected(&self) -> bool {
432        self.raw_channel.is_connected()
433    }
434
435    /// Sets the software ID that should be returned by the next call to
436    /// [`Self::get_sw_id`].
437    ///
438    /// Using software ID `0` is highly discouraged as it is used for device
439    /// notifications.
440    pub fn set_sw_id(&self, sw_id: U4) {
441        self.software_id.store(sw_id.to_lo(), Ordering::SeqCst);
442    }
443
444    /// Sets whether the software ID returned by a call to [`Self::get_sw_id`]
445    /// should increment (and potentially wrap around) after each call.
446    ///
447    /// This comes in handy when trying to map responses to requests
448    /// consistently.
449    ///
450    /// Software ID `0` will be skipped in the rotation process as it is
451    /// reserved for device notifications.
452    pub fn set_rotating_sw_id(&self, enable: bool) {
453        self.rotate_software_id.store(enable, Ordering::SeqCst);
454    }
455
456    /// Lease software id `id` until this channel is dropped, then call `free(id)`.
457    ///
458    /// Replaces any previous lease. Used by OpenLogi so concurrent opens of the
459    /// same HID node hold distinct correlation ids for their full lifetime.
460    ///
461    /// OpenLogi local addition.
462    pub fn set_sw_id_lease(&mut self, id: u8, free: fn(u8)) {
463        self.sw_id_lease = Some((id, free));
464    }
465
466    /// Provides a software ID that can be used to send a HID++ message across
467    /// the channel.
468    ///
469    /// This method should be called separately for every message to send as it
470    /// may rotate (as indicated by [`Self::set_rotating_sw_id`]).
471    pub fn get_sw_id(&self) -> U4 {
472        if self.rotate_software_id.load(Ordering::SeqCst) {
473            U4::from_lo(
474                self.software_id
475                    .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |old| {
476                        Some(if old & 0x0f == 0x0f {
477                            0x01
478                        } else {
479                            old.wrapping_add(1)
480                        })
481                    })
482                    .unwrap(),
483            )
484        } else {
485            U4::from_lo(self.software_id.load(Ordering::SeqCst))
486        }
487    }
488
489    /// Checks whether the channel supports the given HID++ message.
490    pub fn supports_msg(&self, msg: &HidppMessage) -> bool {
491        match msg {
492            HidppMessage::Short(_) => self.supports_short,
493            HidppMessage::Long(_) => self.supports_long,
494        }
495    }
496
497    /// Re-frames a short message as long on a long-only channel — a device that
498    /// exposes only the long HID++ report (e.g. a Bluetooth-LE-direct mouse on
499    /// macOS, where `IOHIDDeviceSetReport` rejects the short report). The HID++
500    /// header bytes sit at the same offsets in both widths, so the only change
501    /// is the report id plus zero-padding the extra payload; the device answers
502    /// with a long report, which still matches the request by header. A no-op on
503    /// channels that advertise short support.
504    ///
505    /// (OpenLogi local addition — candidate for upstreaming.)
506    fn normalize_outgoing(&self, msg: HidppMessage) -> HidppMessage {
507        match msg {
508            HidppMessage::Short(payload) if !self.supports_short && self.supports_long => {
509                HidppMessage::Long(short_payload_as_long(&payload))
510            }
511            other => other,
512        }
513    }
514
515    /// Sends a HID++ message across the channel and waits for a response.
516    ///
517    /// If no response is expected/required, use [`Self::send_and_forget`].
518    ///
519    /// The whole request — the report write plus the wait for a matching
520    /// response — is bounded by [`SEND_RESPONSE_TIMEOUT`]; the future resolves
521    /// to [`ChannelError::Timeout`] on elapse. Use [`Self::send_with_timeout`]
522    /// to choose a different budget.
523    pub async fn send(
524        &self,
525        msg: HidppMessage,
526        response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
527    ) -> Result<HidppMessage, ChannelError> {
528        self.send_with_timeout(msg, response_predicate, SEND_RESPONSE_TIMEOUT)
529            .await
530    }
531
532    /// Sends a HID++ message across the channel and waits for a response,
533    /// bounding the whole request — the report write plus the wait for a
534    /// matching response — by `timeout`.
535    ///
536    /// On elapse the request's pending entry is removed (concurrent in-flight
537    /// requests are unaffected) and [`ChannelError::Timeout`] is returned; a
538    /// response that still arrives later reaches message listeners as an
539    /// unmatched message.
540    ///
541    /// [`Self::send`] uses this with [`SEND_RESPONSE_TIMEOUT`], which suits
542    /// requests to a device that may be asleep. Requests that should fail
543    /// faster — e.g. probing a receiver that answers immediately or not at
544    /// all — can pass a tighter budget.
545    pub async fn send_with_timeout(
546        &self,
547        msg: HidppMessage,
548        response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
549        timeout: Duration,
550    ) -> Result<HidppMessage, ChannelError> {
551        let msg = self.normalize_outgoing(msg);
552        if !self.supports_msg(&msg) {
553            return Err(ChannelError::MessageTypeNotSupported);
554        }
555
556        // Wire trace (off by default; `OPENLOGI_LOG=hidpp=trace`). Capture the
557        // header before `msg` is moved into the send future so the outcome line
558        // below can name the same request.
559        let (dev, feat, func) = msg.header();
560        trace!(dev, feat, func, "hidpp request");
561
562        let (sender, receiver) = oneshot::channel::<HidppMessage>();
563        let pending_id = self.pending_message_id.fetch_add(1, Ordering::SeqCst);
564
565        {
566            let mut pending = self.pending_messages.lock().unwrap();
567            // Drop abandoned requests before queuing this one. Timeouts and
568            // write failures remove their entry eagerly below, but a caller
569            // cancelled mid-flight (an outer `timeout(..)` dropping the whole
570            // future) still leaves its `PendingMessage` behind. On a channel
571            // reused across inventory ticks those would accumulate unboundedly
572            // — and a late response could be mis-delivered to a recycled
573            // software id. `is_canceled()` is true once the receiver is gone,
574            // so this prunes exactly the give-ups.
575            pending.retain(|m| !m.sender.is_canceled());
576            pending.push_back(PendingMessage {
577                id: pending_id,
578                response_predicate: Box::new(response_predicate),
579                sender,
580            });
581        }
582
583        // The deadline covers the write as well: `write_report` has no
584        // bounded-time contract of its own, so a wedged device could otherwise
585        // park `send` forever before the response wait even starts.
586        let mut request = std::pin::pin!(
587            async {
588                self.send_and_forget(msg).await?;
589                receiver.await.map_err(|_| ChannelError::NoResponse)
590            }
591            .fuse()
592        );
593
594        let result = select! {
595            result = request => result,
596            _ = futures_timer::Delay::new(timeout).fuse() => Err(ChannelError::Timeout),
597        };
598
599        match &result {
600            Ok(_) => trace!(dev, feat, "hidpp response"),
601            Err(e) => trace!(dev, feat, error = ?e, "hidpp no response"),
602        }
603
604        if result.is_err() {
605            // A timeout or write failure leaves the entry queued — remove it
606            // eagerly. After a matched response the read thread has already
607            // taken it, so this is a no-op then.
608            self.remove_pending_message(pending_id);
609        }
610
611        result
612    }
613
614    fn remove_pending_message(&self, id: u64) {
615        let mut pending = self.pending_messages.lock().unwrap();
616        if let Some(pos) = pending.iter().position(|msg| msg.id == id) {
617            pending.remove(pos);
618        }
619    }
620
621    /// Sends a HID++ message across the channel and does not wait for a
622    /// response.
623    ///
624    /// If a response is expected, use [`Self::send`],
625    pub async fn send_and_forget(&self, msg: HidppMessage) -> Result<(), ChannelError> {
626        let msg = self.normalize_outgoing(msg);
627        if !self.supports_msg(&msg) {
628            return Err(ChannelError::MessageTypeNotSupported);
629        }
630
631        let mut buf = [0u8; LONG_REPORT_LENGTH];
632        let len = msg.write_raw(&mut buf);
633        self.raw_channel
634            .write_report(&buf[..len])
635            .await
636            .map(|_| ())
637            .map_err(ChannelError::Implementation)
638    }
639
640    /// Write one raw HID report through this channel's already-owned transport.
641    ///
642    /// Reports must contain `1..=64` bytes, including their report ID. The
643    /// operation is bounded by [`SEND_RESPONSE_TIMEOUT`] and returns the exact
644    /// byte count reported by the transport. This is intended for HID++ report
645    /// widths such as the 64-byte `0x12` lighting frame that [`HidppMessage`]
646    /// cannot represent.
647    pub async fn write_raw_report(&self, report: &[u8]) -> Result<usize, ChannelError> {
648        self.write_raw_report_with_timeout(report, SEND_RESPONSE_TIMEOUT)
649            .await
650    }
651
652    async fn write_raw_report_with_timeout(
653        &self,
654        report: &[u8],
655        timeout: Duration,
656    ) -> Result<usize, ChannelError> {
657        if !(1..=MAX_RAW_REPORT_LENGTH).contains(&report.len()) {
658            return Err(ChannelError::InvalidRawReportLength(report.len()));
659        }
660
661        let mut write = std::pin::pin!(self.raw_channel.write_report(report).fuse());
662        select! {
663            result = write => result.map_err(ChannelError::Implementation),
664            _ = futures_timer::Delay::new(timeout).fuse() => Err(ChannelError::Timeout),
665        }
666    }
667
668    /// Registers a listener that will be called for every incoming message.
669    ///
670    /// Returns a handle that can be used to remove the listener using a call to
671    /// [`Self::remove_msg_listener`].
672    pub fn add_msg_listener(
673        &self,
674        listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
675    ) -> u32 {
676        let mut listeners = self.message_listeners.lock().unwrap();
677
678        let mut rng = rand::rng();
679        let mut hdl = rng.random::<u32>();
680        while listeners.contains_key(&hdl) {
681            hdl = rng.random::<u32>();
682        }
683
684        listeners.insert(hdl, Arc::new(listener));
685        hdl
686    }
687
688    /// Registers a listener that is automatically removed when the returned
689    /// guard is dropped.
690    pub fn add_msg_listener_guarded(
691        &self,
692        listener: impl Fn(HidppMessage, bool) + Send + Sync + 'static,
693    ) -> MessageListenerGuard {
694        let hdl = self.add_msg_listener(listener);
695        MessageListenerGuard {
696            message_listeners: Arc::downgrade(&self.message_listeners),
697            hdl,
698        }
699    }
700
701    /// Removes a previously registered message listener.
702    ///
703    /// Returns whether a listener was found using the given handle.
704    pub fn remove_msg_listener(&self, hdl: u32) -> bool {
705        self.message_listeners
706            .lock()
707            .unwrap()
708            .remove(&hdl)
709            .is_some()
710    }
711}
712
713/// Represents an error that occurred when creating or interacting with a HID or
714/// HID++ communication channel.
715#[derive(Debug, Error)]
716#[non_exhaustive]
717pub enum ChannelError {
718    /// Indicates that the concrete implementation of [`RawHidChannel`] returned
719    /// an error.
720    #[error("the HID channel implementation returned an error")]
721    Implementation(#[from] Box<dyn Error + Sync + Send>),
722
723    /// Indicates that the HID report descriptor could not be parsed.
724    #[error("the report descriptor could not be parsed")]
725    ReportDescriptor(hidreport::ParserError),
726
727    /// Indicates that the channel in question does not support HID++.
728    #[error("the HID channel does not support HID++")]
729    HidppNotSupported,
730
731    /// Indicates that the HID++ channel does not support messages of the given
732    /// type (short/long).
733    #[error("the channel does not support the given HID++ message type")]
734    MessageTypeNotSupported,
735
736    /// Indicates that a raw output report was empty or exceeded 64 bytes.
737    #[error("raw HID reports must contain 1..=64 bytes, got {0}")]
738    InvalidRawReportLength(usize),
739
740    /// Indicates that no response was received following a request.
741    #[error("the device did not respond to the request")]
742    NoResponse,
743
744    /// Indicates that a bounded channel operation did not complete — typically
745    /// because the device is asleep, out of range, connected to another host,
746    /// or its transport write is wedged. See
747    /// [`HidppChannel::send_with_timeout`] and
748    /// [`HidppChannel::write_raw_report`].
749    #[error("the HID channel operation timed out")]
750    Timeout,
751}
752
753/// Widen a short HID++ payload (6 bytes) to a long one (19 bytes): the HID++
754/// header bytes (device / feature / function|sw) sit at the same offsets in
755/// both widths, so the only change is zero-padding the trailing payload. Used
756/// to re-frame short messages as long on a long-only channel — see
757/// [`HidppChannel::normalize_outgoing`]. (OpenLogi local addition.)
758fn short_payload_as_long(payload: &[u8; SHORT_REPORT_LENGTH - 1]) -> [u8; LONG_REPORT_LENGTH - 1] {
759    let mut long = [0u8; LONG_REPORT_LENGTH - 1];
760    long[..payload.len()].copy_from_slice(payload);
761    long
762}
763
764#[cfg(test)]
765pub(crate) mod tests {
766    use super::*;
767    use std::{
768        io,
769        sync::{
770            Arc, Mutex,
771            atomic::{AtomicBool, AtomicUsize, Ordering},
772        },
773        time::{Duration, Instant},
774    };
775
776    use crate::{
777        nibble,
778        protocol::v20::{self, ErrorType, Hidpp20Error},
779    };
780
781    #[test]
782    fn short_payload_widens_preserving_header_and_padding() {
783        // [device, feature, function|sw, p0, p1, p2]
784        let short = [0xff, 0x05, 0x1e, 0xaa, 0xbb, 0xcc];
785        let long = short_payload_as_long(&short);
786        assert_eq!(&long[..short.len()], &short[..]); // header + payload copied verbatim
787        assert!(long[short.len()..].iter().all(|&b| b == 0)); // remainder zero-padded
788        assert_eq!(long.len(), LONG_REPORT_LENGTH - 1);
789    }
790
791    #[test]
792    fn send_returns_response_before_timeout() {
793        futures::executor::block_on(async {
794            let (raw, handle) = MockRawHidChannel::new();
795            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
796
797            let request = short_msg(0x10);
798            let response = short_msg(0x20);
799            handle.queue_response(response);
800
801            let actual = channel
802                .send_with_timeout(
803                    request,
804                    move |candidate| *candidate == response,
805                    Duration::from_secs(1),
806                )
807                .await
808                .unwrap();
809
810            assert_eq!(actual, response);
811            assert_eq!(handle.written_reports().len(), 1);
812            assert_pending_empty(&channel);
813        });
814    }
815
816    #[test]
817    fn send_times_out_and_removes_pending_message() {
818        futures::executor::block_on(async {
819            let (raw, handle) = MockRawHidChannel::new();
820            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
821            let request = short_msg(0x10);
822            let response = short_msg(0x20);
823
824            let started = Instant::now();
825            let err = channel
826                .send_with_timeout(
827                    request,
828                    move |candidate| *candidate == response,
829                    Duration::from_millis(25),
830                )
831                .await
832                .unwrap_err();
833
834            assert!(matches!(err, ChannelError::Timeout));
835            assert!(started.elapsed() < Duration::from_secs(1));
836            assert_eq!(handle.written_reports().len(), 1);
837            assert_pending_empty(&channel);
838        });
839    }
840
841    #[test]
842    fn timeout_removes_only_its_own_pending_message() {
843        futures::executor::block_on(async {
844            let (raw, handle) = MockRawHidChannel::new();
845            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
846
847            let never_answered = short_msg(0x20);
848            let slow_response = short_msg(0x21);
849
850            let timed_out = channel.send_with_timeout(
851                short_msg(0x10),
852                move |candidate| *candidate == never_answered,
853                Duration::from_millis(25),
854            );
855            let answered = channel.send_with_timeout(
856                short_msg(0x11),
857                move |candidate| *candidate == slow_response,
858                Duration::from_secs(1),
859            );
860            // Answer the second request only after the first has timed out, so
861            // a removal that took the wrong entry would fail this test.
862            let respond_late = async {
863                futures_timer::Delay::new(Duration::from_millis(100)).await;
864                handle.send_incoming(slow_response).await;
865            };
866
867            let (timed_out, answered, ()) = futures::join!(timed_out, answered, respond_late);
868
869            assert!(matches!(timed_out.unwrap_err(), ChannelError::Timeout));
870            assert_eq!(answered.unwrap(), slow_response);
871            assert_pending_empty(&channel);
872        });
873    }
874
875    #[test]
876    fn late_response_after_timeout_is_ignored() {
877        futures::executor::block_on(async {
878            let (raw, handle) = MockRawHidChannel::new();
879            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
880            let events = Arc::new(Mutex::new(Vec::new()));
881            let listener_events = Arc::clone(&events);
882            channel.add_msg_listener(move |msg, matched| {
883                listener_events.lock().unwrap().push((msg, matched));
884            });
885
886            let request = short_msg(0x10);
887            let late_response = short_msg(0x20);
888            let err = channel
889                .send_with_timeout(
890                    request,
891                    move |candidate| *candidate == late_response,
892                    Duration::from_millis(25),
893                )
894                .await
895                .unwrap_err();
896
897            assert!(matches!(err, ChannelError::Timeout));
898            assert_pending_empty(&channel);
899
900            handle.send_incoming(late_response).await;
901            wait_for_event_count(&events, 1).await;
902            assert_eq!(events.lock().unwrap()[0], (late_response, false));
903            assert_pending_empty(&channel);
904
905            let later_request = short_msg(0x30);
906            let later_response = short_msg(0x40);
907            handle.queue_response(later_response);
908            let actual = channel
909                .send_with_timeout(
910                    later_request,
911                    move |candidate| *candidate == later_response,
912                    Duration::from_secs(1),
913                )
914                .await
915                .unwrap();
916
917            assert_eq!(actual, later_response);
918            wait_for_event_count(&events, 2).await;
919            assert_eq!(events.lock().unwrap()[1], (later_response, true));
920            assert_pending_empty(&channel);
921        });
922    }
923
924    #[test]
925    fn send_and_forget_writes_without_pending_message() {
926        futures::executor::block_on(async {
927            let (raw, handle) = MockRawHidChannel::new();
928            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
929
930            channel.send_and_forget(short_msg(0x10)).await.unwrap();
931
932            assert_eq!(handle.written_reports().len(), 1);
933            assert_pending_empty(&channel);
934        });
935    }
936
937    #[test]
938    fn raw_report_write_forwards_exact_bytes_and_length() {
939        futures::executor::block_on(async {
940            let (raw, handle) = MockRawHidChannel::new();
941            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
942            let report = [0x12; MAX_RAW_REPORT_LENGTH];
943
944            let written = channel.write_raw_report(&report).await.unwrap();
945
946            assert_eq!(written, report.len());
947            assert_eq!(handle.written_reports(), [report.to_vec()]);
948        });
949    }
950
951    #[test]
952    fn raw_report_write_rejects_empty_and_oversized_inputs_without_io() {
953        futures::executor::block_on(async {
954            let (raw, handle) = MockRawHidChannel::new();
955            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
956
957            let empty = channel.write_raw_report(&[]).await.unwrap_err();
958            let oversized = channel
959                .write_raw_report(&[0; MAX_RAW_REPORT_LENGTH + 1])
960                .await
961                .unwrap_err();
962
963            assert!(matches!(empty, ChannelError::InvalidRawReportLength(0)));
964            assert!(matches!(
965                oversized,
966                ChannelError::InvalidRawReportLength(65)
967            ));
968            assert!(handle.written_reports().is_empty());
969        });
970    }
971
972    #[test]
973    fn raw_report_write_times_out_when_the_transport_parks() {
974        futures::executor::block_on(async {
975            let (raw, handle) = MockRawHidChannel::new();
976            handle.park_writes();
977            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
978            let started = Instant::now();
979
980            let error = channel
981                .write_raw_report_with_timeout(&[LONG_REPORT_ID], Duration::from_millis(25))
982                .await
983                .unwrap_err();
984
985            assert!(matches!(error, ChannelError::Timeout));
986            assert!(started.elapsed() < Duration::from_secs(1));
987        });
988    }
989
990    #[test]
991    fn listener_can_remove_another_listener_during_dispatch() {
992        futures::executor::block_on(async {
993            let (raw, handle) = MockRawHidChannel::new();
994            let channel = Arc::new(HidppChannel::from_raw_channel(raw).await.unwrap());
995            let removed_listener_calls = Arc::new(AtomicUsize::new(0));
996            let removing_listener_calls = Arc::new(AtomicUsize::new(0));
997
998            let removed_listener_calls_for_listener = Arc::clone(&removed_listener_calls);
999            let removed_hdl = channel.add_msg_listener(move |_, _| {
1000                removed_listener_calls_for_listener.fetch_add(1, Ordering::SeqCst);
1001            });
1002
1003            let channel_for_listener = Arc::clone(&channel);
1004            let removing_listener_calls_for_listener = Arc::clone(&removing_listener_calls);
1005            channel.add_msg_listener(move |_, _| {
1006                removing_listener_calls_for_listener.fetch_add(1, Ordering::SeqCst);
1007                channel_for_listener.remove_msg_listener(removed_hdl);
1008            });
1009
1010            handle.send_incoming(short_msg(0x20)).await;
1011            wait_for_atomic_count(&removing_listener_calls, 1).await;
1012            wait_for_atomic_count(&removed_listener_calls, 1).await;
1013
1014            handle.send_incoming(short_msg(0x21)).await;
1015            wait_for_atomic_count(&removing_listener_calls, 2).await;
1016
1017            assert_eq!(removed_listener_calls.load(Ordering::SeqCst), 1);
1018        });
1019    }
1020
1021    // --- HID++2.0 (v20) send/matcher characterization tests -----------------
1022    //
1023    // `HidppChannel::send`/`send_with_timeout` above are protocol-agnostic:
1024    // they match on an arbitrary predicate over raw `HidppMessage`s. The
1025    // v20-specific correlation logic (matching by header, splitting out error
1026    // frames) lives in `protocol::v20::HidppChannel::send_v20`, which is built
1027    // directly on top of `send`. These tests pin that logic's current
1028    // behaviour using the same mock transport as the tests above.
1029
1030    #[test]
1031    fn send_v20_matches_response_by_header_ignoring_unrelated_messages() {
1032        futures::executor::block_on(async {
1033            let (raw, handle) = MockRawHidChannel::new();
1034            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1035
1036            let header = v20::MessageHeader {
1037                device_index: 0x01,
1038                feature_index: 0x05,
1039                function_id: U4::from_lo(0x2),
1040                software_id: U4::from_lo(0x3),
1041            };
1042            let request = v20::Message::Short(header, [0x00, 0x00, 0x00]);
1043            let response = v20::Message::Short(header, [0xaa, 0xbb, 0xcc]);
1044
1045            // Each decoy differs from the request in exactly one header field, so
1046            // none of them may be mistaken for its response.
1047            let wrong_device = v20::Message::Short(
1048                v20::MessageHeader {
1049                    device_index: 0x02,
1050                    ..header
1051                },
1052                [0, 0, 0],
1053            );
1054            let wrong_feature = v20::Message::Short(
1055                v20::MessageHeader {
1056                    feature_index: 0x06,
1057                    ..header
1058                },
1059                [0, 0, 0],
1060            );
1061            let wrong_sw_id = v20::Message::Short(
1062                v20::MessageHeader {
1063                    software_id: U4::from_lo(0x4),
1064                    ..header
1065                },
1066                [0, 0, 0],
1067            );
1068
1069            let send_fut = channel.send_v20(request);
1070            let feed_fut = async {
1071                handle.send_incoming(wrong_device.into()).await;
1072                handle.send_incoming(wrong_feature.into()).await;
1073                handle.send_incoming(wrong_sw_id.into()).await;
1074                handle.send_incoming(response.into()).await;
1075            };
1076
1077            let (result, ()) = futures::join!(send_fut, feed_fut);
1078
1079            assert_eq!(result.unwrap(), response);
1080            assert_pending_empty(&channel);
1081        });
1082    }
1083
1084    #[test]
1085    fn send_v20_broadcast_event_does_not_resolve_pending_request() {
1086        futures::executor::block_on(async {
1087            let (raw, handle) = MockRawHidChannel::new();
1088            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1089            let events = Arc::new(Mutex::new(Vec::new()));
1090            let listener_events = Arc::clone(&events);
1091            channel.add_msg_listener(move |msg, matched| {
1092                listener_events.lock().unwrap().push((msg, matched));
1093            });
1094
1095            let header = v20::MessageHeader {
1096                device_index: 0x01,
1097                feature_index: 0x05,
1098                function_id: U4::from_lo(0x2),
1099                software_id: U4::from_lo(0x3),
1100            };
1101            let request = v20::Message::Short(header, [0, 0, 0]);
1102            let response = v20::Message::Short(header, [0xaa, 0xbb, 0xcc]);
1103
1104            // Software ID 0 is reserved for unsolicited device notifications
1105            // (see `feature::event_payload`). The request above uses a non-zero
1106            // ID, so an incoming broadcast sharing device/feature but using ID 0
1107            // must be routed to listeners, not consumed as this request's
1108            // response.
1109            let event = v20::Message::Short(
1110                v20::MessageHeader {
1111                    software_id: U4::from_lo(0x0),
1112                    ..header
1113                },
1114                [0x01, 0x02, 0x03],
1115            );
1116
1117            let send_fut = channel.send_v20(request);
1118            let feed_fut = async {
1119                handle.send_incoming(event.into()).await;
1120                wait_for_event_count(&events, 1).await;
1121                handle.send_incoming(response.into()).await;
1122            };
1123
1124            let (result, ()) = futures::join!(send_fut, feed_fut);
1125
1126            assert_eq!(result.unwrap(), response);
1127            // The oneshot resolves before the listener loop runs on the read
1128            // thread; wait for both deliveries before asserting on them.
1129            wait_for_event_count(&events, 2).await;
1130            let recorded = events.lock().unwrap().clone();
1131            assert_eq!(
1132                recorded,
1133                vec![
1134                    (HidppMessage::from(event), false),
1135                    (HidppMessage::from(response), true),
1136                ]
1137            );
1138            assert_pending_empty(&channel);
1139        });
1140    }
1141
1142    #[test]
1143    fn send_v20_response_may_arrive_as_a_different_report_width() {
1144        futures::executor::block_on(async {
1145            let (raw, handle) = MockRawHidChannel::new();
1146            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1147
1148            let header = v20::MessageHeader {
1149                device_index: 0x01,
1150                feature_index: 0x05,
1151                function_id: U4::from_lo(0x2),
1152                software_id: U4::from_lo(0x3),
1153            };
1154            let request = v20::Message::Short(header, [0, 0, 0]);
1155            // Quirk: `send_v20`'s response predicate compares only the parsed
1156            // v20 header, not the underlying report width. A device replying
1157            // with a long report to a short request — same header, wider
1158            // payload — is still accepted as the response.
1159            let response = v20::Message::Long(header, [0xaa; 16]);
1160            handle.queue_response(response.into());
1161
1162            let result = channel.send_v20(request).await.unwrap();
1163
1164            assert_eq!(result, response);
1165            assert_pending_empty(&channel);
1166        });
1167    }
1168
1169    #[test]
1170    fn send_v20_error_frame_resolves_to_feature_error() {
1171        futures::executor::block_on(async {
1172            let (raw, handle) = MockRawHidChannel::new();
1173            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1174
1175            let header = v20::MessageHeader {
1176                device_index: 0x01,
1177                feature_index: 0x05,
1178                function_id: U4::from_lo(0x2),
1179                software_id: U4::from_lo(0x3),
1180            };
1181            let request = v20::Message::Short(header, [0, 0, 0]);
1182            let error_response = v20_error_frame(header, ErrorType::InvalidArgument.into());
1183            handle.queue_response(error_response.into());
1184
1185            let err = channel.send_v20(request).await.unwrap_err();
1186
1187            assert!(matches!(
1188                err,
1189                Hidpp20Error::Feature(ErrorType::InvalidArgument)
1190            ));
1191            assert_pending_empty(&channel);
1192        });
1193    }
1194
1195    #[test]
1196    fn send_v20_error_frame_with_unmapped_code_is_unsupported_response() {
1197        futures::executor::block_on(async {
1198            let (raw, handle) = MockRawHidChannel::new();
1199            let channel = HidppChannel::from_raw_channel(raw).await.unwrap();
1200
1201            let header = v20::MessageHeader {
1202                device_index: 0x01,
1203                feature_index: 0x05,
1204                function_id: U4::from_lo(0x2),
1205                software_id: U4::from_lo(0x3),
1206            };
1207            let request = v20::Message::Short(header, [0, 0, 0]);
1208            // 0xfe is not a defined `ErrorType` variant.
1209            let error_response = v20_error_frame(header, 0xfe);
1210            handle.queue_response(error_response.into());
1211
1212            let err = channel.send_v20(request).await.unwrap_err();
1213
1214            assert!(matches!(err, Hidpp20Error::UnsupportedResponse));
1215            assert_pending_empty(&channel);
1216        });
1217    }
1218
1219    /// Builds the HID++2.0 error-frame encoding for `request_header`: feature
1220    /// index 0xFF, with the original feature index and function|software byte
1221    /// shifted one byte to the right (see `v20::HidppChannel::send_v20`'s
1222    /// `is_error` predicate for the reverse mapping).
1223    fn v20_error_frame(request_header: v20::MessageHeader, error_code: u8) -> v20::Message {
1224        let error_header = v20::MessageHeader {
1225            device_index: request_header.device_index,
1226            feature_index: 0xff,
1227            function_id: U4::from_hi(request_header.feature_index),
1228            software_id: U4::from_lo(request_header.feature_index),
1229        };
1230        let mut payload = [0u8; 3];
1231        payload[0] = nibble::combine(request_header.function_id, request_header.software_id);
1232        payload[1] = error_code;
1233        v20::Message::Short(error_header, payload)
1234    }
1235
1236    #[derive(Clone)]
1237    pub(crate) struct MockRawHidHandle {
1238        incoming_tx: async_channel::Sender<Vec<u8>>,
1239        written_reports: Arc<Mutex<Vec<Vec<u8>>>>,
1240        responses_on_write: Arc<Mutex<VecDeque<Vec<u8>>>>,
1241        park_writes: Arc<AtomicBool>,
1242    }
1243
1244    impl MockRawHidHandle {
1245        pub(crate) fn queue_response(&self, msg: HidppMessage) {
1246            self.responses_on_write
1247                .lock()
1248                .unwrap()
1249                .push_back(raw_report(msg));
1250        }
1251
1252        async fn send_incoming(&self, msg: HidppMessage) {
1253            self.incoming_tx.send(raw_report(msg)).await.unwrap();
1254        }
1255
1256        pub(crate) fn written_reports(&self) -> Vec<Vec<u8>> {
1257            self.written_reports.lock().unwrap().clone()
1258        }
1259
1260        fn park_writes(&self) {
1261            self.park_writes.store(true, Ordering::SeqCst);
1262        }
1263    }
1264
1265    pub(crate) struct MockRawHidChannel {
1266        incoming_tx: async_channel::Sender<Vec<u8>>,
1267        incoming_rx: async_channel::Receiver<Vec<u8>>,
1268        written_reports: Arc<Mutex<Vec<Vec<u8>>>>,
1269        responses_on_write: Arc<Mutex<VecDeque<Vec<u8>>>>,
1270        park_writes: Arc<AtomicBool>,
1271    }
1272
1273    impl MockRawHidChannel {
1274        pub(crate) fn new() -> (Self, MockRawHidHandle) {
1275            let (incoming_tx, incoming_rx) = async_channel::unbounded();
1276            let written_reports = Arc::new(Mutex::new(Vec::new()));
1277            let responses_on_write = Arc::new(Mutex::new(VecDeque::new()));
1278            let park_writes = Arc::new(AtomicBool::new(false));
1279
1280            let handle = MockRawHidHandle {
1281                incoming_tx: incoming_tx.clone(),
1282                written_reports: Arc::clone(&written_reports),
1283                responses_on_write: Arc::clone(&responses_on_write),
1284                park_writes: Arc::clone(&park_writes),
1285            };
1286
1287            (
1288                Self {
1289                    incoming_tx,
1290                    incoming_rx,
1291                    written_reports,
1292                    responses_on_write,
1293                    park_writes,
1294                },
1295                handle,
1296            )
1297        }
1298    }
1299
1300    #[async_trait]
1301    impl RawHidChannel for MockRawHidChannel {
1302        fn vendor_id(&self) -> u16 {
1303            0x046d
1304        }
1305
1306        fn product_id(&self) -> u16 {
1307            0xc539
1308        }
1309
1310        async fn write_report(&self, src: &[u8]) -> Result<usize, Box<dyn Error + Sync + Send>> {
1311            self.written_reports.lock().unwrap().push(src.to_vec());
1312            if self.park_writes.load(Ordering::SeqCst) {
1313                return std::future::pending().await;
1314            }
1315            let response = self.responses_on_write.lock().unwrap().pop_front();
1316            if let Some(response) = response {
1317                self.incoming_tx.send(response).await.unwrap();
1318            }
1319
1320            Ok(src.len())
1321        }
1322
1323        async fn read_report(&self, buf: &mut [u8]) -> Result<usize, Box<dyn Error + Sync + Send>> {
1324            let report = self.incoming_rx.recv().await.map_err(|_| mock_error())?;
1325            let len = report.len().min(buf.len());
1326            buf[..len].copy_from_slice(&report[..len]);
1327            Ok(len)
1328        }
1329
1330        fn supports_short_long_hidpp(&self) -> Option<(bool, bool)> {
1331            Some((true, true))
1332        }
1333
1334        async fn get_report_descriptor(
1335            &self,
1336            _buf: &mut [u8],
1337        ) -> Result<usize, Box<dyn Error + Sync + Send>> {
1338            unreachable!("mock declares HID++ support")
1339        }
1340    }
1341
1342    fn short_msg(marker: u8) -> HidppMessage {
1343        HidppMessage::Short([0xff, marker, 0x10, marker, marker, marker])
1344    }
1345
1346    fn raw_report(msg: HidppMessage) -> Vec<u8> {
1347        let mut buf = [0u8; LONG_REPORT_LENGTH];
1348        let len = msg.write_raw(&mut buf);
1349        buf[..len].to_vec()
1350    }
1351
1352    fn assert_pending_empty(channel: &HidppChannel) {
1353        assert!(channel.pending_messages.lock().unwrap().is_empty());
1354    }
1355
1356    async fn wait_for_event_count(events: &Arc<Mutex<Vec<(HidppMessage, bool)>>>, count: usize) {
1357        let started = Instant::now();
1358        while started.elapsed() < Duration::from_secs(1) {
1359            if events.lock().unwrap().len() >= count {
1360                return;
1361            }
1362            futures_timer::Delay::new(Duration::from_millis(10)).await;
1363        }
1364
1365        panic!("timed out waiting for {count} listener events");
1366    }
1367
1368    async fn wait_for_atomic_count(count: &AtomicUsize, expected: usize) {
1369        let started = Instant::now();
1370        while started.elapsed() < Duration::from_secs(1) {
1371            if count.load(Ordering::SeqCst) >= expected {
1372                return;
1373            }
1374            futures_timer::Delay::new(Duration::from_millis(10)).await;
1375        }
1376
1377        panic!("timed out waiting for atomic count {expected}");
1378    }
1379
1380    fn mock_error() -> Box<dyn Error + Sync + Send> {
1381        Box::new(io::Error::new(
1382            io::ErrorKind::BrokenPipe,
1383            "mock channel closed",
1384        ))
1385    }
1386}