Skip to content

Commit edf6c90

Browse files
blackspherefollowerqdot
authored andcommitted
feat: Fluffer support
This change adds support for fluffer based MotorBunny devices that have been updated to the latest firmware.
1 parent f411a3a commit edf6c90

File tree

7 files changed

+523
-0
lines changed

7 files changed

+523
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2+
//
3+
// Copyright 2016-2024 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::{ProtocolHandler, ProtocolIdentifier, ProtocolInitializer, ProtocolKeepaliveStrategy},
11+
};
12+
use aes::Aes128;
13+
use async_trait::async_trait;
14+
use buttplug_core::errors::ButtplugDeviceError;
15+
use buttplug_server_device_config::{
16+
Endpoint,
17+
ProtocolCommunicationSpecifier,
18+
ServerDeviceDefinition,
19+
UserDeviceIdentifier,
20+
};
21+
use ecb::cipher::block_padding::Pkcs7;
22+
use ecb::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit};
23+
use rand::prelude::*;
24+
use sha2::Digest;
25+
use std::sync::{
26+
Arc,
27+
atomic::{AtomicU8, Ordering},
28+
};
29+
use uuid::{Uuid, uuid};
30+
31+
type Aes128EcbEnc = ecb::Encryptor<Aes128>;
32+
type Aes128EcbDec = ecb::Decryptor<Aes128>;
33+
34+
const FLUFFER_PROTOCOL_UUID: Uuid = uuid!("d3721a71-a81d-461a-b404-8599ce50c00b");
35+
const FLUFFER_KEY: [u8; 16] = *b"jdk#Flu%y6fer32f";
36+
37+
pub mod setup {
38+
use crate::device::protocol::{ProtocolIdentifier, ProtocolIdentifierFactory};
39+
#[derive(Default)]
40+
pub struct FlufferIdentifierFactory {}
41+
42+
impl ProtocolIdentifierFactory for FlufferIdentifierFactory {
43+
fn identifier(&self) -> &str {
44+
"fluffer"
45+
}
46+
47+
fn create(&self) -> Box<dyn ProtocolIdentifier> {
48+
Box::new(super::FlufferIdentifier::default())
49+
}
50+
}
51+
}
52+
53+
#[derive(Default)]
54+
pub struct FlufferIdentifier {}
55+
56+
#[async_trait]
57+
impl ProtocolIdentifier for FlufferIdentifier {
58+
async fn identify(
59+
&mut self,
60+
hardware: Arc<Hardware>,
61+
proto: ProtocolCommunicationSpecifier,
62+
) -> Result<(UserDeviceIdentifier, Box<dyn ProtocolInitializer>), ButtplugDeviceError> {
63+
let mut data: Vec<u8> = vec![];
64+
if let ProtocolCommunicationSpecifier::BluetoothLE(bt_proto) = proto {
65+
data = bt_proto
66+
.manufacturer_data()
67+
.iter()
68+
.find(|d| d.company().to_le_bytes().eq(&[0x4au8, 0x68]))
69+
.map(|d| {
70+
let mut advertisement = vec![];
71+
advertisement.extend(d.company().to_le_bytes());
72+
advertisement.extend(d.data().clone().unwrap_or(vec![]).as_slice());
73+
advertisement
74+
})
75+
.unwrap_or(vec![])
76+
}
77+
if data.is_empty() {
78+
warn!(
79+
"Failed to get manufacturer data for Fluffer device: {}",
80+
hardware.name()
81+
);
82+
}
83+
Ok((
84+
UserDeviceIdentifier::new(
85+
hardware.address(),
86+
"fluffer",
87+
&Some(hardware.name().to_owned()),
88+
),
89+
Box::new(FlufferInitializer::new(data)),
90+
))
91+
}
92+
}
93+
94+
#[derive(Default)]
95+
pub struct FlufferInitializer {
96+
advertisment_data: Vec<u8>,
97+
}
98+
99+
impl FlufferInitializer {
100+
fn new(advertisment_data: Vec<u8>) -> Self {
101+
Self { advertisment_data }
102+
}
103+
}
104+
105+
fn encrypt(data: Vec<u8>) -> Vec<u8> {
106+
let enc = Aes128EcbEnc::new(&FLUFFER_KEY.into());
107+
let res = enc.encrypt_padded_vec_mut::<Pkcs7>(data.as_slice());
108+
109+
info!("Encoded {:?} to {:?}", data, res);
110+
res
111+
}
112+
113+
fn decrypt(data: Vec<u8>) -> Vec<u8> {
114+
let dec = Aes128EcbDec::new(&FLUFFER_KEY.into());
115+
let res = dec.decrypt_padded_vec_mut::<Pkcs7>(&data).unwrap();
116+
117+
info!("Decoded {:?} from {:?}", res, data);
118+
res
119+
}
120+
121+
fn extract_adv(adv_bytes: &[u8]) -> Vec<u8> {
122+
let key = adv_bytes[2] ^ adv_bytes[1];
123+
let mut out = vec![];
124+
let mut src_offset = 0;
125+
126+
while out.len() < 4 && (3 + src_offset) < adv_bytes.len() {
127+
let mut b = adv_bytes[3 + src_offset];
128+
if b != 0 && b != key {
129+
b ^= key;
130+
}
131+
out.push(b);
132+
src_offset += 1;
133+
}
134+
out
135+
}
136+
137+
#[async_trait]
138+
impl ProtocolInitializer for FlufferInitializer {
139+
async fn initialize(
140+
&mut self,
141+
hardware: Arc<Hardware>,
142+
_: &ServerDeviceDefinition,
143+
) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
144+
let mut event_receiver = hardware.event_stream();
145+
hardware
146+
.subscribe(&HardwareSubscribeCmd::new(
147+
FLUFFER_PROTOCOL_UUID,
148+
Endpoint::Rx,
149+
))
150+
.await?;
151+
152+
if self.advertisment_data.len() > 0 {
153+
// custom logic to compute 4 bytes from the advBytes
154+
let adv4bytes = extract_adv(self.advertisment_data.as_slice());
155+
156+
// random 4 bytes
157+
let rand: [u8; 4] = [random(), random(), random(), random()];
158+
159+
// sha256 of the 4 random bytes + the 4 advertisement bytes
160+
let mut hash = sha2::Sha256::new();
161+
Digest::update(&mut hash, rand);
162+
Digest::update(&mut hash, adv4bytes);
163+
let digest = hash.finalize().to_vec();
164+
165+
// first 4 bytes from sha256'd data (official app picks a random 4 adjacent bytes)
166+
let pattern = digest.get(0..4).expect("SHA256 has many bytes...");
167+
168+
// build full command of [0xA5, 0x0a, 0x08, ...rand 4 bytes, ...first 4 bytes from sha256'd data]
169+
let mut auth_data = vec![0xa5, 0x01, 0x08];
170+
auth_data.extend(rand);
171+
auth_data.extend(pattern);
172+
173+
hardware
174+
.write_value(&HardwareWriteCmd::new(
175+
&[FLUFFER_PROTOCOL_UUID],
176+
Endpoint::Tx,
177+
encrypt(auth_data),
178+
false,
179+
))
180+
.await?;
181+
182+
loop {
183+
let event = event_receiver.recv().await;
184+
return if let Ok(HardwareEvent::Notification(_, _, n)) = event {
185+
let decoded = decrypt(n);
186+
if decoded.eq(&vec![0xa5, 0x01, 0x01, 0x00]) {
187+
debug!("Fluffer authenticated!");
188+
189+
hardware
190+
.write_value(&HardwareWriteCmd::new(
191+
&[FLUFFER_PROTOCOL_UUID],
192+
Endpoint::Tx,
193+
encrypt(vec![0x82, 0x0E, 0x02, 0x00, 0x01]),
194+
false,
195+
))
196+
.await?;
197+
198+
Ok(Arc::new(Fluffer::default()))
199+
} else {
200+
Err(ButtplugDeviceError::ProtocolSpecificError(
201+
"Fluffer".to_owned(),
202+
"Fluffer didn't provide a valid security handshake".to_owned(),
203+
))
204+
}
205+
} else {
206+
Err(ButtplugDeviceError::ProtocolSpecificError(
207+
"Fluffer".to_owned(),
208+
"Fluffer didn't provide a valid security handshake".to_owned(),
209+
))
210+
};
211+
}
212+
} else {
213+
Ok(Arc::new(Fluffer::default()))
214+
}
215+
}
216+
}
217+
218+
#[derive(Default)]
219+
pub struct Fluffer {
220+
speeds: [AtomicU8; 2],
221+
}
222+
223+
impl Fluffer {
224+
fn send_command(&self) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
225+
let s1 = self.speeds[0].fetch_add(0, Ordering::Relaxed);
226+
let s2 = self.speeds[1].fetch_add(0, Ordering::Relaxed);
227+
Ok(vec![
228+
HardwareWriteCmd::new(
229+
&[FLUFFER_PROTOCOL_UUID],
230+
Endpoint::Tx,
231+
encrypt(vec![0x82, 0x0F, 0x05, 0x00, s1, s2, 0x00, 0x00]),
232+
false,
233+
)
234+
.into(),
235+
])
236+
}
237+
}
238+
impl ProtocolHandler for Fluffer {
239+
fn keepalive_strategy(&self) -> ProtocolKeepaliveStrategy {
240+
ProtocolKeepaliveStrategy::HardwareRequiredRepeatPacketStrategy(HardwareWriteCmd::new(
241+
&[FLUFFER_PROTOCOL_UUID],
242+
Endpoint::Tx,
243+
encrypt(vec![0x80, 0x02, 0x00]),
244+
false,
245+
))
246+
}
247+
fn handle_output_vibrate_cmd(
248+
&self,
249+
feature_index: u32,
250+
_feature_id: uuid::Uuid,
251+
speed: u32,
252+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
253+
self.speeds[feature_index as usize].store(speed as u8, Ordering::Relaxed);
254+
self.send_command()
255+
}
256+
257+
fn handle_output_rotate_cmd(
258+
&self,
259+
feature_index: u32,
260+
_feature_id: uuid::Uuid,
261+
speed: i32,
262+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
263+
self.speeds[feature_index as usize].store(
264+
if speed < 0 {
265+
speed.abs() + 100
266+
} else {
267+
speed.abs()
268+
} as u8,
269+
Ordering::Relaxed,
270+
);
271+
self.send_command()
272+
}
273+
274+
fn handle_output_oscillate_cmd(
275+
&self,
276+
feature_index: u32,
277+
_feature_id: uuid::Uuid,
278+
speed: u32,
279+
) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
280+
self.speeds[feature_index as usize].store(speed as u8, Ordering::Relaxed);
281+
self.send_command()
282+
}
283+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod cupido;
1919
pub mod deepsire;
2020
pub mod feelingso;
2121
pub mod fleshy_thrust;
22+
pub mod fluffer;
2223
pub mod foreo;
2324
pub mod fox;
2425
pub mod fredorch;
@@ -190,6 +191,10 @@ pub fn get_default_protocol_map() -> HashMap<String, Arc<dyn ProtocolIdentifierF
190191
&mut map,
191192
fleshy_thrust::setup::FleshyThrustIdentifierFactory::default(),
192193
);
194+
add_to_protocol_map(
195+
&mut map,
196+
fluffer::setup::FlufferIdentifierFactory::default(),
197+
);
193198
add_to_protocol_map(&mut map, foreo::setup::ForeoIdentifierFactory::default());
194199
add_to_protocol_map(&mut map, fox::setup::FoxIdentifierFactory::default());
195200
add_to_protocol_map(

0 commit comments

Comments
 (0)