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::{ServerDeviceAttributes, TryFromDeviceAttributes};
9use buttplug_core::{
10 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError},
11 message::{
12 ButtplugDeviceMessage,
13 ButtplugMessage,
14 ButtplugMessageFinalizer,
15 ButtplugMessageValidator,
16 OutputCmdV4,
17 OutputCommand,
18 },
19};
20
21use getset::{CopyGetters, Getters};
22use uuid::Uuid;
23
24use super::spec_enums::ButtplugDeviceMessageNameV4;
25
26#[derive(
27 Debug, ButtplugDeviceMessage, ButtplugMessageFinalizer, Clone, Getters, CopyGetters, Eq,
28)]
29#[getset(get_copy = "pub")]
30pub struct CheckedOutputCmdV4 {
31 id: u32,
32 device_index: u32,
33 feature_index: u32,
34 feature_id: Uuid,
35 output_command: OutputCommand,
36}
37
38impl PartialEq for CheckedOutputCmdV4 {
39 fn eq(&self, other: &Self) -> bool {
40 // Compare everything but the message id
41 self.device_index() == other.device_index()
42 && self.feature_index() == other.feature_index()
43 && self.feature_id() == other.feature_id()
44 && self.output_command() == other.output_command()
45 }
46}
47
48/*
49
50impl From<CheckedActuatorCmdV4> for ActuatorCmdV4 {
51 fn from(value: CheckedActuatorCmdV4) -> Self {
52 ActuatorCmdV4::new(
53 value.device_index(),
54 value.feature_index(),
55 value.actuator_type(),
56 value.output_command()
57 )
58 }
59}
60 */
61
62impl CheckedOutputCmdV4 {
63 pub fn new(
64 id: u32,
65 device_index: u32,
66 feature_index: u32,
67 feature_id: Uuid,
68 output_command: OutputCommand,
69 ) -> Self {
70 Self {
71 id,
72 device_index,
73 feature_index,
74 feature_id,
75 output_command,
76 }
77 }
78}
79
80impl ButtplugMessageValidator for CheckedOutputCmdV4 {
81 fn is_valid(&self) -> Result<(), ButtplugMessageError> {
82 self.is_not_system_id(self.id)?;
83 Ok(())
84 }
85}
86
87impl TryFromDeviceAttributes<OutputCmdV4> for CheckedOutputCmdV4 {
88 fn try_from_device_attributes(
89 cmd: OutputCmdV4,
90 attrs: &ServerDeviceAttributes,
91 ) -> Result<Self, ButtplugError> {
92 let features = attrs.features();
93
94 // Since we have the feature info already, check limit and unpack into step range when creating.
95 //
96 // If this message isn't the result of an upgrade from another older message, we won't have set
97 // our feature id yet.
98 let (feature, _) = if let Some(feature) = features.get(cmd.feature_index() as usize) {
99 (feature, feature.id())
100 } else {
101 return Err(ButtplugError::from(
102 ButtplugDeviceError::DeviceFeatureIndexError(features.len() as u32, cmd.feature_index()),
103 ));
104 };
105
106 // Check to make sure the feature has an actuator that handles the data we've been passed
107 if let Some(output_map) = feature.output() {
108 let output_type = cmd.command().as_output_type();
109 let value = cmd.command().value();
110 let new_value = output_map
111 .calculate_from_value(output_type, value)
112 .map_err(|e| {
113 error!("{:?}", e);
114 ButtplugDeviceError::DeviceStepRangeError(0, value)
115 })?;
116 let mut new_command = cmd.command();
117 new_command.set_value(new_value);
118 // We can't make a private trait impl to turn a ValueCmd into a CheckedValueCmd, and this
119 // is all about security, so we just copy. Silly, but it works for our needs in terms of
120 // making this a barrier.
121 Ok(Self {
122 id: cmd.id(),
123 feature_id: feature.id(),
124 device_index: cmd.device_index(),
125 feature_index: cmd.feature_index(),
126 output_command: new_command,
127 })
128 } else {
129 Err(ButtplugError::from(
130 ButtplugDeviceError::MessageNotSupported(
131 ButtplugDeviceMessageNameV4::OutputCmd.to_string(),
132 ),
133 ))
134 }
135 }
136}