Skip to content

Commit

Permalink
feat: Add support for VibCrafter devices
Browse files Browse the repository at this point in the history
Note that this protocol encrypts its messages using AES128 ECB, which
requires some additional dependencies. I was hoping that rustls would
expose what I needed, but alas no (OpenSSL has spoiled me as a does
everything cypto library).

In addition to needing aes and ecb, I also needed:
* rand (although I suspect could get away without this)
* sha2 (for sha256 hashing)

I all 4 libraries are either MIT, Apache 2 or dual license.
  • Loading branch information
blackspherefollower authored and qdot committed Jan 21, 2024
1 parent 088a0d4 commit ed48ca7
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 0 deletions.
4 changes: 4 additions & 0 deletions buttplug/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
34 changes: 34 additions & 0 deletions buttplug/buttplug-device-config/buttplug-device-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
}
}
}
}
17 changes: 17 additions & 0 deletions buttplug/buttplug-device-config/buttplug-device-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

5 changes: 5 additions & 0 deletions buttplug/src/server/device/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -467,6 +468,10 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
&mut map,
tcode_v03::setup::TCodeV03IdentifierFactory::default(),
);
add_to_protocol_map(
&mut map,
vibcrafter::setup::VibCrafterIdentifierFactory::default(),
);
add_to_protocol_map(
&mut map,
vibratissimo::setup::VibratissimoIdentifierFactory::default(),
Expand Down
166 changes: 166 additions & 0 deletions buttplug/src/server/device/protocol/vibcrafter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Buttplug Rust Source Code File - See https://buttplug.io for more info.
//
// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
//
// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
// for full license information.

use crate::core::message::ActuatorType;
use crate::server::device::configuration::ProtocolDeviceAttributes;
use crate::server::device::hardware::{HardwareEvent, HardwareSubscribeCmd};
use crate::{
core::{errors::ButtplugDeviceError, message::Endpoint},
server::device::{
configuration::ProtocolAttributesType,
hardware::{Hardware, HardwareCommand, HardwareWriteCmd},
protocol::{
generic_protocol_initializer_setup,
ProtocolHandler,
ProtocolIdentifier,
ProtocolInitializer,
},
ServerDeviceIdentifier,
},
};
use aes::Aes128;
use async_trait::async_trait;
use ecb::cipher::block_padding::Pkcs7;
use ecb::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit};
use std::sync::Arc;

use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use regex::Regex;
use sha2::{Digest, Sha256};

type Aes128EcbEnc = ecb::Encryptor<Aes128>;
type Aes128EcbDec = ecb::Decryptor<Aes128>;

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<u8> {
let enc = Aes128EcbEnc::new(&VIBCRAFTER_KEY.into());
let res = enc.encrypt_padded_vec_mut::<Pkcs7>(command.as_bytes());

info!("Encoded {} to {:?}", command, res);
return res;
}

fn decrypt(data: Vec<u8>) -> String {
let dec = Aes128EcbDec::new(&VIBCRAFTER_KEY.into());
let res = String::from_utf8(dec.decrypt_padded_vec_mut::<Pkcs7>(&data).unwrap()).unwrap();

info!("Decoded {} from {:?}", res, data);
return res;
}

#[async_trait]
impl ProtocolInitializer for VibCrafterInitializer {
async fn initialize(
&mut self,
hardware: Arc<Hardware>,
_: &ProtocolDeviceAttributes,
) -> Result<Arc<dyn ProtocolHandler>, 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::<String>();
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<Vec<HardwareCommand>, 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()])
}
}

0 comments on commit ed48ca7

Please sign in to comment.