Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2023 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 async_trait::async_trait;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU8, Ordering};
11use uuid::{Uuid, uuid};
12
13use futures_util::future::BoxFuture;
14use futures_util::{FutureExt, future};
15
16use buttplug_core::errors::ButtplugDeviceError;
17use buttplug_core::message::{InputData, InputReadingV4, InputType, InputTypeData};
18use buttplug_server_device_config::Endpoint;
19
20use buttplug_server_device_config::{
21 ProtocolCommunicationSpecifier,
22 ServerDeviceDefinition,
23 UserDeviceIdentifier,
24};
25
26use crate::device::{
27 hardware::{
28 Hardware,
29 HardwareCommand,
30 HardwareEvent,
31 HardwareSubscribeCmd,
32 HardwareUnsubscribeCmd,
33 HardwareWriteCmd,
34 },
35 protocol::{
36 ProtocolHandler,
37 ProtocolIdentifier,
38 ProtocolInitializer,
39 generic_protocol_initializer_setup,
40 },
41};
42
43static KEY_TAB: [[u32; 12]; 4] = [
44 [0, 24, 152, 247, 165, 61, 13, 41, 37, 80, 68, 70],
45 [0, 69, 110, 106, 111, 120, 32, 83, 45, 49, 46, 55],
46 [0, 101, 120, 32, 84, 111, 121, 115, 10, 142, 157, 163],
47 [0, 197, 214, 231, 248, 10, 50, 32, 111, 98, 13, 10],
48];
49
50fn get_tab_key(r: usize, t: usize) -> u32 {
51 let e = 3 & r;
52 KEY_TAB[e][t]
53}
54
55fn encrypt(data: Vec<u32>) -> Vec<u32> {
56 let mut new_data = vec![data[0]];
57 for i in 1..data.len() {
58 let a = get_tab_key(new_data[i - 1] as usize, i);
59 let u = (a ^ data[0] ^ data[i]) + a;
60 new_data.push(u);
61 }
62 new_data
63}
64
65fn send_bytes(data: Vec<u32>) -> Vec<u8> {
66 let mut new_data = vec![35];
67 new_data.extend(data);
68 new_data.push(new_data.iter().sum());
69 let mut uint8_array: Vec<u8> = Vec::new();
70 for value in encrypt(new_data) {
71 uint8_array.push(value as u8);
72 }
73 uint8_array
74}
75
76/*
77
78fn decrypt(data: Vec<u32>) -> Vec<u32> {
79 let mut new_data = vec![data[0]];
80 for i in 1..data.len() {
81 let a = get_tab_key(data[i - 1] as usize, i);
82 let u = (data[i] as i32 - a as i32) ^ data[0] as i32 ^ a as i32;
83 new_data.push(if u < 0 { (u + 256) as u32 } else { u as u32 });
84 }
85 new_data
86}
87
88
89fn read_value(data: Vec<u8>) -> u32 {
90 let mut uint32_data: Vec<u32> = Vec::new();
91 for value in data {
92 uint32_data.push(value as u32);
93 }
94 let decrypted_data = decrypt(uint32_data);
95 if !decrypted_data.is_empty() {
96 decrypted_data[4]
97 } else {
98 0
99 }
100}
101
102*/
103
104const GALAKU_PROTOCOL_UUID: Uuid = uuid!("766d15d5-0f43-4768-a73a-96ff48bc389e");
105generic_protocol_initializer_setup!(Galaku, "galaku");
106
107#[derive(Default)]
108pub struct GalakuInitializer {}
109
110#[async_trait]
111impl ProtocolInitializer for GalakuInitializer {
112 async fn initialize(
113 &mut self,
114 hardware: Arc<Hardware>,
115 def: &ServerDeviceDefinition,
116 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
117 let mut protocol = Galaku::default();
118 protocol.is_caiping_pump_device = false;
119 if hardware.name() == "AC695X_1(BLE)" {
120 protocol.is_caiping_pump_device = true;
121 }
122 for _ in 0..def
123 .features()
124 .iter()
125 .filter(|f| f.output().is_some())
126 .count()
127 {
128 protocol.speeds.push(AtomicU8::new(0));
129 }
130 Ok(Arc::new(protocol))
131 }
132}
133
134#[derive(Default)]
135pub struct Galaku {
136 is_caiping_pump_device: bool,
137 speeds: Vec<AtomicU8>,
138}
139
140impl Galaku {
141 fn handle_local_output_cmd(
142 &self,
143 feature_index: u32,
144 _feature_id: Uuid,
145 speed: u32,
146 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
147 if self.speeds.len() == 1 {
148 if self.is_caiping_pump_device {
149 let data: Vec<u8> = vec![
150 0xAA,
151 1,
152 10,
153 3,
154 speed as u8,
155 if speed == 0 { 0 } else { 1 },
156 0,
157 0,
158 0,
159 0,
160 0,
161 0,
162 0,
163 0,
164 0,
165 0,
166 ];
167 Ok(vec![
168 HardwareWriteCmd::new(&[GALAKU_PROTOCOL_UUID], Endpoint::Tx, data, false).into(),
169 ])
170 } else {
171 let data = vec![90, 0, 0, 1, 49, speed, 0, 0, 0, 0];
172
173 Ok(vec![
174 HardwareWriteCmd::new(
175 &[GALAKU_PROTOCOL_UUID],
176 Endpoint::Tx,
177 send_bytes(data),
178 false,
179 )
180 .into(),
181 ])
182 }
183 } else {
184 if feature_index < self.speeds.len() as u32 {
185 self.speeds[feature_index as usize].store(speed as u8, Ordering::Relaxed);
186 } else {
187 error!(
188 "Tried to set value on out-of-bounds index {} (size {})",
189 feature_index,
190 self.speeds.len()
191 );
192 }
193
194 let data: Vec<u32> = vec![
195 90,
196 0,
197 0,
198 1,
199 64,
200 3,
201 self.speeds[0].load(Ordering::Relaxed) as u32,
202 self.speeds[1].load(Ordering::Relaxed) as u32,
203 0,
204 0,
205 ];
206 Ok(vec![
207 HardwareWriteCmd::new(
208 &[GALAKU_PROTOCOL_UUID],
209 Endpoint::Tx,
210 send_bytes(data),
211 false,
212 )
213 .into(),
214 ])
215 }
216 }
217}
218
219impl ProtocolHandler for Galaku {
220 fn handle_output_oscillate_cmd(
221 &self,
222 feature_index: u32,
223 feature_id: Uuid,
224 speed: u32,
225 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
226 self.handle_local_output_cmd(feature_index, feature_id, speed)
227 }
228 fn handle_output_vibrate_cmd(
229 &self,
230 feature_index: u32,
231 feature_id: Uuid,
232 speed: u32,
233 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
234 self.handle_local_output_cmd(feature_index, feature_id, speed)
235 }
236 fn handle_output_constrict_cmd(
237 &self,
238 feature_index: u32,
239 feature_id: Uuid,
240 level: u32,
241 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
242 self.handle_local_output_cmd(feature_index, feature_id, level)
243 }
244
245 fn handle_input_subscribe_cmd(
246 &self,
247 _device_index: u32,
248 device: Arc<Hardware>,
249 _feature_index: u32,
250 feature_id: Uuid,
251 sensor_type: InputType,
252 ) -> BoxFuture<'_, Result<(), ButtplugDeviceError>> {
253 match sensor_type {
254 InputType::Battery => {
255 async move {
256 device
257 .subscribe(&HardwareSubscribeCmd::new(
258 feature_id,
259 Endpoint::RxBLEBattery,
260 ))
261 .await?;
262 Ok(())
263 }
264 }
265 .boxed(),
266 _ => future::ready(Err(ButtplugDeviceError::UnhandledCommand(
267 "Command not implemented for this sensor".to_string(),
268 )))
269 .boxed(),
270 }
271 }
272
273 fn handle_input_unsubscribe_cmd(
274 &self,
275 device: Arc<Hardware>,
276 _feature_index: u32,
277 feature_id: Uuid,
278 sensor_type: InputType,
279 ) -> BoxFuture<'_, Result<(), ButtplugDeviceError>> {
280 match sensor_type {
281 InputType::Battery => {
282 async move {
283 device
284 .unsubscribe(&HardwareUnsubscribeCmd::new(
285 feature_id,
286 Endpoint::RxBLEBattery,
287 ))
288 .await?;
289 Ok(())
290 }
291 }
292 .boxed(),
293 _ => future::ready(Err(ButtplugDeviceError::UnhandledCommand(
294 "Command not implemented for this sensor".to_string(),
295 )))
296 .boxed(),
297 }
298 }
299
300 fn handle_battery_level_cmd(
301 &self,
302 device_index: u32,
303 device: Arc<Hardware>,
304 feature_index: u32,
305 feature_id: Uuid,
306 ) -> BoxFuture<'_, Result<InputReadingV4, ButtplugDeviceError>> {
307 let data: Vec<u32> = vec![90, 0, 0, 1, 19, 0, 0, 0, 0, 0];
308 let mut device_notification_receiver = device.event_stream();
309 async move {
310 device
311 .subscribe(&HardwareSubscribeCmd::new(
312 feature_id,
313 Endpoint::RxBLEBattery,
314 ))
315 .await?;
316 device
317 .write_value(&HardwareWriteCmd::new(
318 &[feature_id],
319 Endpoint::Tx,
320 send_bytes(data),
321 true,
322 ))
323 .await?;
324 while let Ok(event) = device_notification_receiver.recv().await {
325 return match event {
326 HardwareEvent::Notification(_, endpoint, data) => {
327 if endpoint != Endpoint::RxBLEBattery {
328 continue;
329 }
330 let battery_reading = InputReadingV4::new(
331 device_index,
332 feature_index,
333 InputTypeData::Battery(InputData::new(data[0])),
334 );
335 Ok(battery_reading)
336 }
337 HardwareEvent::Disconnected(_) => Err(ButtplugDeviceError::ProtocolSpecificError(
338 "Galaku".to_owned(),
339 "Galaku Device disconnected while getting Battery info.".to_owned(),
340 )),
341 };
342 }
343 Err(ButtplugDeviceError::ProtocolSpecificError(
344 "Galaku".to_owned(),
345 "Galaku Device disconnected while getting Battery info.".to_owned(),
346 ))
347 }
348 .boxed()
349 }
350}