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}