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::{
9 ButtplugServerError,
10 device::{ServerDeviceManager, ServerDeviceManagerBuilder},
11 ping_timer::PingTimer,
12 server::ButtplugServer,
13};
14use buttplug_core::{
15 errors::*,
16 message::{self, ButtplugServerMessageV4},
17 util::async_manager,
18};
19use buttplug_server_device_config::DeviceConfigurationManagerBuilder;
20use std::sync::{
21 Arc,
22 atomic::{AtomicBool, Ordering},
23};
24use tokio::sync::broadcast;
25use tracing_futures::Instrument;
26
27/// Configures and creates [ButtplugServer] instances.
28pub struct ButtplugServerBuilder {
29 /// Name of the server, will be sent to the client as part of the [initial connection
30 /// handshake](https://buttplug-spec.docs.buttplug.io/architecture.html#stages).
31 name: String,
32 /// Maximum time system will live without receiving a Ping message before disconnecting. If None,
33 /// ping timer does not run.
34 max_ping_time: Option<u32>,
35 /// Device manager builder for the server
36 device_manager: Arc<ServerDeviceManager>,
37}
38
39impl Default for ButtplugServerBuilder {
40 fn default() -> Self {
41 Self {
42 name: "Buttplug Server".to_owned(),
43 max_ping_time: None,
44 device_manager: Arc::new(
45 ServerDeviceManagerBuilder::new(
46 DeviceConfigurationManagerBuilder::default()
47 .finish()
48 .unwrap(),
49 )
50 .finish()
51 .unwrap(),
52 ),
53 }
54 }
55}
56
57impl ButtplugServerBuilder {
58 pub fn new(device_manager: ServerDeviceManager) -> Self {
59 Self {
60 name: "Buttplug Server".to_owned(),
61 max_ping_time: None,
62 device_manager: Arc::new(device_manager),
63 }
64 }
65
66 pub fn with_shared_device_manager(device_manager: Arc<ServerDeviceManager>) -> Self {
67 Self {
68 name: "Buttplug Server".to_owned(),
69 max_ping_time: None,
70 device_manager,
71 }
72 }
73
74 /// Set the name of the server, which is relayed to the client on connection (mostly for
75 /// confirmation in UI dialogs)
76 pub fn name(&mut self, name: &str) -> &mut Self {
77 self.name = name.to_owned();
78 self
79 }
80
81 /// Set the maximum ping time, in milliseconds, for the server. If the server does not receive a
82 /// [Ping](buttplug_core::messages::Ping) message in this amount of time after the handshake has
83 /// succeeded, the server will automatically disconnect. If this is not called, the ping timer
84 /// will not be activated.
85 ///
86 /// Note that this has nothing to do with communication medium specific pings, like those built
87 /// into the Websocket protocol. This ping is specific to the Buttplug protocol.
88 pub fn max_ping_time(&mut self, ping_time: u32) -> &mut Self {
89 self.max_ping_time = Some(ping_time);
90 self
91 }
92
93 /// Try to build a [ButtplugServer] using the parameters given.
94 pub fn finish(&self) -> Result<ButtplugServer, ButtplugServerError> {
95 // Create the server
96 debug!("Creating server '{}'", self.name);
97
98 // Set up our channels to different parts of the system.
99 let (output_sender, _) = broadcast::channel(256);
100 let output_sender_clone = output_sender.clone();
101
102 let connected = Arc::new(AtomicBool::new(false));
103 let connected_clone = connected.clone();
104
105 // TODO this should use a cancellation token instead of passing around the timer itself.
106 let ping_time = self.max_ping_time.unwrap_or(0);
107 let ping_timer = Arc::new(PingTimer::new(ping_time));
108 let ping_timeout_notifier = ping_timer.ping_timeout_waiter();
109
110 // Spawn the ping timer task, assuming the ping time is > 0.
111 if ping_time > 0 {
112 let device_manager_clone = self.device_manager.clone();
113 async_manager::spawn(
114 async move {
115 // This will only exit if we've pinged out.
116 ping_timeout_notifier.await;
117 error!("Ping out signal received, stopping server");
118 connected_clone.store(false, Ordering::Relaxed);
119 async_manager::spawn(async move {
120 if let Err(e) = device_manager_clone.stop_all_devices().await {
121 error!("Could not stop devices on ping timeout: {:?}", e);
122 }
123 });
124 // TODO Should the event sender return a result instead of an error message?
125 if output_sender_clone
126 .send(ButtplugServerMessageV4::Error(message::ErrorV0::from(
127 ButtplugError::from(ButtplugPingError::PingedOut),
128 )))
129 .is_err()
130 {
131 error!("Server disappeared, cannot update about ping out.");
132 };
133 }
134 .instrument(tracing::info_span!("Buttplug Server Ping Timeout Task")),
135 );
136 }
137
138 // Assuming everything passed, return the server.
139 Ok(ButtplugServer::new(
140 &self.name,
141 ping_time,
142 ping_timer,
143 self.device_manager.clone(),
144 connected,
145 output_sender,
146 ))
147 }
148}