blob: a668c5000161d1b2b1b67f6cad74dca45b38d479 [file] [log] [blame]
// Copyright 2023 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 crate::environment::EnvironmentContext;
use crate::nested::nested_get;
use crate::ConfigValue;
use anyhow::anyhow;
use ffx_config_domain::ConfigMap;
use serde_json::Value;
// Mechanisms for implementing config "aliases", in which one config option can be used
// to stand in for a group of other options. In this (simplistic) implementation, users of
// the aliases option (e.g. "discovery.mdns.enabled") must not query it directly, but must
// instead go through the accessor function.
pub trait ConfigAliases {
fn get_with_alias(&self, key: &str, alias: &str) -> Option<(ConfigValue, ConfigValue)>;
}
impl ConfigAliases for EnvironmentContext {
// Return values at first config level for which either the key or the alias has a value
fn get_with_alias(&self, key: &str, alias: &str) -> Option<(ConfigValue, ConfigValue)> {
let Ok(env) = self.load() else { return None };
let Ok(config) = env.config_from_cache(None) else { return None };
let key_vec: Vec<&str> = key.split('.').collect();
let alias_vec: Vec<&str> = alias.split('.').collect();
// These are called only by functions that hard-code the keys, so we won't panic
let key_head = key_vec[0];
let alias_head = alias_vec[0];
let read_guard = config.read().map_err(|_| anyhow!("config read guard")).ok()?;
for config in read_guard.iter() {
let kval = nested_get(config, key_head, &key_vec[1..]);
let aval = nested_get(config, alias_head, &alias_vec[1..]);
if kval.is_some() || aval.is_some() {
return Some((ConfigValue(kval.cloned()), ConfigValue(aval.cloned())));
}
}
Some((ConfigValue(None), ConfigValue(None)))
}
}
// Specific aliases
//------------------------
// "ffx.isolated"
const FFX_ISOLATED: &str = "ffx.isolated";
const FASTBOOT_USB_DISCOVERY_DISABLED: &str = "fastboot.usb.disabled";
const FFX_ANALYTICS_DISABLED: &str = "ffx.analytics.disabled";
const MDNS_DISCOVERY_ENABLED: &str = "discovery.mdns.enabled";
const MDNS_AUTOCONNECT_ENABLED: &str = "discovery.mdns.autoconnect";
// Get the aliased value, along with the isolation alias -- both bools.
async fn get_with_isolated_alias(
ctx: &EnvironmentContext,
key: &str,
) -> Option<(Option<bool>, Option<bool>)> {
let (v, isov) = ctx.get_with_alias(key, FFX_ISOLATED)?;
Some((bool::try_from(v).ok(), bool::try_from(isov).ok()))
}
pub async fn is_usb_discovery_disabled(ctx: &EnvironmentContext) -> bool {
let default = false;
match get_with_isolated_alias(ctx, FASTBOOT_USB_DISCOVERY_DISABLED).await {
None => return default,
Some((usb, iso)) => usb.unwrap_or_else(|| iso.unwrap_or(default)),
}
}
pub async fn is_analytics_disabled(ctx: &EnvironmentContext) -> bool {
let default = false;
match get_with_isolated_alias(ctx, FFX_ANALYTICS_DISABLED).await {
None => return default,
Some((ad, iso)) => ad.unwrap_or_else(|| iso.unwrap_or(default)),
}
}
pub async fn is_mdns_discovery_disabled(ctx: &EnvironmentContext) -> bool {
let default = false;
match get_with_isolated_alias(ctx, MDNS_DISCOVERY_ENABLED).await {
None => return default,
// The option is _enabled_, so we have to invert it
Some((mdns_disc, iso)) => mdns_disc.map(|b| !b).unwrap_or_else(|| iso.unwrap_or(default)),
}
}
pub async fn is_mdns_autoconnect_disabled(ctx: &EnvironmentContext) -> bool {
let default = false;
match get_with_isolated_alias(ctx, MDNS_AUTOCONNECT_ENABLED).await {
None => return default,
// The option is _enabled_, so we have to invert it
Some((mdns_conn, iso)) => mdns_conn.map(|b| !b).unwrap_or_else(|| iso.unwrap_or(default)),
}
}
/// When run in an isolated dir, also set `ffx.isolated`. This will only work
/// "usefully" if it is invoked with the global EnvironmentContext, i.e. the
/// installed by ffx_config::init()
pub(crate) fn add_isolation_default(cm: &mut ConfigMap) {
cm.insert(FFX_ISOLATED.into(), Value::Bool(true));
}
//------------------------
////////////////////////////////////////////////////////////////////////////////
// tests
#[cfg(test)]
mod test {
use super::*;
use crate::{self as ffx_config, ConfigLevel};
#[fuchsia_async::run_singlethreaded(test)]
async fn test_ffx_isolated() {
let env = ffx_config::test_init().await.expect("create test config");
// It'd be nice to check that isolation is not set by default,
// but since a test may use an isolate-dir (which automatically
// sets isolation), that check is difficult
// assert!(!is_usb_discovery_disabled(&env.context).await);
// assert!(!is_analytics_disabled(&env.context).await);
// assert!(!is_mdns_discovery_disabled(&env.context).await);
// assert!(!is_mdns_autoconnect_disabled(&env.context).await);
env.context
.query("ffx.isolated")
.level(Some(ConfigLevel::User))
.set(Value::Bool(true))
.await
.unwrap();
assert!(is_usb_discovery_disabled(&env.context).await);
assert!(is_analytics_disabled(&env.context).await);
assert!(is_mdns_discovery_disabled(&env.context).await);
assert!(is_mdns_autoconnect_disabled(&env.context).await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_ffx_isolated_can_override_global() {
let env = ffx_config::test_init().await.expect("create test config");
env.context
.query("ffx.isolated")
// Higher precedence
.level(Some(ConfigLevel::User))
.set(Value::Bool(true))
.await
.unwrap();
env.context
.query("fastboot.usb.disabled")
// Lower precedence
.level(Some(ConfigLevel::Global))
.set(Value::Bool(false))
.await
.unwrap();
// Isolation is respected, since it is set at a higher level
assert!(is_usb_discovery_disabled(&env.context).await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_ffx_isolated_can_be_overridden() {
let env = ffx_config::test_init().await.expect("create test config");
env.context
.query("ffx.isolated")
// Higher precedence
.level(Some(ConfigLevel::Global))
.set(Value::Bool(true))
.await
.unwrap();
env.context
.query("fastboot.usb.disabled")
// Lower precedence
.level(Some(ConfigLevel::User))
.set(Value::Bool(false))
.await
.unwrap();
// Isolation is overridden, since it is set at a lower level
// (It's not clear we _want_ this behavior, but this is the current plan)
assert!(!is_usb_discovery_disabled(&env.context).await);
// Nothing else is affected
assert!(is_analytics_disabled(&env.context).await);
}
}