Skip to content

Commit eca787d

Browse files
committed
fix: Check legacy output vector feature bounds
Fixes INTIFACE-CENTRAL-4M Fixes INTIFACE-CENTRAL-11D
1 parent 3ad7cf4 commit eca787d

1 file changed

Lines changed: 84 additions & 28 deletions

File tree

crates/buttplug_server/src/message/v4/checked_output_vec_cmd.rs

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ use getset::{CopyGetters, Getters};
3232

3333
use super::checked_output_cmd::CheckedOutputCmdV4;
3434

35+
fn feature_index_for_id(
36+
attrs: &ServerDeviceAttributes,
37+
feature_id: uuid::Uuid,
38+
command_name: &str,
39+
) -> Result<u32, ButtplugError> {
40+
attrs
41+
.features()
42+
.iter()
43+
.find(|(_, f)| f.id() == feature_id)
44+
.map(|(idx, _)| *idx)
45+
.ok_or_else(|| {
46+
ButtplugDeviceError::DeviceConfigurationError(format!(
47+
"Feature {feature_id} referenced by {command_name} was not found in device attributes."
48+
))
49+
.into()
50+
})
51+
}
52+
3553
#[derive(Debug, Default, PartialEq, Clone, Getters, CopyGetters)]
3654
pub struct CheckedOutputVecCmdV4 {
3755
#[getset(get_copy = "pub")]
@@ -155,21 +173,16 @@ impl TryFromDeviceAttributes<VibrateCmdV1> for CheckedOutputVecCmdV4 {
155173

156174
let mut cmds: Vec<CheckedOutputCmdV4> = vec![];
157175
for vibrate_cmd in msg.speeds() {
158-
if vibrate_cmd.index() > vibrate_attributes.features().len() as u32 {
159-
return Err(ButtplugError::from(
160-
ButtplugDeviceError::DeviceFeatureCountMismatch(
176+
let feature = vibrate_attributes
177+
.features()
178+
.get(vibrate_cmd.index() as usize)
179+
.ok_or(ButtplugError::from(
180+
ButtplugDeviceError::DeviceFeatureIndexError(
181+
vibrate_attributes.features().len() as u32,
161182
vibrate_cmd.index(),
162-
msg.speeds().len() as u32,
163183
),
164-
));
165-
}
166-
let feature = &vibrate_attributes.features()[vibrate_cmd.index() as usize];
167-
let idx = features
168-
.features()
169-
.iter()
170-
.find(|(_, f)| f.id() == feature.id())
171-
.expect("Already checked existence")
172-
.0;
184+
))?;
185+
let idx = feature_index_for_id(features, feature.id(), "VibrateCmdV1")?;
173186
let actuator = feature.get_output(OutputType::Vibrate).ok_or(
174187
ButtplugDeviceError::DeviceConfigurationError(
175188
"Device configuration does not have Vibrate actuator available.".to_owned(),
@@ -178,7 +191,7 @@ impl TryFromDeviceAttributes<VibrateCmdV1> for CheckedOutputVecCmdV4 {
178191
cmds.push(CheckedOutputCmdV4::new(
179192
msg.id(),
180193
msg.device_index(),
181-
*idx,
194+
idx,
182195
feature.id(),
183196
OutputCommand::Vibrate(OutputValue::new(
184197
actuator
@@ -220,12 +233,7 @@ impl TryFromDeviceAttributes<ScalarCmdV3> for CheckedOutputVecCmdV4 {
220233
.ok_or(ButtplugError::from(
221234
ButtplugDeviceError::DeviceFeatureIndexError(scalar_attrs.len() as u32, cmd.index()),
222235
))?;
223-
let idx = attrs
224-
.features()
225-
.iter()
226-
.find(|(_, f)| f.id() == feature.feature().id())
227-
.expect("Already proved existence")
228-
.0;
236+
let idx = feature_index_for_id(attrs, feature.feature().id(), "ScalarCmdV3")?;
229237
let output = feature
230238
.feature()
231239
.get_output(cmd.actuator_type())
@@ -241,7 +249,7 @@ impl TryFromDeviceAttributes<ScalarCmdV3> for CheckedOutputVecCmdV4 {
241249
cmds.push(CheckedOutputCmdV4::new(
242250
msg.id(),
243251
msg.device_index(),
244-
*idx,
252+
idx,
245253
feature.feature.id(),
246254
OutputCommand::from_output_type(cmd.actuator_type(), output_value).unwrap(),
247255
));
@@ -338,12 +346,7 @@ impl TryFromDeviceAttributes<RotateCmdV1> for CheckedOutputVecCmdV4 {
338346
.ok_or(ButtplugError::from(
339347
ButtplugDeviceError::DeviceFeatureIndexError(rotate_attrs.len() as u32, cmd.index()),
340348
))?;
341-
let idx = attrs
342-
.features()
343-
.iter()
344-
.find(|(_, f)| f.id() == feature.feature().id())
345-
.expect("Already proved existence")
346-
.0;
349+
let idx = feature_index_for_id(attrs, feature.feature().id(), "RotateCmdV1")?;
347350
let actuator =
348351
feature
349352
.feature()
@@ -354,7 +357,7 @@ impl TryFromDeviceAttributes<RotateCmdV1> for CheckedOutputVecCmdV4 {
354357
cmds.push(CheckedOutputCmdV4::new(
355358
msg.id(),
356359
msg.device_index(),
357-
*idx,
360+
idx,
358361
feature.feature.id(),
359362
OutputCommand::Rotate(OutputValue::new(
360363
actuator.calculate_from_float(cmd.speed()).map_err(|_| {
@@ -373,3 +376,56 @@ impl TryFromDeviceAttributes<RotateCmdV1> for CheckedOutputVecCmdV4 {
373376
))
374377
}
375378
}
379+
380+
#[cfg(test)]
381+
mod tests {
382+
use super::*;
383+
use crate::message::v1::VibrateSubcommandV1;
384+
use buttplug_core::util::{
385+
range::RangeInclusive,
386+
small_vec_enum_map::SmallVecEnumMap,
387+
};
388+
use buttplug_server_device_config::{
389+
RangeWithLimit,
390+
ServerDeviceFeature,
391+
ServerDeviceFeatureOutputValueProperties,
392+
};
393+
use std::collections::BTreeMap;
394+
use uuid::Uuid;
395+
396+
fn attrs_with_one_vibrate_feature() -> ServerDeviceAttributes {
397+
let output = vec![ServerDeviceFeatureOutput::Vibrate(
398+
ServerDeviceFeatureOutputValueProperties::new(
399+
RangeWithLimit::new(RangeInclusive::new(0, 100)),
400+
false,
401+
),
402+
)]
403+
.into();
404+
let input = SmallVecEnumMap::default();
405+
let feature = ServerDeviceFeature::new(
406+
0,
407+
"Vibrate".to_owned(),
408+
Uuid::new_v4(),
409+
None,
410+
None,
411+
output,
412+
input,
413+
);
414+
let mut features = BTreeMap::new();
415+
features.insert(0, feature);
416+
ServerDeviceAttributes::new(&features)
417+
}
418+
419+
#[test]
420+
fn legacy_vibrate_index_equal_to_feature_count_returns_error() {
421+
let attrs = attrs_with_one_vibrate_feature();
422+
let msg = VibrateCmdV1::new(0, vec![VibrateSubcommandV1::new(1, 0.5)]);
423+
424+
let result = CheckedOutputVecCmdV4::try_from_device_attributes(msg, &attrs);
425+
426+
assert_eq!(
427+
result.unwrap_err(),
428+
ButtplugError::from(ButtplugDeviceError::DeviceFeatureIndexError(1, 1))
429+
);
430+
}
431+
}

0 commit comments

Comments
 (0)