Buttplug sex toy control library
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
8use super::{lelo_harmony::LeloHarmony, lelof1s::LeloF1s};
9use crate::device::{
10 hardware::{
11 Hardware,
12 HardwareEvent,
13 HardwareSubscribeCmd,
14 HardwareUnsubscribeCmd,
15 HardwareWriteCmd,
16 },
17 protocol::{
18 ProtocolHandler,
19 ProtocolIdentifier,
20 ProtocolInitializer,
21 generic_protocol_initializer_setup,
22 },
23};
24use async_trait::async_trait;
25use buttplug_core::errors::ButtplugDeviceError;
26use buttplug_server_device_config::Endpoint;
27use buttplug_server_device_config::{
28 ProtocolCommunicationSpecifier,
29 ServerDeviceDefinition,
30 UserDeviceIdentifier,
31};
32use std::sync::Arc;
33use uuid::{Uuid, uuid};
34
35const LELO_F1S_V2_PROTOCOL_UUID: Uuid = uuid!("85c59ac5-89ee-4549-8958-ce5449226a5c");
36generic_protocol_initializer_setup!(LeloF1sV2, "lelo-f1sv2");
37
38#[derive(Default)]
39pub struct LeloF1sV2Initializer {}
40
41#[async_trait]
42impl ProtocolInitializer for LeloF1sV2Initializer {
43 async fn initialize(
44 &mut self,
45 hardware: Arc<Hardware>,
46 _: &ServerDeviceDefinition,
47 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
48 let use_harmony = !hardware.endpoints().contains(&Endpoint::Whitelist);
49 let sec_endpoint = if use_harmony {
50 Endpoint::Generic0
51 } else {
52 Endpoint::Whitelist
53 };
54
55 // The Lelo F1s V2 has a very specific pairing flow:
56 // * First the device is turned on in BLE mode (long press)
57 // * Then the security endpoint (Whitelist) needs to be read (which we can do via subscribe)
58 // * If it returns 0x00,00,00,00,00,00,00,00 the connection isn't not authorised
59 // * To authorize, the password must be writen to the characteristic.
60 // * If the password is unknown (buttplug lacks a storage mechanism right now), the power button
61 // must be pressed to send the password
62 // * The password must not be sent whilst subscribed to the endpoint
63 // * Once the password has been sent, the endpoint can be read for status again
64 // * If it returns 0x00,00,00,00,00,00,00,00 the connection is authorised
65 let mut event_receiver = hardware.event_stream();
66 hardware
67 .subscribe(&HardwareSubscribeCmd::new(
68 LELO_F1S_V2_PROTOCOL_UUID,
69 sec_endpoint,
70 ))
71 .await?;
72 let noauth: Vec<u8> = vec![0; 8];
73 let authed: Vec<u8> = vec![1, 0, 0, 0, 0, 0, 0, 0];
74
75 loop {
76 let event = event_receiver.recv().await;
77 if let Ok(HardwareEvent::Notification(_, _, n)) = event {
78 if n.eq(&noauth) {
79 info!(
80 "Lelo F1s V2 isn't authorised: Tap the device's power button to complete connection."
81 )
82 } else if n.eq(&authed) {
83 debug!("Lelo F1s V2 is authorised!");
84 if use_harmony {
85 return Ok(Arc::new(LeloHarmony::default()));
86 } else {
87 return Ok(Arc::new(LeloF1s::new(true)));
88 }
89 } else {
90 debug!("Lelo F1s V2 gave us a password: {:?}", n);
91 // Can't send whilst subscribed
92 hardware
93 .unsubscribe(&HardwareUnsubscribeCmd::new(
94 LELO_F1S_V2_PROTOCOL_UUID,
95 sec_endpoint,
96 ))
97 .await?;
98 // Send with response
99 hardware
100 .write_value(&HardwareWriteCmd::new(
101 &[LELO_F1S_V2_PROTOCOL_UUID],
102 sec_endpoint,
103 n,
104 true,
105 ))
106 .await?;
107 // Get back to the loop
108 hardware
109 .subscribe(&HardwareSubscribeCmd::new(
110 LELO_F1S_V2_PROTOCOL_UUID,
111 sec_endpoint,
112 ))
113 .await?;
114 }
115 } else {
116 return Err(ButtplugDeviceError::ProtocolSpecificError(
117 "LeloF1sV2".to_owned(),
118 "Lelo F1s V2 didn't provided valid security handshake".to_owned(),
119 ));
120 }
121 }
122 }
123}