diff --git a/buttplug/Cargo.toml b/buttplug/Cargo.toml index ac6964b0..a75ffbc9 100644 --- a/buttplug/Cargo.toml +++ b/buttplug/Cargo.toml @@ -94,6 +94,10 @@ instant = "0.1.12" regex = "1.10.2" tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-webpki-roots"], optional = true } rustls = { version = "0.22.1", optional = true } +aes = { version = "0.8.3" } +ecb = { version = "0.1.2", features = ["std"] } +rand = { version = "0.8.5" } +sha2 = { version = "0.10.8", features = ["std"] } [dev-dependencies] serde_yaml = "0.9.29" diff --git a/buttplug/buttplug-device-config/buttplug-device-config.json b/buttplug/buttplug-device-config/buttplug-device-config.json index beacc467..fa968b24 100644 --- a/buttplug/buttplug-device-config/buttplug-device-config.json +++ b/buttplug/buttplug-device-config/buttplug-device-config.json @@ -8799,6 +8799,40 @@ ] } } + }, + "vibcrafter": { + "btle": { + "names": [ + "be gentle" + ], + "services": { + "53300051-0060-4bd4-bbe5-a6920e4c5663": { + "tx": "53300052-0060-4bd4-bbe5-a6920e4c5663", + "rx": "53300053-0060-4bd4-bbe5-a6920e4c5663" + } + } + }, + "defaults": { + "name": "VibCrafter Device", + "messages": { + "ScalarCmd": [ + { + "StepRange": [ + 0, + 99 + ], + "ActuatorType": "Vibrate" + }, + { + "StepRange": [ + 0, + 99 + ], + "ActuatorType": "Vibrate" + } + ] + } + } } } } diff --git a/buttplug/buttplug-device-config/buttplug-device-config.yml b/buttplug/buttplug-device-config/buttplug-device-config.yml index 51d2689f..52fd5a7f 100644 --- a/buttplug/buttplug-device-config/buttplug-device-config.yml +++ b/buttplug/buttplug-device-config/buttplug-device-config.yml @@ -4353,3 +4353,20 @@ protocols: ScalarCmd: - StepRange: [ 0, 25 ] ActuatorType: Vibrate + vibcrafter: + btle: + names: + - be gentle + services: + 53300051-0060-4bd4-bbe5-a6920e4c5663: + tx: 53300052-0060-4bd4-bbe5-a6920e4c5663 + rx: 53300053-0060-4bd4-bbe5-a6920e4c5663 + defaults: + name: VibCrafter Device + messages: + ScalarCmd: + - StepRange: [ 0, 99 ] + ActuatorType: Vibrate + - StepRange: [ 0, 99 ] + ActuatorType: Vibrate + diff --git a/buttplug/src/server/device/protocol/mod.rs b/buttplug/src/server/device/protocol/mod.rs index ce71a8fe..98e1bc24 100644 --- a/buttplug/src/server/device/protocol/mod.rs +++ b/buttplug/src/server/device/protocol/mod.rs @@ -97,6 +97,7 @@ pub mod synchro; pub mod tcode_v03; pub mod thehandy; pub mod tryfun; +pub mod vibcrafter; pub mod vibratissimo; pub mod vorze_sa; pub mod wetoy; @@ -467,6 +468,10 @@ pub fn get_default_protocol_map() -> HashMap; +type Aes128EcbDec = ecb::Decryptor; + +const VIBCRAFTER_KEY: [u8; 16] = *b"jdk#Cra%f5Vib28r"; + +generic_protocol_initializer_setup!(VibCrafter, "vibcrafter"); + +#[derive(Default)] +pub struct VibCrafterInitializer {} + +fn encrypt(command: String) -> Vec { + let enc = Aes128EcbEnc::new(&VIBCRAFTER_KEY.into()); + let res = enc.encrypt_padded_vec_mut::(command.as_bytes()); + + info!("Encoded {} to {:?}", command, res); + return res; +} + +fn decrypt(data: Vec) -> String { + let dec = Aes128EcbDec::new(&VIBCRAFTER_KEY.into()); + let res = String::from_utf8(dec.decrypt_padded_vec_mut::(&data).unwrap()).unwrap(); + + info!("Decoded {} from {:?}", res, data); + return res; +} + +#[async_trait] +impl ProtocolInitializer for VibCrafterInitializer { + async fn initialize( + &mut self, + hardware: Arc, + _: &ProtocolDeviceAttributes, + ) -> Result, ButtplugDeviceError> { + let mut event_receiver = hardware.event_stream(); + hardware + .subscribe(&HardwareSubscribeCmd::new(Endpoint::Rx)) + .await?; + + let auth_str = thread_rng() + .sample_iter(&Alphanumeric) + .take(6) + .map(char::from) + .collect::(); + let auth_msg = format!("Auth:{};", auth_str); + hardware + .write_value(&HardwareWriteCmd::new( + Endpoint::Tx, + encrypt(auth_msg), + false, + )) + .await?; + + loop { + let event = event_receiver.recv().await; + if let Ok(HardwareEvent::Notification(_, _, n)) = event { + let decoded = decrypt(n); + if decoded.eq("OK;") { + debug!("VibCrafter authenticated!"); + return Ok(Arc::new(VibCrafter::default())); + } + let challenge = Regex::new(r"^[a-zA-Z0-9]{4}:([a-zA-Z0-9]+);$") + .expect("This is static and should always compile"); + if let Some(parts) = challenge.captures(decoded.as_str()) { + debug!("VibCrafter challenge {:?}", parts); + if let Some(to_hash) = parts.get(1) { + debug!("VibCrafter to hash {:?}", to_hash); + let mut sha256 = Sha256::new(); + sha256.update(&to_hash.as_str().as_bytes()); + let result = &sha256.finalize(); + + let auth_msg = format!("Auth:{:02x}{:02x};", result[0], result[1]); + hardware + .write_value(&HardwareWriteCmd::new( + Endpoint::Tx, + encrypt(auth_msg), + false, + )) + .await?; + } else { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "VibCrafter".to_owned(), + "VibCrafter didn't provided valid security handshake".to_owned(), + )); + } + } else { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "VibCrafter".to_owned(), + "VibCrafter didn't provided valid security handshake".to_owned(), + )); + } + } else { + return Err(ButtplugDeviceError::ProtocolSpecificError( + "VibCrafter".to_owned(), + "VibCrafter didn't provided valid security handshake".to_owned(), + )); + } + } + } +} + +#[derive(Default)] +pub struct VibCrafter {} + +impl ProtocolHandler for VibCrafter { + fn keepalive_strategy(&self) -> super::ProtocolKeepaliveStrategy { + super::ProtocolKeepaliveStrategy::RepeatLastPacketStrategy + } + + fn needs_full_command_set(&self) -> bool { + true + } + + fn handle_scalar_cmd( + &self, + commands: &[Option<(ActuatorType, u32)>], + ) -> Result, ButtplugDeviceError> { + let speed0 = commands[0].unwrap_or((ActuatorType::Vibrate, 0)).1; + let speed1 = if commands.len() > 1 { + commands[1].unwrap_or((ActuatorType::Vibrate, 0)).1 + } else { + speed0 + }; + + Ok(vec![HardwareWriteCmd::new( + Endpoint::Tx, + encrypt(format!("MtInt:{:02}{:02};", speed0, speed1)), + false, + ) + .into()]) + } +}