1use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, PoisonError, RwLock};
11
12use hidpp::channel::{HidppChannel, HidppMessage, MessageListenerGuard};
13use hidpp::feature::adc_measurement::AdcMeasurementFeature;
14use hidpp::feature::unified_battery::BatteryEvent;
15use hidpp::feature::wireless_device_status::WirelessDeviceStatusEvent;
16use hidpp::protocol::{v10, v20};
17use hidpp::receiver::{bolt, unifying};
18use serde::{Deserialize, Serialize};
19#[cfg(test)]
20use tokio::sync::Notify;
21use tokio::sync::mpsc;
22
23use crate::ReceiverProtocol;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum HidppEventSource {
29 ReceiverConnection,
31 WirelessDeviceStatus,
33 UnifiedBattery,
35 AdcMeasurement,
38}
39
40#[derive(Clone)]
46pub struct EventNotifier {
47 sender: mpsc::Sender<HidppEventSource>,
48 #[cfg(test)]
49 observation: Option<Arc<EventObservation>>,
50}
51
52impl EventNotifier {
53 fn notify(&self, source: HidppEventSource) {
54 let _ = self.sender.try_send(source);
55 #[cfg(test)]
56 if let Some(observation) = &self.observation {
57 observation.count.fetch_add(1, Ordering::Release);
58 observation.changed.notify_waiters();
59 }
60 }
61}
62
63pub type EventReceiver = mpsc::Receiver<HidppEventSource>;
65
66#[must_use]
68pub fn event_channel() -> (EventNotifier, EventReceiver) {
69 let (sender, receiver) = mpsc::channel(1);
70 (
71 EventNotifier {
72 sender,
73 #[cfg(test)]
74 observation: None,
75 },
76 receiver,
77 )
78}
79
80#[cfg(test)]
81struct EventObservation {
82 count: AtomicUsize,
83 changed: Notify,
84}
85
86#[cfg(test)]
88pub(crate) struct EventObserver {
89 observation: Arc<EventObservation>,
90}
91
92#[cfg(test)]
93impl EventObserver {
94 pub(crate) async fn wait_for(&self, expected: usize) {
95 loop {
96 let changed = self.observation.changed.notified();
97 if self.observation.count.load(Ordering::Acquire) >= expected {
98 return;
99 }
100 changed.await;
101 }
102 }
103}
104
105#[cfg(test)]
107pub(crate) fn observed_event_channel() -> (EventNotifier, EventReceiver, EventObserver) {
108 let (sender, receiver) = mpsc::channel(1);
109 let observation = Arc::new(EventObservation {
110 count: AtomicUsize::new(0),
111 changed: Notify::new(),
112 });
113 (
114 EventNotifier {
115 sender,
116 observation: Some(Arc::clone(&observation)),
117 },
118 receiver,
119 EventObserver { observation },
120 )
121}
122
123#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
130pub(super) struct EventFeatureIndices {
131 pub(super) wireless_status: Option<u8>,
132 pub(super) unified_battery: Option<u8>,
133 pub(super) adc_measurement: Option<u8>,
134}
135
136impl EventFeatureIndices {
137 pub(super) fn from_feature_ids(ids: &[u16]) -> Self {
138 let mut indices = Self::default();
139 for (position, id) in ids.iter().copied().enumerate() {
140 let Ok(index) = u8::try_from(position + 1) else {
141 break;
142 };
143 match id {
144 0x1d4b => indices.wireless_status = Some(index),
145 0x1004 => indices.unified_battery = Some(index),
146 0x1f20 => indices.adc_measurement = Some(index),
147 _ => {}
148 }
149 }
150 indices
151 }
152
153 fn recognizes(self, message: &v20::Message) -> Option<HidppEventSource> {
154 let header = message.header();
155 if header.software_id.to_lo() != 0 {
156 return None;
157 }
158 let function_id = header.function_id.to_lo();
159 let payload = message.extend_payload();
160 if self.wireless_status == Some(header.feature_index)
161 && WirelessDeviceStatusEvent::decode(function_id, &payload).is_some()
162 {
163 return Some(HidppEventSource::WirelessDeviceStatus);
164 }
165 if self.unified_battery == Some(header.feature_index)
166 && BatteryEvent::decode(function_id, &payload).is_some()
167 {
168 return Some(HidppEventSource::UnifiedBattery);
169 }
170 if self.adc_measurement == Some(header.feature_index)
173 && AdcMeasurementFeature::is_status_broadcast(function_id)
174 {
175 return Some(HidppEventSource::AdcMeasurement);
176 }
177 None
178 }
179}
180
181#[derive(Clone, Copy)]
182struct DeviceEvents {
183 device_index: u8,
184 features: EventFeatureIndices,
185}
186
187struct SubscriptionState {
188 protocol: Option<ReceiverProtocol>,
189 devices: RwLock<Vec<DeviceEvents>>,
190 receiver_snapshot_depth: AtomicUsize,
191 notifier: EventNotifier,
192}
193
194impl SubscriptionState {
195 fn decode(&self, raw: HidppMessage, matched: bool) -> Option<HidppEventSource> {
196 if matched {
197 return None;
198 }
199
200 let receiver_connection = match self.protocol {
201 Some(ReceiverProtocol::Bolt) => matches!(
202 bolt::decode_notification(&v10::Message::from(raw)),
203 Some(bolt::Event::DeviceConnection(_))
204 ),
205 Some(ReceiverProtocol::Unifying) => matches!(
206 unifying::decode_notification(&v10::Message::from(raw)),
207 Some(unifying::Event::DeviceConnection(_))
208 ),
209 None => false,
210 };
211 if receiver_connection {
212 return (self.receiver_snapshot_depth.load(Ordering::Acquire) == 0)
213 .then_some(HidppEventSource::ReceiverConnection);
214 }
215
216 let message = v20::Message::from(raw);
217 let device_index = message.header().device_index;
218 self.devices
219 .read()
220 .unwrap_or_else(PoisonError::into_inner)
221 .iter()
222 .find(|device| device.device_index == device_index)
223 .and_then(|device| device.features.recognizes(&message))
224 }
225}
226
227#[derive(Clone)]
229pub(super) struct EventSubscriptionHandle {
230 state: Arc<SubscriptionState>,
231}
232
233impl EventSubscriptionHandle {
234 pub(super) fn register_device(&self, device_index: u8, features: EventFeatureIndices) {
239 let mut devices = self
240 .state
241 .devices
242 .write()
243 .unwrap_or_else(PoisonError::into_inner);
244 if let Some(device) = devices
245 .iter_mut()
246 .find(|device| device.device_index == device_index)
247 {
248 device.features = features;
249 } else {
250 devices.push(DeviceEvents {
251 device_index,
252 features,
253 });
254 }
255 }
256
257 pub(super) fn begin_receiver_snapshot(&self) -> ReceiverSnapshotGuard {
261 self.state
262 .receiver_snapshot_depth
263 .fetch_add(1, Ordering::AcqRel);
264 ReceiverSnapshotGuard {
265 state: Arc::clone(&self.state),
266 }
267 }
268}
269
270pub(super) struct ChannelEventSubscriptions {
272 handle: EventSubscriptionHandle,
273 _listener: MessageListenerGuard,
274}
275
276impl ChannelEventSubscriptions {
277 pub(super) fn attach(
278 channel: &Arc<HidppChannel>,
279 protocol: Option<ReceiverProtocol>,
280 notifier: EventNotifier,
281 ) -> Self {
282 let state = Arc::new(SubscriptionState {
283 protocol,
284 devices: RwLock::new(Vec::new()),
285 receiver_snapshot_depth: AtomicUsize::new(0),
286 notifier,
287 });
288 let listener = channel.add_msg_listener_guarded({
289 let state = Arc::clone(&state);
290 move |raw, matched| {
291 if let Some(source) = state.decode(raw, matched) {
292 state.notifier.notify(source);
293 }
294 }
295 });
296 Self {
297 handle: EventSubscriptionHandle { state },
298 _listener: listener,
299 }
300 }
301
302 pub(super) fn handle(&self) -> EventSubscriptionHandle {
303 self.handle.clone()
304 }
305}
306
307pub(super) struct ReceiverSnapshotGuard {
309 state: Arc<SubscriptionState>,
310}
311
312impl Drop for ReceiverSnapshotGuard {
313 fn drop(&mut self) {
314 self.state
315 .receiver_snapshot_depth
316 .fetch_sub(1, Ordering::AcqRel);
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use hidpp::nibble::U4;
323 use hidpp::protocol::{v10, v20};
324
325 use super::*;
326
327 fn state(protocol: Option<ReceiverProtocol>) -> Arc<SubscriptionState> {
328 let (notifier, _receiver) = event_channel();
329 Arc::new(SubscriptionState {
330 protocol,
331 devices: RwLock::new(Vec::new()),
332 receiver_snapshot_depth: AtomicUsize::new(0),
333 notifier,
334 })
335 }
336
337 #[test]
338 fn feature_indices_are_runtime_table_positions() {
339 assert_eq!(
340 EventFeatureIndices::from_feature_ids(&[0x0001, 0x1d4b, 0x1004]),
341 EventFeatureIndices {
342 wireless_status: Some(2),
343 unified_battery: Some(3),
344 adc_measurement: None,
345 }
346 );
347 }
348
349 #[test]
350 fn receiver_snapshot_suppresses_only_triggered_connection_wakeups() {
351 let state = state(Some(ReceiverProtocol::Unifying));
352 let raw = v10::Message::Short(
353 v10::MessageHeader {
354 device_index: 2,
355 sub_id: 0x41,
356 },
357 [0, 0x02, 0x34, 0x12],
358 )
359 .into();
360
361 assert_eq!(
362 state.decode(raw, false),
363 Some(HidppEventSource::ReceiverConnection)
364 );
365 state.receiver_snapshot_depth.store(1, Ordering::Release);
366 assert_eq!(state.decode(raw, false), None);
367 }
368
369 #[test]
370 fn receiver_snapshot_does_not_suppress_device_feature_events() {
371 let state = state(Some(ReceiverProtocol::Bolt));
372 state.devices.write().unwrap().push(DeviceEvents {
373 device_index: 2,
374 features: EventFeatureIndices {
375 wireless_status: Some(5),
376 unified_battery: None,
377 adc_measurement: None,
378 },
379 });
380 state.receiver_snapshot_depth.store(1, Ordering::Release);
381 let wireless = v20::Message::Long(
382 v20::MessageHeader {
383 device_index: 2,
384 feature_index: 5,
385 function_id: U4::from_lo(0),
386 software_id: U4::from_lo(0),
387 },
388 [0; 16],
389 )
390 .into();
391
392 assert_eq!(
393 state.decode(wireless, false),
394 Some(HidppEventSource::WirelessDeviceStatus)
395 );
396 }
397
398 #[test]
399 fn registered_device_events_decode_to_typed_sources() {
400 let state = state(None);
401 state.devices.write().unwrap().push(DeviceEvents {
402 device_index: 3,
403 features: EventFeatureIndices {
404 wireless_status: Some(5),
405 unified_battery: Some(7),
406 adc_measurement: None,
407 },
408 });
409
410 let wireless = v20::Message::Long(
411 v20::MessageHeader {
412 device_index: 3,
413 feature_index: 5,
414 function_id: U4::from_lo(0),
415 software_id: U4::from_lo(0),
416 },
417 [0; 16],
418 )
419 .into();
420 assert_eq!(
421 state.decode(wireless, false),
422 Some(HidppEventSource::WirelessDeviceStatus)
423 );
424
425 let mut payload = [0; 16];
426 payload[0] = 80;
427 payload[1] = 4;
428 let battery = v20::Message::Long(
429 v20::MessageHeader {
430 device_index: 3,
431 feature_index: 7,
432 function_id: U4::from_lo(0),
433 software_id: U4::from_lo(0),
434 },
435 payload,
436 )
437 .into();
438 assert_eq!(
439 state.decode(battery, false),
440 Some(HidppEventSource::UnifiedBattery)
441 );
442 }
443
444 #[test]
445 fn adc_measurement_index_is_its_table_position() {
446 let table = [
448 0x0001, 0x0003, 0x0005, 0x8070, 0x8010, 0x8310, 0x8300, 0x1f20,
449 ];
450 assert_eq!(
451 EventFeatureIndices::from_feature_ids(&table).adc_measurement,
452 Some(8)
453 );
454 }
455
456 #[test]
457 fn adc_measurement_broadcasts_request_reconciliation() {
458 let state = state(None);
459 state.devices.write().unwrap().push(DeviceEvents {
460 device_index: 0xff,
461 features: EventFeatureIndices {
462 wireless_status: None,
463 unified_battery: None,
464 adc_measurement: Some(8),
465 },
466 });
467 let event = |function_id: u8, software_id: u8, bytes: [u8; 3]| {
468 let mut payload = [0; 16];
469 payload[..3].copy_from_slice(&bytes);
470 v20::Message::Long(
471 v20::MessageHeader {
472 device_index: 0xff,
473 feature_index: 8,
474 function_id: U4::from_lo(function_id),
475 software_id: U4::from_lo(software_id),
476 },
477 payload,
478 )
479 .into()
480 };
481
482 for bytes in [[0x00, 0x00, 0x00], [0x10, 0x4a, 0x03]] {
486 assert_eq!(
487 state.decode(event(0, 0, bytes), false),
488 Some(HidppEventSource::AdcMeasurement),
489 "{bytes:02x?}"
490 );
491 }
492 assert_eq!(
495 state.decode(event(0, 0, [0x10, 0x4a, 0x0f]), false),
496 Some(HidppEventSource::AdcMeasurement)
497 );
498 assert_eq!(state.decode(event(0, 0xd, [0x10, 0x4a, 0x01]), false), None);
500 assert_eq!(state.decode(event(1, 0, [0x10, 0x4a, 0x01]), false), None);
502 }
503
504 #[test]
505 fn event_requests_are_bounded_and_coalesced() {
506 let (notifier, mut receiver) = event_channel();
507 notifier.notify(HidppEventSource::ReceiverConnection);
508 notifier.notify(HidppEventSource::UnifiedBattery);
509
510 assert_eq!(
511 receiver.try_recv(),
512 Ok(HidppEventSource::ReceiverConnection)
513 );
514 assert_eq!(receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty));
515 }
516}