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 self::handyplug::Ping;
9
10use crate::device::{
11 hardware::{Hardware, HardwareCommand, HardwareWriteCmd},
12 protocol::{
13 ProtocolHandler,
14 ProtocolIdentifier,
15 ProtocolInitializer,
16 ProtocolKeepaliveStrategy,
17 generic_protocol_initializer_setup,
18 },
19};
20use async_trait::async_trait;
21use buttplug_core::errors::ButtplugDeviceError;
22use buttplug_server_device_config::Endpoint;
23use buttplug_server_device_config::{
24 ProtocolCommunicationSpecifier,
25 ServerDeviceDefinition,
26 UserDeviceIdentifier,
27};
28use prost::Message;
29use std::sync::Arc;
30use uuid::{Uuid, uuid};
31
32mod protocomm {
33 include!("./protocomm.rs");
34}
35
36mod handyplug {
37 include!("./handyplug.rs");
38}
39
40const THEHANDY_PROTOCOL_UUID: Uuid = uuid!("e7c3ba93-ddbf-4f38-a960-30a332739d02");
41generic_protocol_initializer_setup!(TheHandy, "thehandy");
42
43#[derive(Default)]
44pub struct TheHandyInitializer {}
45
46#[async_trait]
47impl ProtocolInitializer for TheHandyInitializer {
48 async fn initialize(
49 &mut self,
50 _hardware: Arc<Hardware>,
51 _: &ServerDeviceDefinition,
52 ) -> Result<Arc<dyn ProtocolHandler>, ButtplugDeviceError> {
53 // Ok, somehow this whole function has been basically a no-op. The read/write lines never had an
54 // await on them, so they were never run. But only now, in Rust 1.75/Buttplug 7.1.15, have we
55 // gotten a complaint from the compiler. Going to comment this out for now and see what happens.
56 // If we don't get any complaints, I'm going to have to rewrite all of my snark here. :(
57
58 // Ok, here we go. This is an overly-complex nightmare but apparently "protocomm makes the
59 // firmware easier".
60 //
61 // This code is mostly my translation of the Handy Python POC. It leaves out a lot of stuff
62 // that doesn't seem needed (ping messages, the whole RequestServerInfo flow, etc...) If they
63 // ever change anything, I quit.
64 //
65 // If you are a sex toy manufacturer reading this code: Please, talk to me before implementing
66 // your protocol. Buttplug is not made to be a hardware/firmware protocol, and you will regret
67 // trying to make it such.
68
69 // First we need to set up a session with The Handy. This will require sending the "security
70 // initializer" to basically say we're sending plaintext. Due to pb3 making everything
71 // optional, we have some Option<T> wrappers here.
72
73 // let session_req = protocomm::SessionData {
74 // sec_ver: protocomm::SecSchemeVersion::SecScheme0 as i32,
75 // proto: Some(protocomm::session_data::Proto::Sec0(
76 // protocomm::Sec0Payload {
77 // msg: protocomm::Sec0MsgType::S0SessionCommand as i32,
78 // payload: Some(protocomm::sec0_payload::Payload::Sc(
79 // protocomm::S0SessionCmd {},
80 // )),
81 // },
82 // )),
83 // };
84
85 // We need to shove this at what we're calling the "firmware" endpoint but is actually the
86 // "prov-session" characteristic. These names are stored in characteristic descriptors, which
87 // isn't super common on sex toys (with exceptions for things that have a lot of sensors, like
88 // the Lelo F1s).
89 //
90 // I don't have to do characteristic descriptor lookups for the other 140+ pieces of hardware
91 // this library supports so I'm damn well not doing it now. YOLO'ing hardcoded values from the
92 // device config.
93 //
94 // If they ever change this, I quit (or will just update the device config).
95
96 // hardware.write_value(&HardwareWriteCmd::new(Endpoint::Firmware, sec_buf, false));
97 // hardware.read_value(&HardwareReadCmd::new(Endpoint::Firmware, 100, 500));
98
99 // At this point, the "handyplug" protocol does actually have both RequestServerInfo and Ping
100 // messages that it can use. However, having removed these and still tried to run the system,
101 // it seems fine. I've omitted those for the moment, and will readd the complexity once it
102 // does not seem needless.
103 //
104 // We have no device name updates here, so just return a device.
105 Ok(Arc::new(TheHandy::default()))
106 }
107}
108
109#[derive(Default)]
110pub struct TheHandy {}
111
112impl ProtocolHandler for TheHandy {
113 fn keepalive_strategy(&self) -> ProtocolKeepaliveStrategy {
114 let ping_payload = handyplug::Payload {
115 messages: vec![handyplug::Message {
116 message: Some(handyplug::message::Message::Ping(Ping { id: 999 })),
117 }],
118 };
119 let mut ping_buf = vec![];
120 ping_payload
121 .encode(&mut ping_buf)
122 .expect("Infallible encode.");
123
124 ProtocolKeepaliveStrategy::HardwareRequiredRepeatPacketStrategy(HardwareWriteCmd::new(
125 &[THEHANDY_PROTOCOL_UUID],
126 Endpoint::Tx,
127 ping_buf,
128 true,
129 ))
130 }
131
132 fn handle_position_with_duration_cmd(
133 &self,
134 _feature_index: u32,
135 _feature_id: Uuid,
136 position: u32,
137 duration: u32,
138 ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
139 // What is "How not to implement a command structure for your device that does one thing", Alex?
140
141 let linear = handyplug::LinearCmd {
142 // You know when message IDs are important? When you have a protocol that handles multiple
143 // asynchronous commands. You know what doesn't handle multiple asynchronous commands? The
144 // handyplug protocol.
145 //
146 // Do you know where you'd pack those? In the top level container, as they should then be
147 // separate from the message context, in order to allow multiple sorters. Do you know what
148 // doesn't need multiple sorters? The handyplug protocol.
149 //
150 // Please do not cargo cult protocols.
151 id: 2,
152 // You know when multiple device indicies are important? WHEN YOU HAVE MULTIPLE DEVICE
153 // CONNECTI... oh fuck it. I am so tired. I am going to bed.
154 device_index: 0,
155 // AND I'M BACK AND WELL RESTED. You know when multiple axes are important? When you have to
156 // support arbitrary devices with multiple axes. You know what device doesn't have multiple
157 // axes?
158 //
159 // Guess.
160 //
161 // I'll wait.
162 //
163 // The handy. It's the handy.
164 vectors: vec![handyplug::linear_cmd::Vector {
165 index: 0,
166 position: position as f64 / 100f64,
167 duration,
168 }],
169 };
170 let linear_payload = handyplug::Payload {
171 messages: vec![handyplug::Message {
172 message: Some(handyplug::message::Message::LinearCmd(linear)),
173 }],
174 };
175 let mut linear_buf = vec![];
176 linear_payload
177 .encode(&mut linear_buf)
178 .expect("Infallible encode.");
179 Ok(vec![
180 HardwareWriteCmd::new(&[THEHANDY_PROTOCOL_UUID], Endpoint::Tx, linear_buf, true).into(),
181 ])
182 }
183}