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}