Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use crate::message::{
9 ButtplugDeviceMessageNameV3,
10 LinearCmdV1,
11 RotateCmdV1,
12 ServerDeviceAttributes,
13 TryFromDeviceAttributes,
14 v0::SingleMotorVibrateCmdV0,
15 v1::VibrateCmdV1,
16 v3::ScalarCmdV3,
17};
18use buttplug_core::{
19 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError},
20 message::{
21 ButtplugDeviceMessage,
22 ButtplugMessage,
23 ButtplugMessageFinalizer,
24 ButtplugMessageValidator,
25 OutputCommand,
26 OutputPositionWithDuration,
27 OutputType,
28 OutputValue,
29 },
30};
31use getset::{CopyGetters, Getters};
32
33use super::checked_output_cmd::CheckedOutputCmdV4;
34
35#[derive(
36 Debug,
37 Default,
38 ButtplugDeviceMessage,
39 ButtplugMessageFinalizer,
40 PartialEq,
41 Clone,
42 Getters,
43 CopyGetters,
44)]
45pub struct CheckedOutputVecCmdV4 {
46 #[getset(get_copy = "pub")]
47 id: u32,
48 #[getset(get_copy = "pub")]
49 device_index: u32,
50 #[getset(get = "pub")]
51 value_vec: Vec<CheckedOutputCmdV4>,
52}
53
54impl CheckedOutputVecCmdV4 {
55 pub fn new(id: u32, device_index: u32, mut value_vec: Vec<CheckedOutputCmdV4>) -> Self {
56 // Several tests and parts of the system assumed we always sorted by feature index. This is not
57 // necessarily true of incoming messages, but we also never explicitly specified the execution
58 // order of subcommands within a message, so we'll just sort here for now to make tests pass,
59 // and implement unordered checking after v4 ships.
60 value_vec.sort_by_key(|k| k.feature_index());
61 Self {
62 id,
63 device_index,
64 value_vec,
65 }
66 }
67}
68
69impl ButtplugMessageValidator for CheckedOutputVecCmdV4 {
70 fn is_valid(&self) -> Result<(), ButtplugMessageError> {
71 self.is_not_system_id(self.id)?;
72 Ok(())
73 }
74}
75
76impl TryFromDeviceAttributes<SingleMotorVibrateCmdV0> for CheckedOutputVecCmdV4 {
77 // For VibrateCmd, just take everything out of V2's VibrateCmd and make a command.
78 fn try_from_device_attributes(
79 msg: SingleMotorVibrateCmdV0,
80 features: &ServerDeviceAttributes,
81 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
82 let mut vibrate_features = features
83 .features()
84 .iter()
85 .enumerate()
86 .filter(|(_, feature)| {
87 feature
88 .output()
89 .as_ref()
90 .is_some_and(|x| x.contains(OutputType::Vibrate))
91 })
92 .peekable();
93
94 // Check to make sure we have any vibrate attributes at all.
95 if vibrate_features.peek().is_none() {
96 return Err(
97 ButtplugDeviceError::DeviceFeatureMismatch("Device has no Vibrate features".to_owned())
98 .into(),
99 );
100 }
101
102 let mut cmds = vec![];
103 for (index, feature) in vibrate_features {
104 // if we've made it this far, we know we have actuators in a list
105 let actuator = feature
106 .output()
107 .as_ref()
108 .unwrap()
109 .vibrate()
110 .as_ref()
111 .expect("Already confirmed we have vibrator for this feature");
112 // This doesn't need to run through a security check because we have to construct it to be
113 // inherently secure anyways.
114 cmds.push(CheckedOutputCmdV4::new(
115 msg.id(),
116 msg.device_index(),
117 index as u32,
118 feature.id(),
119 OutputCommand::Vibrate(OutputValue::new(
120 actuator.calculate_scaled_float(msg.speed()).map_err(
121 |e: buttplug_server_device_config::ButtplugDeviceConfigError| {
122 ButtplugMessageError::InvalidMessageContents(e.to_string())
123 },
124 )?,
125 )),
126 ))
127 }
128 Ok(CheckedOutputVecCmdV4::new(
129 msg.id(),
130 msg.device_index(),
131 cmds,
132 ))
133 }
134}
135
136impl TryFromDeviceAttributes<VibrateCmdV1> for CheckedOutputVecCmdV4 {
137 // VibrateCmd only exists up through Message Spec v2. We can assume that, if we're receiving it,
138 // we can just use the V2 spec client device attributes for it. If this was sent on a V1 protocol,
139 // it'll still have all the same features.
140 //
141 // Due to specs v1/2 using feature counts instead of per-feature objects, we calculate our indexes
142 // based on the feature counts in our current device definitions, as that's how we generate them
143 // on the way out.
144 fn try_from_device_attributes(
145 msg: VibrateCmdV1,
146 features: &ServerDeviceAttributes,
147 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
148 let vibrate_attributes =
149 features
150 .attrs_v2()
151 .vibrate_cmd()
152 .as_ref()
153 .ok_or(ButtplugError::from(
154 ButtplugDeviceError::DeviceFeatureCountMismatch(0, msg.speeds().len() as u32),
155 ))?;
156
157 let mut cmds: Vec<CheckedOutputCmdV4> = vec![];
158 for vibrate_cmd in msg.speeds() {
159 if vibrate_cmd.index() > vibrate_attributes.features().len() as u32 {
160 return Err(ButtplugError::from(
161 ButtplugDeviceError::DeviceFeatureCountMismatch(
162 vibrate_cmd.index(),
163 msg.speeds().len() as u32,
164 ),
165 ));
166 }
167 let feature = &vibrate_attributes.features()[vibrate_cmd.index() as usize];
168 let idx = features
169 .features()
170 .iter()
171 .enumerate()
172 .find(|(_, f)| f.id() == feature.id())
173 .expect("Already checked existence")
174 .0;
175 let actuator = feature
176 .output()
177 .as_ref()
178 .ok_or(ButtplugDeviceError::DeviceConfigurationError(
179 "Device configuration does not have Vibrate actuator available.".to_owned(),
180 ))?
181 .vibrate()
182 .as_ref()
183 .ok_or(ButtplugDeviceError::DeviceConfigurationError(
184 "Device configuration does not have Vibrate actuator available.".to_owned(),
185 ))?;
186 cmds.push(CheckedOutputCmdV4::new(
187 msg.id(),
188 msg.device_index(),
189 idx as u32,
190 feature.id(),
191 OutputCommand::Vibrate(OutputValue::new(
192 actuator
193 .calculate_scaled_float(vibrate_cmd.speed())
194 .map_err(|e| ButtplugMessageError::InvalidMessageContents(e.to_string()))?,
195 )),
196 ))
197 }
198 Ok(CheckedOutputVecCmdV4::new(
199 msg.id(),
200 msg.device_index(),
201 cmds,
202 ))
203 }
204}
205
206impl TryFromDeviceAttributes<ScalarCmdV3> for CheckedOutputVecCmdV4 {
207 // ScalarCmd only came in with V3, so we can just use the V3 device attributes.
208 fn try_from_device_attributes(
209 msg: ScalarCmdV3,
210 attrs: &ServerDeviceAttributes,
211 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
212 let mut cmds: Vec<CheckedOutputCmdV4> = vec![];
213 if msg.scalars().is_empty() {
214 return Err(ButtplugError::from(
215 ButtplugDeviceError::ProtocolRequirementError(
216 "ScalarCmd with no subcommands is not allowed.".to_owned(),
217 ),
218 ));
219 }
220 for cmd in msg.scalars() {
221 let scalar_attrs = if let Some(a) = attrs.attrs_v3().scalar_cmd() {
222 a
223 } else {
224 continue;
225 };
226 let feature = scalar_attrs
227 .get(cmd.index() as usize)
228 .ok_or(ButtplugError::from(
229 ButtplugDeviceError::DeviceFeatureIndexError(scalar_attrs.len() as u32, cmd.index()),
230 ))?;
231 let idx = attrs
232 .features()
233 .iter()
234 .enumerate()
235 .find(|(_, f)| f.id() == feature.feature().id())
236 .expect("Already proved existence")
237 .0 as u32;
238 let output = feature
239 .feature()
240 .output()
241 .as_ref()
242 .ok_or(ButtplugError::from(
243 ButtplugDeviceError::DeviceNoActuatorError("ScalarCmdV3".to_owned()),
244 ))?;
245 let output_value = output
246 .calculate_from_float(cmd.actuator_type(), cmd.scalar())
247 .map_err(|e| {
248 error!("{:?}", e);
249 ButtplugError::from(ButtplugDeviceError::DeviceNoActuatorError(
250 "ScalarCmdV3".to_owned(),
251 ))
252 })?;
253 cmds.push(CheckedOutputCmdV4::new(
254 msg.id(),
255 msg.device_index(),
256 idx,
257 feature.feature.id(),
258 OutputCommand::from_output_type(cmd.actuator_type(), output_value).unwrap(),
259 ));
260 }
261
262 Ok(CheckedOutputVecCmdV4::new(
263 msg.id(),
264 msg.device_index(),
265 cmds,
266 ))
267 }
268}
269
270impl TryFromDeviceAttributes<LinearCmdV1> for CheckedOutputVecCmdV4 {
271 fn try_from_device_attributes(
272 msg: LinearCmdV1,
273 features: &ServerDeviceAttributes,
274 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
275 let features = features
276 .attrs_v3()
277 .linear_cmd()
278 .as_ref()
279 .ok_or(ButtplugError::from(
280 ButtplugDeviceError::DeviceFeatureMismatch(
281 "Device has no PositionWithDuration features".to_owned(),
282 ),
283 ))?;
284
285 let mut cmds = vec![];
286 for x in msg.vectors() {
287 let f = features
288 .get(x.index() as usize)
289 .ok_or(ButtplugDeviceError::DeviceFeatureIndexError(
290 features.len() as u32,
291 x.index(),
292 ))?
293 .feature();
294 let actuator = f
295 .output()
296 .as_ref()
297 .ok_or(ButtplugError::from(
298 ButtplugDeviceError::DeviceFeatureMismatch(
299 "Device got LinearCmd command but has no actuators on Linear feature.".to_owned(),
300 ),
301 ))?
302 .position_with_duration()
303 .as_ref()
304 .ok_or(ButtplugError::from(
305 ButtplugDeviceError::DeviceFeatureMismatch(
306 "Device got LinearCmd command but has no actuators on Linear feature.".to_owned(),
307 ),
308 ))?;
309 cmds.push(CheckedOutputCmdV4::new(
310 msg.device_index(),
311 x.index(),
312 0,
313 f.id(),
314 OutputCommand::PositionWithDuration(OutputPositionWithDuration::new(
315 actuator.calculate_scaled_float(x.position()).map_err(|_| {
316 ButtplugError::from(ButtplugMessageError::InvalidMessageContents(
317 "Position should be 0.0 < x < 1.0".to_owned(),
318 ))
319 })?,
320 x.duration().try_into().map_err(|_| {
321 ButtplugError::from(ButtplugMessageError::InvalidMessageContents(
322 "Duration should be under 2^31. You are not waiting 24 days to run this command."
323 .to_owned(),
324 ))
325 })?,
326 )),
327 ));
328 }
329 Ok(CheckedOutputVecCmdV4::new(
330 msg.id(),
331 msg.device_index(),
332 cmds,
333 ))
334 }
335}
336
337impl TryFromDeviceAttributes<RotateCmdV1> for CheckedOutputVecCmdV4 {
338 // RotateCmd exists up through Message Spec v3. We can assume that, if we're receiving it, we can
339 // just use the V3 spec client device attributes for it. If this was sent on a V1/V2 protocol,
340 // it'll still have all the same features.
341 fn try_from_device_attributes(
342 msg: RotateCmdV1,
343 attrs: &ServerDeviceAttributes,
344 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
345 let mut cmds: Vec<CheckedOutputCmdV4> = vec![];
346 for cmd in msg.rotations() {
347 let rotate_attrs = attrs
348 .attrs_v3()
349 .rotate_cmd()
350 .as_ref()
351 .ok_or(ButtplugError::from(
352 ButtplugDeviceError::MessageNotSupported(
353 ButtplugDeviceMessageNameV3::RotateCmd.to_string(),
354 ),
355 ))?;
356 let feature = rotate_attrs
357 .get(cmd.index() as usize)
358 .ok_or(ButtplugError::from(
359 ButtplugDeviceError::DeviceFeatureIndexError(rotate_attrs.len() as u32, cmd.index()),
360 ))?;
361 let idx = attrs
362 .features()
363 .iter()
364 .enumerate()
365 .find(|(_, f)| f.id() == feature.feature().id())
366 .expect("Already proved existence")
367 .0 as u32;
368 let actuator = feature
369 .feature()
370 .output()
371 .as_ref()
372 .ok_or(ButtplugError::from(
373 ButtplugDeviceError::DeviceNoActuatorError("RotateCmdV1".to_owned()),
374 ))?
375 .rotate()
376 .as_ref()
377 .ok_or(ButtplugError::from(
378 ButtplugDeviceError::DeviceNoActuatorError("RotateCmdV1".to_owned()),
379 ))?;
380 cmds.push(CheckedOutputCmdV4::new(
381 msg.id(),
382 msg.device_index(),
383 idx,
384 feature.feature.id(),
385 OutputCommand::Rotate(OutputValue::new(
386 actuator.calculate_scaled_float(cmd.speed()).map_err(|_| {
387 ButtplugError::from(ButtplugMessageError::InvalidMessageContents(
388 "Position should be 0.0 < x < 1.0".to_owned(),
389 ))
390 })?
391 * (if cmd.clockwise() { 1 } else { -1 }),
392 )),
393 ));
394 }
395 Ok(CheckedOutputVecCmdV4::new(
396 msg.id(),
397 msg.device_index(),
398 cmds,
399 ))
400 }
401}