1use std::{any::TypeId, collections::HashMap, sync::Arc, time::Duration};
4
5use futures::{FutureExt, select};
6use thiserror::Error;
7use tracing::trace;
8
9use crate::{
10 channel::{ChannelError, HidppChannel},
11 feature::{
12 self, CreatableFeature, Feature,
13 feature_set::{FeatureInformation, FeatureSetFeature},
14 root::RootFeature,
15 },
16 protocol::{self, ProtocolVersion, v20::Hidpp20Error},
17};
18
19#[derive(Clone)]
23pub struct Device {
24 chan: Arc<HidppChannel>,
26
27 features: HashMap<TypeId, Arc<dyn Feature>>,
29
30 pub device_index: u8,
32
33 pub protocol_version: ProtocolVersion,
35}
36
37impl Device {
38 pub async fn new(chan: Arc<HidppChannel>, device_index: u8) -> Result<Self, DeviceError> {
49 let protocol_version = protocol::determine_version(&chan, device_index).await?;
50
51 if protocol_version.is_none() {
52 return Err(DeviceError::DeviceNotFound);
53 }
54 let version = protocol_version.unwrap();
55
56 if version == ProtocolVersion::V10 {
57 return Err(DeviceError::UnsupportedProtocolVersion);
58 }
59
60 let mut device = Self {
61 chan,
62 features: HashMap::new(),
63 device_index,
64 protocol_version: version,
65 };
66
67 device.add_feature::<RootFeature>(0);
70
71 Ok(device)
72 }
73
74 pub fn root(&self) -> Arc<RootFeature> {
77 self.get_feature::<RootFeature>().unwrap()
78 }
79
80 pub fn add_feature_instance<F: Feature>(&mut self, feature: F) -> Arc<F> {
85 let feat_rc: Arc<dyn Feature> = Arc::new(feature);
86
87 self.features
88 .insert(TypeId::of::<F>(), Arc::clone(&feat_rc));
89
90 Arc::downcast::<F>(feat_rc).unwrap()
91 }
92
93 pub fn add_feature<F: CreatableFeature>(&mut self, feature_index: u8) -> Arc<F> {
102 self.add_feature_instance(F::new(
103 Arc::clone(&self.chan),
104 self.device_index,
105 feature_index,
106 ))
107 }
108
109 pub fn provides_feature<F: Feature>(&self) -> bool {
112 self.features.contains_key(&TypeId::of::<F>())
113 }
114
115 pub fn get_feature<F: Feature>(&self) -> Option<Arc<F>> {
120 self.features
121 .get(&TypeId::of::<F>())
122 .cloned()
123 .and_then(|feat| Arc::downcast::<F>(feat).ok())
124 }
125
126 pub async fn enumerate_features(
134 &mut self,
135 ) -> Result<Option<Vec<FeatureInformation>>, Hidpp20Error> {
136 let Some(feature_set_info) = self.root().get_feature(FeatureSetFeature::ID).await? else {
137 return Ok(None);
138 };
139
140 let feature_set_feature = self.add_feature::<FeatureSetFeature>(feature_set_info.index);
141
142 let count = feature_set_feature.count().await?;
143 trace!(
144 index = self.device_index,
145 count, "enumerating feature table"
146 );
147 let mut features = Vec::with_capacity(count as usize);
148 for i in 1..=count {
149 let info = read_feature_entry(&feature_set_feature, self.device_index, i).await?;
150 trace!(
151 index = self.device_index,
152 slot = i,
153 id = format_args!("{:#06x}", info.id),
154 version = info.version,
155 "feature",
156 );
157 features.push(info);
158
159 if i == feature_set_info.index {
160 continue;
161 }
162
163 let Some(impls) = feature::registry::lookup_version(info.id, info.version) else {
164 continue;
165 };
166
167 for feat_impl in impls {
168 let (type_id, instance) =
169 (feat_impl.producer)(Arc::clone(&self.chan), self.device_index, i);
170
171 self.features.insert(type_id, instance);
172 }
173 }
174
175 Ok(Some(features))
176 }
177}
178
179const FEATURE_READ_ATTEMPT: Duration = Duration::from_millis(700);
187
188const FEATURE_READ_ATTEMPTS: u8 = 4;
194
195const FEATURE_READ_BACKOFF: Duration = Duration::from_millis(120);
197
198async fn read_feature_entry(
205 feature_set: &FeatureSetFeature,
206 device_index: u8,
207 index: u8,
208) -> Result<FeatureInformation, Hidpp20Error> {
209 let mut last_error = None;
210 for attempt in 1..=FEATURE_READ_ATTEMPTS {
211 let mut read = std::pin::pin!(feature_set.get_feature(index).fuse());
212 let outcome = select! {
213 result = read => Some(result),
214 _ = futures_timer::Delay::new(FEATURE_READ_ATTEMPT).fuse() => None,
215 };
216 match outcome {
217 Some(Ok(info)) => return Ok(info),
218 Some(Err(e @ (Hidpp20Error::Feature(_) | Hidpp20Error::UnsupportedResponse))) => {
219 return Err(e);
220 }
221 Some(Err(e)) => last_error = Some(e),
222 None => trace!(
223 index = device_index,
224 slot = index,
225 attempt,
226 "feature-table read timed out — re-asking"
227 ),
228 }
229 if attempt < FEATURE_READ_ATTEMPTS {
230 futures_timer::Delay::new(FEATURE_READ_BACKOFF).await;
231 }
232 }
233 Err(last_error.unwrap_or(Hidpp20Error::Channel(ChannelError::Timeout)))
234}
235
236#[cfg(test)]
237mod tests {
238 use std::sync::Arc;
239
240 use crate::{
241 channel::{HidppChannel, tests::MockRawHidChannel},
242 feature::{CreatableFeature as _, feature_set::FeatureSetFeature},
243 protocol::v20::Hidpp20Error,
244 };
245
246 use super::{FEATURE_READ_ATTEMPTS, read_feature_entry};
247
248 #[test]
252 fn lost_feature_entry_is_retried_before_giving_up() {
253 futures::executor::block_on(async {
254 let (raw, handle) = MockRawHidChannel::new();
255 let channel = Arc::new(HidppChannel::from_raw_channel(raw).await.unwrap());
256 let feature_set = FeatureSetFeature::new(Arc::clone(&channel), 0xff, 0x01);
258
259 let err = read_feature_entry(&feature_set, 0xff, 1).await.unwrap_err();
260
261 assert!(
262 matches!(err, Hidpp20Error::Channel(_)),
263 "an unanswered entry surfaces as a transport failure, got {err:?}"
264 );
265 assert_eq!(
266 handle.written_reports().len(),
267 usize::from(FEATURE_READ_ATTEMPTS),
268 "every attempt should reach the wire"
269 );
270 });
271 }
272}
273
274#[derive(Debug, Error)]
276#[non_exhaustive]
277pub enum DeviceError {
278 #[error("the HID++ channel returned an error")]
280 Channel(#[from] ChannelError),
281
282 #[error("there is no device with the specified device index")]
284 DeviceNotFound,
285
286 #[error("the device does not support HID++2.0 or newer")]
288 UnsupportedProtocolVersion,
289}