blob: 0b4e1d03000f6167d22ed3d5436290ecbfd66bb0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_bluetooth_sys::{self as sys, BrEdrSecurityMode, LeSecurityMode};
use serde::{Deserialize, Serialize};
use std::{cmp::PartialEq, fs::OpenOptions, path::Path};
static OVERRIDE_CONFIG_FILE_PATH: &'static str = "/config/data/build-config.json";
static DEFAULT_CONFIG_FILE_PATH: &'static str = "/pkg/data/bt-gap-default.json";
/// The `build_config` module enables build-time configuration of the Bluetooth Host Subsystem.
/// Default configuration parameters are taken from //src/connectivity/bluetooth/core/bt-gap/
/// config/default.json. `build_config` also enables overriding the default configuration without
/// changing the Fuchsia source tree through the `config-data` component sandbox feature and
/// associated `config_data` GN template with the arguments:
/// ```
/// for_pkg = "bt-gap",
/// outputs = [
/// "build-config.json"
/// ]
/// ``` (https://fuchsia.dev/fuchsia-src/development/components/config_data).
/// `build_config::Config` clients access the configuration settings through the `load_default`
/// free function.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct Config {
pub le: LeConfig,
pub bredr: BrEdrConfig,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct LeConfig {
#[serde(rename = "privacy-enabled")]
pub privacy_enabled: bool,
#[serde(rename = "background-scan-enabled")]
pub background_scan_enabled: bool,
#[serde(rename = "security-mode")]
#[serde(with = "LeSecurityModeDef")]
pub security_mode: LeSecurityMode,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct BrEdrConfig {
pub connectable: bool,
#[serde(rename = "security-mode")]
#[serde(with = "BrEdrSecurityModeDef")]
pub security_mode: BrEdrSecurityMode,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "BrEdrSecurityMode")]
pub enum BrEdrSecurityModeDef {
Mode4 = 1,
SecureConnectionsOnly = 2,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "LeSecurityMode")]
pub enum LeSecurityModeDef {
Mode1 = 1,
SecureConnectionsOnly = 2,
}
impl Config {
pub fn update_with_sys_settings(&self, new_settings: &sys::Settings) -> Self {
let mut new_config = self.clone();
new_config.le.privacy_enabled = new_settings.le_privacy.unwrap_or(self.le.privacy_enabled);
new_config.le.background_scan_enabled =
new_settings.le_background_scan.unwrap_or(self.le.background_scan_enabled);
new_config.le.security_mode =
new_settings.le_security_mode.unwrap_or(self.le.security_mode);
new_config.bredr.connectable =
new_settings.bredr_connectable_mode.unwrap_or(self.bredr.connectable);
new_config.bredr.security_mode =
new_settings.bredr_security_mode.unwrap_or(self.bredr.security_mode);
new_config
}
}
impl Into<sys::Settings> for Config {
fn into(self) -> sys::Settings {
sys::Settings {
le_privacy: Some(self.le.privacy_enabled),
le_background_scan: Some(self.le.background_scan_enabled),
bredr_connectable_mode: Some(self.bredr.connectable),
le_security_mode: Some(self.le.security_mode),
bredr_security_mode: Some(self.bredr.security_mode),
..Default::default()
}
}
}
pub fn load_default() -> Config {
load_internal(Path::new(OVERRIDE_CONFIG_FILE_PATH), Path::new(DEFAULT_CONFIG_FILE_PATH))
}
fn load_internal(override_file_path: &Path, default_file_path: &Path) -> Config {
let config_path =
if override_file_path.exists() { override_file_path } else { default_file_path };
let config_file = OpenOptions::new().read(true).write(false).open(config_path).unwrap();
serde_json::from_reader(config_file)
.expect("Malformatted bt-gap config file, cannot initialize Bluetooth stack")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::host_device::HostDevice;
use {
assert_matches::assert_matches,
fidl_fuchsia_bluetooth_host::{HostMarker, HostRequest},
fuchsia_bluetooth::types::{Address, HostId},
futures::{future, join, stream::TryStreamExt},
std::collections::HashSet,
tempfile::NamedTempFile,
};
static BASIC_CONFIG: Config = Config {
le: LeConfig {
privacy_enabled: true,
background_scan_enabled: true,
security_mode: LeSecurityMode::Mode1,
},
bredr: BrEdrConfig { connectable: true, security_mode: BrEdrSecurityMode::Mode4 },
};
#[test]
fn prefer_overridden_config() {
let default_file = NamedTempFile::new().unwrap();
serde_json::to_writer(&default_file, &BASIC_CONFIG).unwrap();
// There should be no file at OVERRIDE_CONFIG_FILE_PATH; `config_data` templates should not
// target this test package. This means config will load from the (existing) default path.
assert_eq!(
BASIC_CONFIG,
load_internal(Path::new(OVERRIDE_CONFIG_FILE_PATH), default_file.path())
);
// Write a different config file and set it as the override location to verify that `load`
// picks up and prefers the override file.
let override_config = Config {
le: LeConfig {
privacy_enabled: true,
background_scan_enabled: false,
security_mode: LeSecurityMode::SecureConnectionsOnly,
},
bredr: BrEdrConfig {
connectable: false,
security_mode: BrEdrSecurityMode::SecureConnectionsOnly,
},
};
let override_file = NamedTempFile::new().unwrap();
serde_json::to_writer(&override_file, &override_config).unwrap();
assert_eq!(override_config, load_internal(override_file.path(), default_file.path()));
}
#[fuchsia::test]
async fn apply_config() {
let (host_proxy, host_server) =
fidl::endpoints::create_proxy_and_stream::<HostMarker>().unwrap();
let host_device = HostDevice::mock(
HostId(42),
Address::Public([1, 2, 3, 4, 5, 6]),
"/dev/host".to_string(),
host_proxy,
);
let test_config = Config {
le: LeConfig {
privacy_enabled: true,
background_scan_enabled: false,
security_mode: LeSecurityMode::Mode1,
},
bredr: BrEdrConfig { connectable: false, security_mode: BrEdrSecurityMode::Mode4 },
};
let run_host = async {
let mut expected_reqs = HashSet::<String>::new();
let _ = expected_reqs.insert("enable_privacy".into());
let _ = expected_reqs.insert("enable_background_scan".into());
let _ = expected_reqs.insert("set_connectable".into());
let _ = expected_reqs.insert("set_le_security_mode".into());
let _ = expected_reqs.insert("set_bredr_security_mode".into());
host_server
.try_for_each(|req| {
match req {
HostRequest::EnablePrivacy { enabled, .. } => {
assert!(expected_reqs.remove("enable_privacy"));
assert_eq!(test_config.le.privacy_enabled, enabled);
}
HostRequest::EnableBackgroundScan { enabled, .. } => {
assert!(expected_reqs.remove("enable_background_scan"));
assert_eq!(test_config.le.background_scan_enabled, enabled);
}
HostRequest::SetConnectable { enabled, responder } => {
assert!(expected_reqs.remove("set_connectable"));
assert_eq!(test_config.bredr.connectable, enabled);
assert_matches!(responder.send(Ok(())), Ok(()));
}
HostRequest::SetLeSecurityMode { le_security_mode, .. } => {
assert!(expected_reqs.remove("set_le_security_mode"));
assert_eq!(test_config.le.security_mode, le_security_mode);
}
HostRequest::SetBrEdrSecurityMode { bredr_security_mode, .. } => {
assert!(expected_reqs.remove("set_bredr_security_mode"));
assert_eq!(test_config.bredr.security_mode, bredr_security_mode);
}
_ => panic!("unexpected HostRequest!"),
};
future::ok(())
})
.await
.unwrap();
assert_eq!(expected_reqs.into_iter().collect::<Vec<String>>(), Vec::<String>::new());
};
let apply_config = async {
host_device.apply_config(test_config.clone()).await.unwrap();
// Drop `host_device` so the host server request stream terminates
drop(host_device)
};
join!(run_host, apply_config);
}
#[test]
fn update_with_sys_settings() {
let partial_settings = sys::Settings {
le_privacy: Some(false),
bredr_connectable_mode: Some(false),
..Default::default()
};
let expected_config = Config {
le: LeConfig { privacy_enabled: false, ..BASIC_CONFIG.le },
bredr: BrEdrConfig { connectable: false, ..BASIC_CONFIG.bredr },
};
assert_eq!(
expected_config,
BASIC_CONFIG.clone().update_with_sys_settings(&partial_settings)
);
}
}