use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, PoisonError, RwLock};
use hidpp::channel::{HidppChannel, HidppMessage, MessageListenerGuard};
use hidpp::feature::unified_battery::BatteryEvent;
use hidpp::feature::wireless_device_status::WirelessDeviceStatusEvent;
use hidpp::protocol::{v10, v20};
use hidpp::receiver::{bolt, unifying};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use tokio::sync::Notify;
use tokio::sync::mpsc;
use crate::ReceiverProtocol;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HidppEventSource {
ReceiverConnection,
WirelessDeviceStatus,
UnifiedBattery,
}
#[derive(Clone)]
pub struct EventNotifier {
sender: mpsc::Sender<HidppEventSource>,
#[cfg(test)]
observation: Option<Arc<EventObservation>>,
}
impl EventNotifier {
fn notify(&self, source: HidppEventSource) {
let _ = self.sender.try_send(source);
#[cfg(test)]
if let Some(observation) = &self.observation {
observation.count.fetch_add(1, Ordering::Release);
observation.changed.notify_waiters();
}
}
}
pub type EventReceiver = mpsc::Receiver<HidppEventSource>;
#[must_use]
pub fn event_channel() -> (EventNotifier, EventReceiver) {
let (sender, receiver) = mpsc::channel(1);
(
EventNotifier {
sender,
#[cfg(test)]
observation: None,
},
receiver,
)
}
#[cfg(test)]
struct EventObservation {
count: AtomicUsize,
changed: Notify,
}
#[cfg(test)]
pub(crate) struct EventObserver {
observation: Arc<EventObservation>,
}
#[cfg(test)]
impl EventObserver {
pub(crate) async fn wait_for(&self, expected: usize) {
loop {
let changed = self.observation.changed.notified();
if self.observation.count.load(Ordering::Acquire) >= expected {
return;
}
changed.await;
}
}
}
#[cfg(test)]
pub(crate) fn observed_event_channel() -> (EventNotifier, EventReceiver, EventObserver) {
let (sender, receiver) = mpsc::channel(1);
let observation = Arc::new(EventObservation {
count: AtomicUsize::new(0),
changed: Notify::new(),
});
(
EventNotifier {
sender,
observation: Some(Arc::clone(&observation)),
},
receiver,
EventObserver { observation },
)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct EventFeatureIndices {
pub(super) wireless_status: Option<u8>,
pub(super) unified_battery: Option<u8>,
}
impl EventFeatureIndices {
pub(super) fn from_feature_ids(ids: &[u16]) -> Self {
let mut indices = Self::default();
for (position, id) in ids.iter().copied().enumerate() {
let Ok(index) = u8::try_from(position + 1) else {
break;
};
match id {
0x1d4b => indices.wireless_status = Some(index),
0x1004 => indices.unified_battery = Some(index),
_ => {}
}
}
indices
}
fn recognizes(self, message: &v20::Message) -> Option<HidppEventSource> {
let header = message.header();
if header.software_id.to_lo() != 0 {
return None;
}
let function_id = header.function_id.to_lo();
let payload = message.extend_payload();
if self.wireless_status == Some(header.feature_index)
&& WirelessDeviceStatusEvent::decode(function_id, &payload).is_some()
{
return Some(HidppEventSource::WirelessDeviceStatus);
}
if self.unified_battery == Some(header.feature_index)
&& BatteryEvent::decode(function_id, &payload).is_some()
{
return Some(HidppEventSource::UnifiedBattery);
}
None
}
}
#[derive(Clone, Copy)]
struct DeviceEvents {
device_index: u8,
features: EventFeatureIndices,
}
struct SubscriptionState {
protocol: Option<ReceiverProtocol>,
devices: RwLock<Vec<DeviceEvents>>,
receiver_snapshot_depth: AtomicUsize,
notifier: EventNotifier,
}
impl SubscriptionState {
fn decode(&self, raw: HidppMessage, matched: bool) -> Option<HidppEventSource> {
if matched {
return None;
}
let receiver_connection = match self.protocol {
Some(ReceiverProtocol::Bolt) => matches!(
bolt::decode_notification(&v10::Message::from(raw)),
Some(bolt::Event::DeviceConnection(_))
),
Some(ReceiverProtocol::Unifying) => matches!(
unifying::decode_notification(&v10::Message::from(raw)),
Some(unifying::Event::DeviceConnection(_))
),
None => false,
};
if receiver_connection {
return (self.receiver_snapshot_depth.load(Ordering::Acquire) == 0)
.then_some(HidppEventSource::ReceiverConnection);
}
let message = v20::Message::from(raw);
let device_index = message.header().device_index;
self.devices
.read()
.unwrap_or_else(PoisonError::into_inner)
.iter()
.find(|device| device.device_index == device_index)
.and_then(|device| device.features.recognizes(&message))
}
}
#[derive(Clone)]
pub(super) struct EventSubscriptionHandle {
state: Arc<SubscriptionState>,
}
impl EventSubscriptionHandle {
pub(super) fn register_device(&self, device_index: u8, features: EventFeatureIndices) {
let mut devices = self
.state
.devices
.write()
.unwrap_or_else(PoisonError::into_inner);
if let Some(device) = devices
.iter_mut()
.find(|device| device.device_index == device_index)
{
device.features = features;
} else {
devices.push(DeviceEvents {
device_index,
features,
});
}
}
pub(super) fn begin_receiver_snapshot(&self) -> ReceiverSnapshotGuard {
self.state
.receiver_snapshot_depth
.fetch_add(1, Ordering::AcqRel);
ReceiverSnapshotGuard {
state: Arc::clone(&self.state),
}
}
}
pub(super) struct ChannelEventSubscriptions {
handle: EventSubscriptionHandle,
_listener: MessageListenerGuard,
}
impl ChannelEventSubscriptions {
pub(super) fn attach(
channel: &Arc<HidppChannel>,
protocol: Option<ReceiverProtocol>,
notifier: EventNotifier,
) -> Self {
let state = Arc::new(SubscriptionState {
protocol,
devices: RwLock::new(Vec::new()),
receiver_snapshot_depth: AtomicUsize::new(0),
notifier,
});
let listener = channel.add_msg_listener_guarded({
let state = Arc::clone(&state);
move |raw, matched| {
if let Some(source) = state.decode(raw, matched) {
state.notifier.notify(source);
}
}
});
Self {
handle: EventSubscriptionHandle { state },
_listener: listener,
}
}
pub(super) fn handle(&self) -> EventSubscriptionHandle {
self.handle.clone()
}
}
pub(super) struct ReceiverSnapshotGuard {
state: Arc<SubscriptionState>,
}
impl Drop for ReceiverSnapshotGuard {
fn drop(&mut self) {
self.state
.receiver_snapshot_depth
.fetch_sub(1, Ordering::AcqRel);
}
}
#[cfg(test)]
mod tests {
use hidpp::nibble::U4;
use hidpp::protocol::{v10, v20};
use super::*;
fn state(protocol: Option<ReceiverProtocol>) -> Arc<SubscriptionState> {
let (notifier, _receiver) = event_channel();
Arc::new(SubscriptionState {
protocol,
devices: RwLock::new(Vec::new()),
receiver_snapshot_depth: AtomicUsize::new(0),
notifier,
})
}
#[test]
fn feature_indices_are_runtime_table_positions() {
assert_eq!(
EventFeatureIndices::from_feature_ids(&[0x0001, 0x1d4b, 0x1004]),
EventFeatureIndices {
wireless_status: Some(2),
unified_battery: Some(3),
}
);
}
#[test]
fn receiver_snapshot_suppresses_only_triggered_connection_wakeups() {
let state = state(Some(ReceiverProtocol::Unifying));
let raw = v10::Message::Short(
v10::MessageHeader {
device_index: 2,
sub_id: 0x41,
},
[0, 0x02, 0x34, 0x12],
)
.into();
assert_eq!(
state.decode(raw, false),
Some(HidppEventSource::ReceiverConnection)
);
state.receiver_snapshot_depth.store(1, Ordering::Release);
assert_eq!(state.decode(raw, false), None);
}
#[test]
fn receiver_snapshot_does_not_suppress_device_feature_events() {
let state = state(Some(ReceiverProtocol::Bolt));
state.devices.write().unwrap().push(DeviceEvents {
device_index: 2,
features: EventFeatureIndices {
wireless_status: Some(5),
unified_battery: None,
},
});
state.receiver_snapshot_depth.store(1, Ordering::Release);
let wireless = v20::Message::Long(
v20::MessageHeader {
device_index: 2,
feature_index: 5,
function_id: U4::from_lo(0),
software_id: U4::from_lo(0),
},
[0; 16],
)
.into();
assert_eq!(
state.decode(wireless, false),
Some(HidppEventSource::WirelessDeviceStatus)
);
}
#[test]
fn registered_device_events_decode_to_typed_sources() {
let state = state(None);
state.devices.write().unwrap().push(DeviceEvents {
device_index: 3,
features: EventFeatureIndices {
wireless_status: Some(5),
unified_battery: Some(7),
},
});
let wireless = v20::Message::Long(
v20::MessageHeader {
device_index: 3,
feature_index: 5,
function_id: U4::from_lo(0),
software_id: U4::from_lo(0),
},
[0; 16],
)
.into();
assert_eq!(
state.decode(wireless, false),
Some(HidppEventSource::WirelessDeviceStatus)
);
let mut payload = [0; 16];
payload[0] = 80;
payload[1] = 4;
let battery = v20::Message::Long(
v20::MessageHeader {
device_index: 3,
feature_index: 7,
function_id: U4::from_lo(0),
software_id: U4::from_lo(0),
},
payload,
)
.into();
assert_eq!(
state.decode(battery, false),
Some(HidppEventSource::UnifiedBattery)
);
}
#[test]
fn event_requests_are_bounded_and_coalesced() {
let (notifier, mut receiver) = event_channel();
notifier.notify(HidppEventSource::ReceiverConnection);
notifier.notify(HidppEventSource::UnifiedBattery);
assert_eq!(
receiver.try_recv(),
Ok(HidppEventSource::ReceiverConnection)
);
assert_eq!(receiver.try_recv(), Err(mpsc::error::TryRecvError::Empty));
}
}