Buttplug sex toy control library
at master 283 lines 9.2 kB view raw
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}