Skip to content

Commit 2166a5c

Browse files
committed
Add vibio device protocol support
1 parent 64c6018 commit 2166a5c

5 files changed

Lines changed: 385 additions & 3 deletions

File tree

crates/buttplug_server/src/device/protocol_impl/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ pub mod tryfun;
112112
pub mod tryfun_blackhole;
113113
pub mod tryfun_meta2;
114114
pub mod vibcrafter;
115+
pub mod vibio;
115116
pub mod vibratissimo;
116117
pub mod vorze_sa;
117118
pub mod wetoy;
@@ -540,6 +541,10 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
540541
&mut map,
541542
vibcrafter::setup::VibCrafterIdentifierFactory::default(),
542543
);
544+
add_to_protocol_map(
545+
&mut map,
546+
vibio::setup::VibioIdentifierFactory::default(),
547+
);
543548
add_to_protocol_map(
544549
&mut map,
545550
vibratissimo::setup::VibratissimoIdentifierFactory::default(),
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2+
//
3+
// Copyright 2016-2026 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+
8+
use crate::device::{
9+
hardware::{Hardware, HardwareCommand, HardwareEvent, HardwareSubscribeCmd, HardwareWriteCmd},
10+
protocol::{
11+
ProtocolHandler,
12+
ProtocolIdentifier,
13+
ProtocolInitializer,
14+
generic_protocol_initializer_setup,
15+
},
16+
};
17+
use aes::Aes128;
18+
use async_trait::async_trait;
19+
use buttplug_core::errors::ButtplugDeviceError;
20+
use buttplug_server_device_config::Endpoint;
21+
use buttplug_server_device_config::{
22+
ProtocolCommunicationSpecifier,
23+
ServerDeviceDefinition,
24+
UserDeviceIdentifier,
25+
};
26+
use ecb::cipher::block_padding::Pkcs7;
27+
use ecb::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit};
28+
use std::sync::{
29+
Arc,
30+
atomic::{AtomicU8, Ordering},
31+
};
32+
use uuid::{Uuid, uuid};
33+
34+
use rand::distr::Alphanumeric;
35+
use rand::RngExt;
36+
use regex::Regex;
37+
use sha2::{Digest, Sha256};
38+
39+
type Aes128EcbEnc = ecb::Encryptor<Aes128>;
40+
type Aes128EcbDec = ecb::Decryptor<Aes128>;
41+
42+
const VIBIO_PROTOCOL_UUID: Uuid = uuid!("b8c76c9e-cb42-4a94-99f4-7c2a8e5d3b2a");
43+
const VIBIO_KEY: [u8; 16] = *b"jdk#vib%y5fir21a";
44+
45+
generic_protocol_initializer_setup!(Vibio, "vibio");
46+
47+
#[derive(Default)]
48+
pub struct VibioInitializer {}
49+
50+
fn encrypt(command: String) -> Vec<u8> {
51+
let enc = Aes128EcbEnc::new(&VIBIO_KEY.into());
52+
let res = enc.encrypt_padded_vec_mut::<Pkcs7>(command.as_bytes());
53+
54+
info!("Encoded {} to {:?}", command, res);
55+
res
56+
}
57+
58+
fn decrypt(data: Vec<u8>) -> String {
59+
let dec = Aes128EcbDec::new(&VIBIO_KEY.into());
60+
let res = String::from_utf8(dec.decrypt_padded_vec_mut::<Pkcs7>(&data).unwrap()).unwrap();
61+
62+
info!("Decoded {} from {:?}", res, data);
63+
res
64+
}
65+
66+
#[async_trait]
67+
impl ProtocolInitializer for VibioInitializer {
68+
async fn initialize(
69+
&mut self,
70+
hardware: Arc<Hardware>,
71+
_: &ServerDeviceDefinition,
72+
) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
73+
let mut event_receiver = hardware.event_stream();
74+
hardware
75+
.subscribe(&HardwareSubscribeCmd::new(
76+
VIBIO_PROTOCOL_UUID,
77+
Endpoint::Rx,
78+
))
79+
.await?;
80+
81+
let auth_str = rand::rng()
82+
.sample_iter(&Alphanumeric)
83+
.take(8)
84+
.map(char::from)
85+
.collect::<String>();
86+
let auth_msg = format!("Auth:{};", auth_str);
87+
hardware
88+
.write_value(&HardwareWriteCmd::new(
89+
&[VIBIO_PROTOCOL_UUID],
90+
Endpoint::Tx,
91+
encrypt(auth_msg),
92+
false,
93+
))
94+
.await?;
95+
96+
loop {
97+
let event = event_receiver.recv().await;
98+
if let Ok(HardwareEvent::Notification(_, _, n)) = event {
99+
let decoded = decrypt(n);
100+
if decoded.eq("OK;") {
101+
debug!("Vibio authenticated!");
102+
return Ok(Arc::new(Vibio::default()));
103+
}
104+
let challenge = Regex::new(r"^([0-9A-Fa-f]{4}):([^;]+);$")
105+
.expect("This is static and should always compile");
106+
if let Some(parts) = challenge.captures(decoded.as_str()) {
107+
debug!("Vibio challenge {:?}", parts);
108+
if let Some(to_hash) = parts.get(2) {
109+
debug!("Vibio to hash {:?}", to_hash);
110+
let mut sha256 = Sha256::new();
111+
sha256.update(to_hash.as_str().as_bytes());
112+
let result = &sha256.finalize();
113+
114+
let auth_msg = format!("Auth:{:02x}{:02x};", result[0], result[1]);
115+
hardware
116+
.write_value(&HardwareWriteCmd::new(
117+
&[VIBIO_PROTOCOL_UUID],
118+
Endpoint::Tx,
119+
encrypt(auth_msg),
120+
false,
121+
))
122+
.await?;
123+
} else {
124+
return Err(ButtplugDeviceError::ProtocolSpecificError(
125+
"Vibio".to_owned(),
126+
"Vibio didn't provide a valid security handshake".to_owned(),
127+
));
128+
}
129+
} else {
130+
return Err(ButtplugDeviceError::ProtocolSpecificError(
131+
"Vibio".to_owned(),
132+
"Vibio didn't provide a valid security handshake".to_owned(),
133+
));
134+
}
135+
} else {
136+
return Err(ButtplugDeviceError::ProtocolSpecificError(
137+
"Vibio".to_owned(),
138+
"Vibio didn't provide a valid security handshake".to_owned(),
139+
));
140+
}
141+
}
142+
}
143+
}
144+
145+
#[derive(Default)]
146+
pub struct Vibio {
147+
speeds: [AtomicU8; 2],
148+
}
149+
150+
impl ProtocolHandler for Vibio {
151+
fn handle_output_vibrate_cmd(
152+
&self,
153+
feature_index: u32,
154+
feature_id: uuid::Uuid,
155+
speed: u32,
156+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
157+
self.speeds[feature_index as usize].store(speed as u8, Ordering::Relaxed);
158+
159+
Ok(vec![
160+
HardwareWriteCmd::new(
161+
&[feature_id],
162+
Endpoint::Tx,
163+
encrypt(format!(
164+
"MtInt:{:02}{:02};",
165+
self.speeds[0].load(Ordering::Relaxed),
166+
self.speeds[1].load(Ordering::Relaxed)
167+
)),
168+
false,
169+
)
170+
.into(),
171+
])
172+
}
173+
}

crates/buttplug_server_device_config/build-config/buttplug-device-config-v4.json

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"version": {
33
"major": 4,
4-
"minor": 178
4+
"minor": 180
55
},
66
"protocols": {
77
"activejoy": {
@@ -21773,6 +21773,140 @@
2177321773
"name": "VibCrafter Device"
2177421774
}
2177521775
},
21776+
"vibio": {
21777+
"communication": [
21778+
{
21779+
"btle": {
21780+
"names": [
21781+
"Clara_Vibio",
21782+
"Dodson_Vibio",
21783+
"Elle_Vibio",
21784+
"Frida_Vibio",
21785+
"Rivera_Vibio"
21786+
],
21787+
"services": {
21788+
"53300021-0050-4bd4-bbe5-a6920e4c5663": {
21789+
"rx": "53300023-0050-4bd4-bbe5-a6920e4c5663",
21790+
"tx": "53300022-0050-4bd4-bbe5-a6920e4c5663"
21791+
}
21792+
}
21793+
}
21794+
}
21795+
],
21796+
"configurations": [
21797+
{
21798+
"features": [
21799+
{
21800+
"id": "343a8e18-b76c-4482-b048-32d762bf87c9",
21801+
"index": 0,
21802+
"output": {
21803+
"vibrate": {
21804+
"value": [
21805+
0,
21806+
99
21807+
]
21808+
}
21809+
}
21810+
}
21811+
],
21812+
"id": "b55fef0e-baa3-44d0-9545-a4b7b0298515",
21813+
"identifier": [
21814+
"Clara_Vibio"
21815+
],
21816+
"name": "Vibio Clara"
21817+
},
21818+
{
21819+
"features": [
21820+
{
21821+
"id": "343a8e18-b76c-4482-b048-32d762bf87c9",
21822+
"index": 0,
21823+
"output": {
21824+
"vibrate": {
21825+
"value": [
21826+
0,
21827+
99
21828+
]
21829+
}
21830+
}
21831+
}
21832+
],
21833+
"id": "c66fef0e-cbb4-44d0-9545-a4b7b0298516",
21834+
"identifier": [
21835+
"Dodson_Vibio"
21836+
],
21837+
"name": "Vibio Dodson"
21838+
},
21839+
{
21840+
"id": "d77fef0e-dcc5-44d0-9545-a4b7b0298517",
21841+
"identifier": [
21842+
"Rivera_Vibio"
21843+
],
21844+
"name": "Vibio Rivera"
21845+
},
21846+
{
21847+
"id": "e88fef0e-edd6-44d0-9545-a4b7b0298518",
21848+
"identifier": [
21849+
"Elle_Vibio"
21850+
],
21851+
"name": "Vibio Elle"
21852+
},
21853+
{
21854+
"id": "f99fef0e-fee7-44d0-9545-a4b7b0298519",
21855+
"identifier": [
21856+
"Frida_Vibio"
21857+
],
21858+
"name": "Vibio Frida"
21859+
}
21860+
],
21861+
"defaults": {
21862+
"features": [
21863+
{
21864+
"id": "343a8e18-b76c-4482-b048-32d762bf87c9",
21865+
"index": 0,
21866+
"output": {
21867+
"vibrate": {
21868+
"value": [
21869+
0,
21870+
99
21871+
]
21872+
}
21873+
}
21874+
},
21875+
{
21876+
"id": "d92a031e-bd0d-4815-a0bd-6c59566dcce2",
21877+
"index": 1,
21878+
"output": {
21879+
"vibrate": {
21880+
"value": [
21881+
0,
21882+
99
21883+
]
21884+
}
21885+
}
21886+
},
21887+
{
21888+
"description": "Battery Level",
21889+
"id": "e1a2b3c4-d5e6-f7a0-b1c2-d3e4f5a6b7c8",
21890+
"index": 2,
21891+
"input": {
21892+
"battery": {
21893+
"command": [
21894+
"Read"
21895+
],
21896+
"value": [
21897+
[
21898+
0,
21899+
100
21900+
]
21901+
]
21902+
}
21903+
}
21904+
}
21905+
],
21906+
"id": "a44eef0e-b412-44d0-9545-a4b7b0298514",
21907+
"name": "Vibio Device"
21908+
}
21909+
},
2177621910
"vibratissimo": {
2177721911
"communication": [
2177821912
{
@@ -22472,6 +22606,7 @@
2247222606
"btle": {
2247322607
"names": [
2247422608
"Melt",
22609+
"Melt 2",
2247522610
"Moxie",
2247622611
"Vector",
2247722612
"Wand",
@@ -22510,7 +22645,8 @@
2251022645
],
2251122646
"id": "4f73e55c-bea8-4069-8409-cba30fbbfc81",
2251222647
"identifier": [
22513-
"Melt"
22648+
"Melt",
22649+
"Melt 2"
2251422650
],
2251522651
"name": "WeVibe Melt"
2251622652
},

0 commit comments

Comments
 (0)