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
8mod base;
9mod device;
10mod feature;
11mod protocol;
12mod user;
13
14use base::BaseConfigFile;
15
16use crate::device_config_file::{
17 protocol::ProtocolDefinition,
18 user::{UserConfigDefinition, UserConfigFile, UserDeviceConfigPair},
19};
20
21use super::{BaseDeviceIdentifier, DeviceConfigurationManager, DeviceConfigurationManagerBuilder};
22use buttplug_core::{
23 errors::{ButtplugDeviceError, ButtplugError},
24 util::json::JSONValidator,
25};
26use dashmap::DashMap;
27use getset::CopyGetters;
28use serde::{Deserialize, Serialize};
29use std::fmt::Display;
30
31pub static DEVICE_CONFIGURATION_JSON: &str =
32 include_str!("../../build-config/buttplug-device-config-v4.json");
33static DEVICE_CONFIGURATION_JSON_SCHEMA: &str =
34 include_str!("../../device-config-v4/buttplug-device-config-schema-v4.json");
35
36#[derive(Deserialize, Serialize, Debug, CopyGetters, Clone, Copy)]
37#[getset(get_copy = "pub", get_mut = "pub")]
38struct ConfigVersion {
39 pub major: u32,
40 pub minor: u32,
41}
42
43impl Display for ConfigVersion {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{}.{}", self.major, self.minor)
46 }
47}
48
49trait ConfigVersionGetter {
50 fn version(&self) -> ConfigVersion;
51}
52
53fn get_internal_config_version() -> ConfigVersion {
54 let config: BaseConfigFile = serde_json::from_str(DEVICE_CONFIGURATION_JSON)
55 .expect("If this fails, the whole library goes with it.");
56 config.version()
57}
58
59fn load_protocol_config_from_json<'a, T>(
60 config_str: &'a str,
61 skip_version_check: bool,
62) -> Result<T, ButtplugDeviceError>
63where
64 T: ConfigVersionGetter + Deserialize<'a>,
65{
66 let config_validator = JSONValidator::new(DEVICE_CONFIGURATION_JSON_SCHEMA);
67 match config_validator.validate(config_str) {
68 Ok(_) => match serde_json::from_str::<T>(config_str) {
69 Ok(protocol_config) => {
70 let internal_config_version = get_internal_config_version();
71 if !skip_version_check && protocol_config.version().major != internal_config_version.major {
72 Err(ButtplugDeviceError::DeviceConfigurationError(format!(
73 "Device configuration file major version {} is different than internal major version {}. Cannot load external files that do not have matching major version numbers.",
74 protocol_config.version(),
75 internal_config_version
76 )))
77 } else {
78 Ok(protocol_config)
79 }
80 }
81 Err(err) => Err(ButtplugDeviceError::DeviceConfigurationError(format!(
82 "{err}"
83 ))),
84 },
85 Err(err) => Err(ButtplugDeviceError::DeviceConfigurationError(format!(
86 "{err}"
87 ))),
88 }
89}
90
91fn load_main_config(
92 main_config_str: &Option<String>,
93 skip_version_check: bool,
94) -> Result<DeviceConfigurationManagerBuilder, ButtplugDeviceError> {
95 if main_config_str.is_some() {
96 info!("Loading from custom base device configuration...")
97 } else {
98 info!("Loading from internal base device configuration...")
99 }
100 // Start by loading the main config
101 let main_config = load_protocol_config_from_json::<BaseConfigFile>(
102 main_config_str
103 .as_ref()
104 .unwrap_or(&DEVICE_CONFIGURATION_JSON.to_owned()),
105 skip_version_check,
106 )?;
107
108 info!("Loaded config version {:?}", main_config.version());
109
110 let mut dcm_builder = DeviceConfigurationManagerBuilder::default();
111
112 for (protocol_name, protocol_def) in main_config.protocols().clone().unwrap_or_default() {
113 if let Some(specifiers) = protocol_def.communication() {
114 dcm_builder.communication_specifier(&protocol_name, specifiers);
115 }
116
117 let mut default = None;
118 if let Some(features) = protocol_def.defaults() {
119 default = Some(features.clone());
120 dcm_builder.base_device_definition(
121 &BaseDeviceIdentifier::new_default(&protocol_name),
122 &features.clone().into(),
123 );
124 }
125
126 for config in protocol_def.configurations() {
127 if let Some(idents) = config.identifier() {
128 for config_ident in idents {
129 let ident = BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident);
130 if let Some(d) = &default {
131 dcm_builder
132 .base_device_definition(&ident, &d.update_with_configuration(config.clone()).into());
133 } else {
134 dcm_builder.base_device_definition(&ident, &config.clone().into());
135 }
136 }
137 }
138 }
139 }
140
141 Ok(dcm_builder)
142}
143
144fn load_user_config(
145 user_config_str: &str,
146 skip_version_check: bool,
147 dcm_builder: &mut DeviceConfigurationManagerBuilder,
148) -> Result<(), ButtplugDeviceError> {
149 let base_dcm = dcm_builder.clone().finish().unwrap();
150
151 info!("Loading user configuration from string.");
152 let user_config_file =
153 load_protocol_config_from_json::<UserConfigFile>(user_config_str, skip_version_check)?;
154
155 if user_config_file.user_configs().is_none() {
156 info!("No user configurations provided in user config.");
157 return Ok(());
158 }
159
160 let user_config = user_config_file
161 .user_configs()
162 .clone()
163 .expect("Just checked validity");
164
165 for (protocol_name, protocol_def) in user_config.protocols().clone().unwrap_or_default() {
166 if let Some(specifiers) = protocol_def.communication() {
167 dcm_builder.user_communication_specifier(&protocol_name, specifiers);
168 }
169
170 // Defaults aren't valid in user config files. All we can do is create new configurations with
171 // valid identifiers.
172
173 for config in protocol_def.configurations() {
174 if let Some(idents) = config.identifier() {
175 for config_ident in idents {
176 let ident = BaseDeviceIdentifier::new_with_identifier(&protocol_name, config_ident);
177 dcm_builder.base_device_definition(&ident, &config.clone().into());
178 }
179 }
180 }
181 }
182
183 for user_device_config_pair in user_config
184 .user_device_configs()
185 .clone()
186 .unwrap_or_default()
187 {
188 //let ident = BaseDeviceIdentifier::new(user_device_config_pair.identifier().protocol(), &None);
189 // Use device UUID instead of identifier to match here, otherwise we have to do really weird stuff with identifier hashes.
190 // TODO How do we deal with user configs derived from default here? We don't handle loading this correctly?
191 if let Some(base_config) = base_dcm
192 .base_device_definitions()
193 .iter()
194 .find(|x| x.1.id() == user_device_config_pair.config().base_id())
195 {
196 if let Ok(loaded_user_config) = user_device_config_pair
197 .config()
198 .build_from_base_definition(base_config.1)
199 && let Err(e) = dcm_builder
200 .user_device_definition(user_device_config_pair.identifier(), &loaded_user_config)
201 {
202 error!(
203 "Device definition not valid, skipping:\n{:?}\n{:?}",
204 e, user_config
205 )
206 }
207 } else {
208 error!(
209 "Device identifier {:?} does not have a match base identifier that matches anything in the base config, removing from database.",
210 user_device_config_pair.identifier()
211 );
212 }
213 }
214
215 Ok(())
216}
217
218pub fn load_protocol_configs(
219 main_config_str: &Option<String>,
220 user_config_str: &Option<String>,
221 skip_version_check: bool,
222) -> Result<DeviceConfigurationManagerBuilder, ButtplugDeviceError> {
223 let mut dcm_builder = load_main_config(main_config_str, skip_version_check)?;
224
225 if let Some(config_str) = user_config_str {
226 load_user_config(config_str, skip_version_check, &mut dcm_builder)?;
227 } else {
228 info!("No user configuration provided.");
229 }
230
231 Ok(dcm_builder)
232}
233
234pub fn save_user_config(dcm: &DeviceConfigurationManager) -> Result<String, ButtplugError> {
235 let user_specifiers = dcm.user_communication_specifiers();
236 let user_definitions_vec = dcm
237 .user_device_definitions()
238 .iter()
239 .map(|kv| UserDeviceConfigPair {
240 identifier: kv.key().clone(),
241 config: kv.value().into(),
242 })
243 .collect();
244 let user_protos = DashMap::new();
245 for spec in user_specifiers {
246 user_protos.insert(
247 spec.key().clone(),
248 ProtocolDefinition {
249 communication: Some(spec.value().clone()),
250 ..Default::default()
251 },
252 );
253 }
254 let user_config_definition = UserConfigDefinition {
255 protocols: Some(user_protos.clone()),
256 user_device_configs: Some(user_definitions_vec),
257 };
258 let mut user_config_file = UserConfigFile::new(4, 0);
259 user_config_file.set_user_configs(Some(user_config_definition));
260 serde_json::to_string_pretty(&user_config_file).map_err(|e| {
261 ButtplugError::from(ButtplugDeviceError::DeviceConfigurationError(format!(
262 "Cannot save device configuration file: {e:?}",
263 )))
264 })
265}
266
267#[cfg(test)]
268mod test {
269 use crate::device_config_file::load_main_config;
270
271 use super::{DEVICE_CONFIGURATION_JSON, base::BaseConfigFile, load_protocol_config_from_json};
272
273 #[test]
274 fn test_config_file_parsing() {
275 load_protocol_config_from_json::<BaseConfigFile>(&DEVICE_CONFIGURATION_JSON.to_owned(), true)
276 .unwrap();
277 }
278
279 #[test]
280 fn test_main_file_parsing() {
281 load_main_config(&None, false).unwrap();
282 }
283}