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 root: Arc<RootFeature>,
31
32 features: HashMap<TypeId, Arc<dyn Feature>>,
34
35 pub device_index: u8,
37
38 pub protocol_version: ProtocolVersion,
40}
41
42impl Device {
43 pub async fn new(chan: Arc<HidppChannel>, device_index: u8) -> Result<Self, DeviceError> {
54 let Some(version) = protocol::determine_version(&chan, device_index).await? else {
55 return Err(DeviceError::DeviceNotFound);
56 };
57
58 if version == ProtocolVersion::V10 {
59 return Err(DeviceError::UnsupportedProtocolVersion);
60 }
61
62 let mut features: HashMap<TypeId, Arc<dyn Feature>> = HashMap::new();
65 let root = insert_feature(
66 &mut features,
67 RootFeature::new(Arc::clone(&chan), device_index, 0),
68 );
69
70 Ok(Self {
71 chan,
72 root,
73 features,
74 device_index,
75 protocol_version: version,
76 })
77 }
78
79 #[must_use]
82 pub fn root(&self) -> Arc<RootFeature> {
83 Arc::clone(&self.root)
84 }
85
86 pub fn add_feature_instance<F: Feature>(&mut self, feature: F) -> Arc<F> {
91 insert_feature(&mut self.features, feature)
92 }
93
94 pub fn add_feature<F: CreatableFeature>(&mut self, feature_index: u8) -> Arc<F> {
103 self.add_feature_instance(F::new(
104 Arc::clone(&self.chan),
105 self.device_index,
106 feature_index,
107 ))
108 }
109
110 #[must_use]
113 pub fn provides_feature<F: Feature>(&self) -> bool {
114 self.features.contains_key(&TypeId::of::<F>())
115 }
116
117 #[must_use]
122 pub fn get_feature<F: Feature>(&self) -> Option<Arc<F>> {
123 self.features
124 .get(&TypeId::of::<F>())
125 .cloned()
126 .and_then(|feat| Arc::downcast::<F>(feat).ok())
127 }
128
129 pub async fn enumerate_features(
137 &mut self,
138 ) -> Result<Option<Vec<FeatureInformation>>, Hidpp20Error> {
139 let Some(feature_set_info) = self.root().get_feature(FeatureSetFeature::ID).await? else {
140 return Ok(None);
141 };
142
143 let feature_set_feature = self.add_feature::<FeatureSetFeature>(feature_set_info.index);
144
145 let count = feature_set_feature.count().await?;
146 trace!(
147 index = self.device_index,
148 count, "enumerating feature table"
149 );
150 let mut features = Vec::with_capacity(count as usize);
151 for i in 1..=count {
152 let info = read_feature_entry(&feature_set_feature, self.device_index, i).await?;
153 trace!(
154 index = self.device_index,
155 slot = i,
156 id = format_args!("{:#06x}", info.id),
157 version = info.version,
158 "feature",
159 );
160 features.push(info);
161
162 if i == feature_set_info.index {
163 continue;
164 }
165
166 let Some(impls) = feature::registry::lookup_version(info.id, info.version) else {
167 continue;
168 };
169
170 for feat_impl in impls {
171 let (type_id, instance) =
172 (feat_impl.producer)(Arc::clone(&self.chan), self.device_index, i);
173
174 self.features.insert(type_id, instance);
175 }
176 }
177
178 Ok(Some(features))
179 }
180}
181
182fn insert_feature<F: Feature>(
189 features: &mut HashMap<TypeId, Arc<dyn Feature>>,
190 feature: F,
191) -> Arc<F> {
192 let feat_rc = Arc::new(feature);
193 features.insert(TypeId::of::<F>(), Arc::clone(&feat_rc) as Arc<dyn Feature>);
194 feat_rc
195}
196
197const FEATURE_READ_ATTEMPT: Duration = Duration::from_millis(700);
205
206const FEATURE_READ_ATTEMPTS: u8 = 4;
212
213const FEATURE_READ_BACKOFF: Duration = Duration::from_millis(120);
215
216async fn read_feature_entry(
223 feature_set: &FeatureSetFeature,
224 device_index: u8,
225 index: u8,
226) -> Result<FeatureInformation, Hidpp20Error> {
227 let mut last_error = None;
228 for attempt in 1..=FEATURE_READ_ATTEMPTS {
229 let mut read = std::pin::pin!(feature_set.get_feature(index).fuse());
230 let outcome = select! {
231 result = read => Some(result),
232 () = futures_timer::Delay::new(FEATURE_READ_ATTEMPT).fuse() => None,
233 };
234 match outcome {
235 Some(Ok(info)) => return Ok(info),
236 Some(Err(e @ (Hidpp20Error::Feature(_) | Hidpp20Error::UnsupportedResponse))) => {
237 return Err(e);
238 }
239 Some(Err(e)) => last_error = Some(e),
240 None => trace!(
241 index = device_index,
242 slot = index,
243 attempt,
244 "feature-table read timed out — re-asking"
245 ),
246 }
247 if attempt < FEATURE_READ_ATTEMPTS {
248 futures_timer::Delay::new(FEATURE_READ_BACKOFF).await;
249 }
250 }
251 Err(last_error.unwrap_or(Hidpp20Error::Channel(ChannelError::Timeout)))
252}
253
254#[cfg(test)]
255#[allow(
256 clippy::unwrap_used,
257 clippy::expect_used,
258 reason = "expect/unwrap are idiomatic in tests"
259)]
260mod tests {
261 use std::sync::Arc;
262
263 use crate::{
264 channel::{HidppChannel, tests::MockRawHidChannel},
265 feature::{CreatableFeature as _, feature_set::FeatureSetFeature},
266 protocol::v20::Hidpp20Error,
267 };
268
269 use super::{FEATURE_READ_ATTEMPTS, read_feature_entry};
270
271 #[test]
275 fn lost_feature_entry_is_retried_before_giving_up() {
276 futures::executor::block_on(async {
277 let (raw, handle) = MockRawHidChannel::new();
278 let channel = Arc::new(HidppChannel::from_raw_channel(raw).await.unwrap());
279 let feature_set = FeatureSetFeature::new(Arc::clone(&channel), 0xff, 0x01);
281
282 let err = read_feature_entry(&feature_set, 0xff, 1).await.unwrap_err();
283
284 assert!(
285 matches!(err, Hidpp20Error::Channel(_)),
286 "an unanswered entry surfaces as a transport failure, got {err:?}"
287 );
288 assert_eq!(
289 handle.written_reports().len(),
290 usize::from(FEATURE_READ_ATTEMPTS),
291 "every attempt should reach the wire"
292 );
293 });
294 }
295}
296
297#[derive(Debug, Error)]
299#[non_exhaustive]
300pub enum DeviceError {
301 #[error("the HID++ channel returned an error")]
303 Channel(#[from] ChannelError),
304
305 #[error("there is no device with the specified device index")]
307 DeviceNotFound,
308
309 #[error("the device does not support HID++2.0 or newer")]
311 UnsupportedProtocolVersion,
312}