Buttplug sex toy control library
at dev 13 kB view raw
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}