Buttplug sex toy control library
1use std::{collections::HashMap, fmt::Debug};
2
3use crate::message::{
4 ButtplugClientMessageVariant,
5 RequestServerInfoV1,
6 ServerDeviceAttributes,
7 TryFromDeviceAttributes,
8 server_device_attributes::TryFromClientMessage,
9 v0::ButtplugClientMessageV0,
10 v1::ButtplugClientMessageV1,
11 v2::ButtplugClientMessageV2,
12 v3::ButtplugClientMessageV3,
13};
14use buttplug_core::{
15 errors::{ButtplugDeviceError, ButtplugError, ButtplugMessageError},
16 message::{
17 ButtplugClientMessageV4,
18 ButtplugDeviceMessage,
19 ButtplugMessage,
20 ButtplugMessageFinalizer,
21 ButtplugMessageValidator,
22 PingV0,
23 RequestDeviceListV0,
24 RequestServerInfoV4,
25 StartScanningV0,
26 StopAllDevicesV0,
27 StopDeviceCmdV0,
28 StopScanningV0,
29 },
30};
31
32use super::{
33 checked_input_cmd::CheckedInputCmdV4,
34 checked_output_cmd::CheckedOutputCmdV4,
35 checked_output_vec_cmd::CheckedOutputVecCmdV4,
36};
37
38/// An CheckedClientMessage has had its contents verified and should need no further error/validity
39/// checking. Processing may still return errors, but should be due to system state, not message
40/// contents.
41///
42/// There should only be one version of CheckedClientMessage in the library, matching the latest
43/// version of the message spec. For any messages that don't require error checking, their regular
44/// struct can be used as an enum parameter. Any messages requiring error checking or validation
45/// will have an alternate Checked[x] form that they will need to be cast as.
46#[derive(
47 Debug,
48 Clone,
49 PartialEq,
50 ButtplugMessage,
51 ButtplugMessageValidator,
52 ButtplugMessageFinalizer,
53 FromSpecificButtplugMessage,
54)]
55pub enum ButtplugCheckedClientMessageV4 {
56 // Handshake messages
57 RequestServerInfo(RequestServerInfoV4),
58 Ping(PingV0),
59 // Device enumeration messages
60 StartScanning(StartScanningV0),
61 StopScanning(StopScanningV0),
62 RequestDeviceList(RequestDeviceListV0),
63 // Generic commands
64 StopDeviceCmd(StopDeviceCmdV0),
65 StopAllDevices(StopAllDevicesV0),
66 OutputCmd(CheckedOutputCmdV4),
67 // Sensor commands
68 InputCmd(CheckedInputCmdV4),
69 // Internal conversions for v1-v3 messages with subcommands
70 OutputVecCmd(CheckedOutputVecCmdV4),
71}
72
73impl TryFromClientMessage<ButtplugClientMessageV4> for ButtplugCheckedClientMessageV4 {
74 fn try_from_client_message(
75 value: ButtplugClientMessageV4,
76 feature_map: &HashMap<u32, ServerDeviceAttributes>,
77 ) -> Result<Self, ButtplugError> {
78 match value {
79 // Messages that don't need checking
80 ButtplugClientMessageV4::RequestServerInfo(m) => {
81 Ok(ButtplugCheckedClientMessageV4::RequestServerInfo(m))
82 }
83 ButtplugClientMessageV4::Ping(m) => Ok(ButtplugCheckedClientMessageV4::Ping(m)),
84 ButtplugClientMessageV4::StartScanning(m) => {
85 Ok(ButtplugCheckedClientMessageV4::StartScanning(m))
86 }
87 ButtplugClientMessageV4::StopScanning(m) => {
88 Ok(ButtplugCheckedClientMessageV4::StopScanning(m))
89 }
90 ButtplugClientMessageV4::RequestDeviceList(m) => {
91 Ok(ButtplugCheckedClientMessageV4::RequestDeviceList(m))
92 }
93 ButtplugClientMessageV4::StopAllDevices(m) => {
94 Ok(ButtplugCheckedClientMessageV4::StopAllDevices(m))
95 }
96
97 // Messages that need device index checking
98 ButtplugClientMessageV4::StopDeviceCmd(m) => {
99 if feature_map.get(&m.device_index()).is_some() {
100 Ok(ButtplugCheckedClientMessageV4::StopDeviceCmd(m))
101 } else {
102 Err(ButtplugError::from(
103 ButtplugDeviceError::DeviceNotAvailable(m.device_index()),
104 ))
105 }
106 }
107
108 // Message that need device index and feature checking
109 ButtplugClientMessageV4::OutputCmd(m) => {
110 if let Some(features) = feature_map.get(&m.device_index()) {
111 Ok(ButtplugCheckedClientMessageV4::OutputCmd(
112 CheckedOutputCmdV4::try_from_device_attributes(m, features)?,
113 ))
114 } else {
115 Err(ButtplugError::from(
116 ButtplugDeviceError::DeviceNotAvailable(m.device_index()),
117 ))
118 }
119 }
120 ButtplugClientMessageV4::InputCmd(m) => {
121 if let Some(features) = feature_map.get(&m.device_index()) {
122 Ok(ButtplugCheckedClientMessageV4::InputCmd(
123 CheckedInputCmdV4::try_from_device_attributes(m, features)?,
124 ))
125 } else {
126 Err(ButtplugError::from(
127 ButtplugDeviceError::DeviceNotAvailable(m.device_index()),
128 ))
129 }
130 }
131 }
132 }
133}
134
135impl From<RequestServerInfoV1> for RequestServerInfoV4 {
136 fn from(value: RequestServerInfoV1) -> Self {
137 let mut msg = RequestServerInfoV4::new(value.client_name(), value.message_version(), 0);
138 msg.set_id(value.id());
139 msg
140 }
141}
142
143// For v3 to v4, all deprecations should be treated as conversions, but will require current
144// connected device state, meaning they'll need to be implemented where they can also access the
145// device manager.
146impl TryFrom<ButtplugClientMessageV3> for ButtplugCheckedClientMessageV4 {
147 type Error = ButtplugMessageError;
148
149 fn try_from(value: ButtplugClientMessageV3) -> Result<Self, Self::Error> {
150 match value {
151 ButtplugClientMessageV3::Ping(m) => Ok(ButtplugCheckedClientMessageV4::Ping(m.clone())),
152 ButtplugClientMessageV3::RequestServerInfo(m) => Ok(
153 ButtplugCheckedClientMessageV4::RequestServerInfo(RequestServerInfoV4::from(m)),
154 ),
155 ButtplugClientMessageV3::StartScanning(m) => {
156 Ok(ButtplugCheckedClientMessageV4::StartScanning(m.clone()))
157 }
158 ButtplugClientMessageV3::StopScanning(m) => {
159 Ok(ButtplugCheckedClientMessageV4::StopScanning(m.clone()))
160 }
161 ButtplugClientMessageV3::RequestDeviceList(m) => {
162 Ok(ButtplugCheckedClientMessageV4::RequestDeviceList(m.clone()))
163 }
164 ButtplugClientMessageV3::StopAllDevices(m) => {
165 Ok(ButtplugCheckedClientMessageV4::StopAllDevices(m.clone()))
166 }
167 ButtplugClientMessageV3::StopDeviceCmd(m) => {
168 Ok(ButtplugCheckedClientMessageV4::StopDeviceCmd(m.clone()))
169 }
170 _ => Err(ButtplugMessageError::MessageConversionError(format!(
171 "Cannot convert message {value:?} to V4 message spec while lacking state."
172 ))),
173 }
174 }
175}
176
177impl TryFromClientMessage<ButtplugClientMessageVariant> for ButtplugCheckedClientMessageV4 {
178 fn try_from_client_message(
179 msg: ButtplugClientMessageVariant,
180 features: &HashMap<u32, ServerDeviceAttributes>,
181 ) -> Result<Self, buttplug_core::errors::ButtplugError> {
182 let id = msg.id();
183 let mut converted_msg = match msg {
184 ButtplugClientMessageVariant::V0(m) => Self::try_from_client_message(m, features),
185 ButtplugClientMessageVariant::V1(m) => Self::try_from_client_message(m, features),
186 ButtplugClientMessageVariant::V2(m) => Self::try_from_client_message(m, features),
187 ButtplugClientMessageVariant::V3(m) => Self::try_from_client_message(m, features),
188 ButtplugClientMessageVariant::V4(m) => Self::try_from_client_message(m, features),
189 }?;
190 // Always make sure the ID is set after conversion
191 converted_msg.set_id(id);
192 Ok(converted_msg)
193 }
194}
195
196impl TryFromClientMessage<ButtplugClientMessageV0> for ButtplugCheckedClientMessageV4 {
197 fn try_from_client_message(
198 msg: ButtplugClientMessageV0,
199 features: &HashMap<u32, ServerDeviceAttributes>,
200 ) -> Result<Self, ButtplugError> {
201 // All v0 messages can be converted to v1 messages.
202 Self::try_from_client_message(ButtplugClientMessageV1::from(msg), features)
203 }
204}
205
206fn check_device_index_and_convert<T, U>(
207 msg: T,
208 features: &HashMap<u32, ServerDeviceAttributes>,
209) -> Result<U, ButtplugError>
210where
211 T: ButtplugDeviceMessage + Debug,
212 U: TryFromDeviceAttributes<T> + Debug,
213{
214 // Vorze and RotateCmd are equivalent, so this is an ok conversion.
215 if let Some(attrs) = features.get(&msg.device_index()) {
216 Ok(U::try_from_device_attributes(msg.clone(), attrs)?)
217 } else {
218 Err(ButtplugError::from(
219 ButtplugDeviceError::DeviceNotAvailable(msg.device_index()),
220 ))
221 }
222}
223
224impl TryFromClientMessage<ButtplugClientMessageV1> for ButtplugCheckedClientMessageV4 {
225 fn try_from_client_message(
226 msg: ButtplugClientMessageV1,
227 features: &HashMap<u32, ServerDeviceAttributes>,
228 ) -> Result<Self, ButtplugError> {
229 // Instead of converting to v2 message attributes then to v4 device features, we move directly
230 // from v0 command messages to v4 device features here. There's no reason to do the middle step.
231 match msg {
232 ButtplugClientMessageV1::VorzeA10CycloneCmd(_) => {
233 // Vorze and RotateCmd are equivalent, so this is an ok conversion.
234 Err(ButtplugError::ButtplugMessageError(ButtplugMessageError::MessageConversionError("VorzeA10CycloneCmd is considered unused, and no longer supported. If you are seeing this message and need VorzeA10CycloneCmd, file an issue in the Buttplug repo.".to_owned())))
235 }
236 ButtplugClientMessageV1::SingleMotorVibrateCmd(m) => {
237 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
238 }
239 _ => Self::try_from_client_message(ButtplugClientMessageV2::try_from(msg)?, features),
240 }
241 }
242}
243
244impl TryFromClientMessage<ButtplugClientMessageV2> for ButtplugCheckedClientMessageV4 {
245 fn try_from_client_message(
246 msg: ButtplugClientMessageV2,
247 features: &HashMap<u32, ServerDeviceAttributes>,
248 ) -> Result<Self, ButtplugError> {
249 match msg {
250 // Convert v2 specific queries to v3 generic sensor queries
251 ButtplugClientMessageV2::BatteryLevelCmd(m) => {
252 Ok(check_device_index_and_convert::<_, CheckedInputCmdV4>(m, features)?.into())
253 }
254 // Convert VibrateCmd to a ScalarCmd command
255 ButtplugClientMessageV2::VibrateCmd(m) => {
256 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
257 }
258 _ => Self::try_from_client_message(ButtplugClientMessageV3::try_from(msg)?, features),
259 }
260 }
261}
262
263impl TryFromClientMessage<ButtplugClientMessageV3> for ButtplugCheckedClientMessageV4 {
264 fn try_from_client_message(
265 msg: ButtplugClientMessageV3,
266 features: &HashMap<u32, ServerDeviceAttributes>,
267 ) -> Result<Self, ButtplugError> {
268 match msg {
269 // Convert v1/v2 message attribute commands into device feature commands
270 ButtplugClientMessageV3::VibrateCmd(m) => {
271 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
272 }
273 ButtplugClientMessageV3::ScalarCmd(m) => {
274 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
275 }
276 ButtplugClientMessageV3::RotateCmd(m) => {
277 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
278 }
279 ButtplugClientMessageV3::LinearCmd(m) => {
280 Ok(check_device_index_and_convert::<_, CheckedOutputVecCmdV4>(m, features)?.into())
281 }
282 ButtplugClientMessageV3::SensorReadCmd(m) => {
283 Ok(check_device_index_and_convert::<_, CheckedInputCmdV4>(m, features)?.into())
284 }
285 ButtplugClientMessageV3::SensorSubscribeCmd(_) => {
286 // Always reject v3 sub/unsub. It was never implemented or indexed correctly.
287 Err(ButtplugError::from(
288 ButtplugDeviceError::MessageNotSupported("SensorSubscribeCmdV3".to_owned()),
289 ))
290 }
291 ButtplugClientMessageV3::SensorUnsubscribeCmd(_) => {
292 // Always reject v3 sub/unsub. It was never implemented or indexed correctly.
293 Err(ButtplugError::from(
294 ButtplugDeviceError::MessageNotSupported("SensorUnsubscribeCmdV3".to_owned()),
295 ))
296 }
297 _ => {
298 ButtplugCheckedClientMessageV4::try_from(msg).map_err(|e: ButtplugMessageError| e.into())
299 }
300 }
301 }
302}
303
304/// Represents messages that should go to the
305/// [DeviceManager][crate::server::device_manager::DeviceManager] of a
306/// [ButtplugServer](crate::server::ButtplugServer)
307#[derive(
308 Debug,
309 Clone,
310 PartialEq,
311 Eq,
312 ButtplugMessage,
313 ButtplugMessageValidator,
314 ButtplugMessageFinalizer,
315 FromSpecificButtplugMessage,
316)]
317pub(crate) enum ButtplugDeviceManagerMessageUnion {
318 RequestDeviceList(RequestDeviceListV0),
319 StopAllDevices(StopAllDevicesV0),
320 StartScanning(StartScanningV0),
321 StopScanning(StopScanningV0),
322}
323
324impl TryFrom<ButtplugCheckedClientMessageV4> for ButtplugDeviceManagerMessageUnion {
325 type Error = ();
326
327 fn try_from(value: ButtplugCheckedClientMessageV4) -> Result<Self, Self::Error> {
328 match value {
329 ButtplugCheckedClientMessageV4::RequestDeviceList(m) => {
330 Ok(ButtplugDeviceManagerMessageUnion::RequestDeviceList(m))
331 }
332 ButtplugCheckedClientMessageV4::StopAllDevices(m) => {
333 Ok(ButtplugDeviceManagerMessageUnion::StopAllDevices(m))
334 }
335 ButtplugCheckedClientMessageV4::StartScanning(m) => {
336 Ok(ButtplugDeviceManagerMessageUnion::StartScanning(m))
337 }
338 ButtplugCheckedClientMessageV4::StopScanning(m) => {
339 Ok(ButtplugDeviceManagerMessageUnion::StopScanning(m))
340 }
341 _ => Err(()),
342 }
343 }
344}
345
346/// Represents all possible device command message types.
347#[derive(
348 Debug,
349 Clone,
350 PartialEq,
351 ButtplugDeviceMessage,
352 ButtplugMessageValidator,
353 ButtplugMessageFinalizer,
354 FromSpecificButtplugMessage,
355)]
356pub enum ButtplugDeviceCommandMessageUnionV4 {
357 StopDeviceCmd(StopDeviceCmdV0),
358 OutputCmd(CheckedOutputCmdV4),
359 OutputVecCmd(CheckedOutputVecCmdV4),
360 InputCmd(CheckedInputCmdV4),
361}
362
363impl TryFrom<ButtplugCheckedClientMessageV4> for ButtplugDeviceCommandMessageUnionV4 {
364 type Error = ();
365
366 fn try_from(value: ButtplugCheckedClientMessageV4) -> Result<Self, Self::Error> {
367 match value {
368 ButtplugCheckedClientMessageV4::StopDeviceCmd(m) => {
369 Ok(ButtplugDeviceCommandMessageUnionV4::StopDeviceCmd(m))
370 }
371 ButtplugCheckedClientMessageV4::OutputCmd(m) => {
372 Ok(ButtplugDeviceCommandMessageUnionV4::OutputCmd(m))
373 }
374 ButtplugCheckedClientMessageV4::OutputVecCmd(m) => {
375 Ok(ButtplugDeviceCommandMessageUnionV4::OutputVecCmd(m))
376 }
377 ButtplugCheckedClientMessageV4::InputCmd(m) => {
378 Ok(ButtplugDeviceCommandMessageUnionV4::InputCmd(m))
379 }
380 _ => Err(()),
381 }
382 }
383}
384
385#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash, Display)]
386pub enum ButtplugDeviceMessageNameV4 {
387 StopDeviceCmd,
388 InputCmd,
389 OutputCmd,
390}