blob: 670800c6f9be077946e0cfe44a596a1140255be2 [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 anyhow::{anyhow, Error};
use fidl_fuchsia_power_broker::{
self as fpb, BinaryPowerLevel, DependencyType, LeaseStatus, Permissions, PowerLevel,
RegisterDependencyTokenError, UnregisterDependencyTokenError,
};
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use itertools::Itertools;
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::Hash;
use std::iter::repeat;
use uuid::Uuid;
use crate::credentials::*;
use crate::topology::*;
pub struct Broker {
catalog: Catalog,
credentials: Registry,
// The current level for each element, as reported to the broker.
current: SubscribeMap<ElementID, PowerLevel>,
// The level for each element required by the topology.
required: SubscribeMap<ElementID, PowerLevel>,
}
impl Broker {
pub fn new() -> Self {
Broker {
catalog: Catalog::new(),
credentials: Registry::new(),
current: SubscribeMap::new(),
required: SubscribeMap::new(),
}
}
fn lookup_credentials(&self, token: Token) -> Option<Credential> {
self.credentials.lookup(token)
}
fn unregister_all_credentials_for_element(&mut self, element_id: &ElementID) {
self.credentials.unregister_all_for_element(element_id)
}
pub fn register_dependency_token(
&mut self,
element_id: &ElementID,
token: Token,
dependency_type: DependencyType,
) -> Result<(), RegisterDependencyTokenError> {
let permissions = match dependency_type {
DependencyType::Active => Permissions::MODIFY_ACTIVE_DEPENDENT,
DependencyType::Passive => Permissions::MODIFY_PASSIVE_DEPENDENT,
};
match self
.credentials
.register(element_id, CredentialToRegister { broker_token: token, permissions })
{
Err(RegisterCredentialsError::AlreadyInUse) => {
Err(RegisterDependencyTokenError::AlreadyInUse)
}
Err(RegisterCredentialsError::Internal) => Err(RegisterDependencyTokenError::Internal),
Ok(_) => Ok(()),
}
}
pub fn unregister_dependency_token(
&mut self,
element_id: &ElementID,
token: Token,
) -> Result<(), UnregisterDependencyTokenError> {
let Some(credential) = self.lookup_credentials(token) else {
tracing::debug!("unregister_dependency_token: token not found");
return Err(UnregisterDependencyTokenError::NotFound);
};
if credential.get_element() != element_id {
tracing::debug!(
"unregister_dependency_token: token is registered to {:?}, not {:?}",
&credential.get_element(),
&element_id,
);
return Err(UnregisterDependencyTokenError::NotAuthorized);
}
self.credentials.unregister(&credential);
Ok(())
}
fn current_level_satisfies(&self, required: &ElementLevel) -> bool {
self.current
.get(&required.element_id)
// If current level is unknown, required is not satisfied.
.is_some_and(|current| current.satisfies(required.level))
}
pub fn update_current_level(
&mut self,
element_id: &ElementID,
level: PowerLevel,
) -> Result<(), fpb::CurrentLevelError> {
tracing::debug!("update_current_level({:?}, {:?})", element_id, level);
self.current.update(element_id, level);
// Some previously pending claims may now be ready to be activated,
// because the active claims upon which they depend may now be
// satisfied.
// Find active or passive claims that are now satisfied by the new
// current level:
let claims_for_required_element = self
.catalog
.active_claims
.for_required_element(element_id)
.into_iter()
.chain(self.catalog.passive_claims.for_required_element(element_id));
let claims_satisfied: Vec<Claim> =
claims_for_required_element.filter(|c| level.satisfies(c.requires().level)).collect();
tracing::debug!("claims_satisfied = {:?})", &claims_satisfied);
let mut leases_to_check_if_satisfied = HashSet::new();
for claim in &claims_satisfied {
leases_to_check_if_satisfied.insert(&claim.lease_id);
// Look for pending claims that were (at least partially) blocked
// by a claim on this element level that is now satisfied.
// (They may still have other blockers, though.)
let pending_claims =
self.catalog.pending_claims.for_required_element(&claim.dependent().element_id);
tracing::debug!(
"pending_claims.for_required_element({:?}) = {:?})",
&claim.dependent().element_id,
&pending_claims
);
let claims_activated = self.activate_claims_if_dependencies_satisfied(pending_claims);
tracing::debug!("claims_activated = {:?})", &claims_activated);
self.update_required_levels(element_ids_required_by_claims(&claims_activated));
}
tracing::debug!("leases_to_check_if_satisfied = {:?})", &leases_to_check_if_satisfied);
for lease_id in leases_to_check_if_satisfied {
self.update_lease_status(lease_id);
}
// Find claims to drop
let claims_to_drop_for_element = self.catalog.active_claims.to_drop_for_element(element_id);
let claims_dropped = self.drop_claims_with_no_dependents(&claims_to_drop_for_element);
self.update_required_levels(element_ids_required_by_claims(&claims_dropped));
Ok(())
}
#[cfg(test)]
pub fn get_current_level(&mut self, element_id: &ElementID) -> Option<PowerLevel> {
self.current.get(element_id)
}
pub fn watch_current_level(
&mut self,
element_id: &ElementID,
) -> UnboundedReceiver<Option<PowerLevel>> {
self.current.subscribe(element_id)
}
#[cfg(test)]
pub fn get_required_level(&mut self, element_id: &ElementID) -> Option<PowerLevel> {
self.required.get(element_id)
}
pub fn watch_required_level(
&mut self,
element_id: &ElementID,
) -> UnboundedReceiver<Option<PowerLevel>> {
self.required.subscribe(element_id)
}
pub fn acquire_lease(
&mut self,
element_id: &ElementID,
level: PowerLevel,
) -> Result<Lease, fpb::LeaseError> {
let (lease, claims) = self.catalog.create_lease_and_claims(element_id, level);
let claims_activated = self.activate_claims_if_dependencies_satisfied(claims);
self.update_required_levels(element_ids_required_by_claims(&claims_activated));
self.update_lease_status(&lease.id);
Ok(lease)
}
pub fn drop_lease(&mut self, lease_id: &LeaseID) -> Result<(), Error> {
let (_, claims) = self.catalog.drop(lease_id)?;
let claims_dropped = self.drop_claims_with_no_dependents(&claims);
self.update_required_levels(element_ids_required_by_claims(&claims_dropped));
self.catalog.lease_status.remove(lease_id);
Ok(())
}
fn calculate_lease_status(&self, lease_id: &LeaseID) -> LeaseStatus {
// If a lease has any Pending claims, it is Pending.
if !self.catalog.pending_claims.for_lease(lease_id).is_empty() {
return LeaseStatus::Pending;
}
// If a lease has any active claims that have not been satisfied
// it is still Pending.
for claim in self.catalog.active_claims.for_lease(lease_id) {
if !self.current_level_satisfies(claim.requires()) {
return LeaseStatus::Pending;
}
}
// If a lease has any passive claims that have not been satisfied
// it is still Pending.
for claim in self.catalog.passive_claims.for_lease(lease_id) {
if !self.current_level_satisfies(claim.requires()) {
return LeaseStatus::Pending;
}
}
// All claims are active and satisfied, so the lease is Satisfied.
LeaseStatus::Satisfied
}
pub fn update_lease_status(&mut self, lease_id: &LeaseID) {
let status = self.calculate_lease_status(lease_id);
self.catalog.lease_status.update(lease_id, status);
}
#[cfg(test)]
pub fn get_lease_status(&mut self, lease_id: &LeaseID) -> Option<LeaseStatus> {
self.catalog.get_lease_status(lease_id)
}
pub fn watch_lease_status(
&mut self,
lease_id: &LeaseID,
) -> UnboundedReceiver<Option<LeaseStatus>> {
self.catalog.watch_lease_status(lease_id)
}
fn update_required_levels(&mut self, element_ids: Vec<&ElementID>) {
for element_id in element_ids {
let new_required_level = self.catalog.calculate_required_level(element_id);
tracing::debug!("update required level({:?}, {:?})", element_id, new_required_level);
self.required.update(element_id, new_required_level);
}
}
/// Examines a Vec of pending claims and activates each claim for which the
/// required element is already at the required level (and thus the claim
/// is already satisfied), or all of the dependencies of its required
/// ElementLevel are met.
/// For example, let us imagine elements A, B, C and D where A depends on B
/// and B depends on C and D. In order to activate the A->B claim, all
/// dependencies of B (i.e. B->C and B->D) must first be satisfied.
/// Returns a Vec of activated claims.
fn activate_claims_if_dependencies_satisfied(&mut self, claims: Vec<Claim>) -> Vec<Claim> {
tracing::debug!("activate_claims_if_dependencies_satisfied: {:?}", claims);
let claims_to_activate: Vec<Claim> = claims
.into_iter()
.filter(|c| {
// If the required element is already at the required level,
// then the claim can immediately be activated (and is
// already satisfied).
self.current_level_satisfies(c.requires())
// Otherwise, it can only be activated if all of its
// dependencies are satisfied.
|| self.all_dependencies_satisfied(&c.requires())
})
.collect();
for claim in &claims_to_activate {
self.catalog.activate_pending_claim(&claim.id);
}
claims_to_activate
}
/// Examines the direct active and passive dependencies of an element level
/// and returns true if they are all satisfied (current level >= required).
fn all_dependencies_satisfied(&self, element_level: &ElementLevel) -> bool {
let active_dependencies = self.catalog.topology.direct_active_dependencies(&element_level);
let passive_dependencies =
self.catalog.topology.direct_passive_dependencies(&element_level);
active_dependencies.into_iter().chain(passive_dependencies).all(|dep| {
if !self.current_level_satisfies(&dep.requires) {
tracing::debug!(
"dependency {dep:?} of element_level {element_level:?} is not satisfied: \
current level of {:?} = {:?}, {:?} required",
&dep.requires.element_id,
self.current.get(&dep.requires.element_id),
&dep.requires.level
);
return false;
}
return true;
})
}
/// Examines a Vec of claims and drops any that no longer have any
/// dependents. Returns a Vec of dropped claims.
fn drop_claims_with_no_dependents(&mut self, claims: &Vec<Claim>) -> Vec<Claim> {
tracing::debug!("check_claims_to_drop: {:?}", claims);
let mut claims_to_drop = Vec::new();
for claim_to_check in claims {
let mut has_dependents = false;
// Only claims belonging to the same lease can be a dependent.
for related_claim in self.catalog.active_claims.for_lease(&claim_to_check.lease_id) {
if related_claim.requires() == claim_to_check.dependent() {
has_dependents = true;
break;
}
}
if has_dependents {
continue;
}
tracing::debug!("will drop claim: {:?}", claim_to_check);
claims_to_drop.push(claim_to_check.clone());
}
for claim in &claims_to_drop {
self.catalog.drop_active_claim(&claim.id);
}
claims_to_drop
}
pub fn add_element(
&mut self,
name: &str,
initial_current_level: PowerLevel,
valid_levels: Vec<PowerLevel>,
level_dependencies: Vec<fpb::LevelDependency>,
active_dependency_tokens: Vec<Token>,
passive_dependency_tokens: Vec<Token>,
) -> Result<ElementID, AddElementError> {
if valid_levels.len() < 1 {
return Err(AddElementError::Invalid);
}
let id = self.catalog.topology.add_element(name, valid_levels.to_vec())?;
self.current.update(&id, initial_current_level);
let Some(minimum_level) = self.catalog.topology.minimum_level(&id) else {
self.remove_element(&id);
return Err(AddElementError::Internal);
};
self.required.update(&id, minimum_level);
for dependency in level_dependencies {
if let Err(err) = self.add_dependency(
&id,
dependency.dependency_type,
dependency.dependent_level,
dependency.requires_token.into(),
dependency.requires_level,
) {
// Clean up by removing the element we just added.
self.remove_element(&id);
return Err(match err {
ModifyDependencyError::AlreadyExists => AddElementError::Invalid,
ModifyDependencyError::Invalid => AddElementError::Invalid,
ModifyDependencyError::NotFound(_) => AddElementError::Invalid,
ModifyDependencyError::NotAuthorized => AddElementError::NotAuthorized,
});
};
}
let labeled_dependency_tokens = active_dependency_tokens
.into_iter()
.zip(repeat(DependencyType::Active))
.chain(passive_dependency_tokens.into_iter().zip(repeat(DependencyType::Passive)));
for (token, dependency_type) in labeled_dependency_tokens {
if let Err(err) = self.register_dependency_token(&id, token, dependency_type) {
match err {
RegisterDependencyTokenError::Internal => {
tracing::debug!("can't register_dependency_token for {:?}: internal", &id);
return Err(AddElementError::Internal);
}
RegisterDependencyTokenError::AlreadyInUse => {
tracing::debug!(
"can't register_dependency_token for {:?}: already in use",
&id
);
return Err(AddElementError::Invalid);
}
fpb::RegisterDependencyTokenErrorUnknown!() => {
tracing::warn!(
"unknown RegisterDependencyTokenError received: {}",
err.into_primitive()
);
return Err(AddElementError::Internal);
}
}
}
}
Ok(id)
}
#[cfg(test)]
fn element_exists(&self, element_id: &ElementID) -> bool {
self.catalog.topology.element_exists(element_id)
}
pub fn remove_element(&mut self, element_id: &ElementID) {
self.catalog.topology.remove_element(element_id);
self.unregister_all_credentials_for_element(element_id);
self.current.remove(element_id);
self.required.remove(element_id);
}
/// Checks authorization from requires_token, and if valid, adds an active
/// or passive dependency to the Topology, according to dependency_type.
pub fn add_dependency(
&mut self,
element_id: &ElementID,
dependency_type: DependencyType,
dependent_level: PowerLevel,
requires_token: Token,
requires_level: PowerLevel,
) -> Result<(), ModifyDependencyError> {
let Some(requires_cred) = self.lookup_credentials(requires_token) else {
return Err(ModifyDependencyError::NotAuthorized);
};
let dependency = Dependency {
dependent: ElementLevel { element_id: element_id.clone(), level: dependent_level },
requires: ElementLevel {
element_id: requires_cred.get_element().clone(),
level: requires_level,
},
};
match dependency_type {
DependencyType::Active => {
if !requires_cred.contains(Permissions::MODIFY_ACTIVE_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.add_active_dependency(&dependency)
}
DependencyType::Passive => {
if !requires_cred.contains(Permissions::MODIFY_PASSIVE_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.add_passive_dependency(&dependency)
}
}
}
/// Checks authorization from requires_token, and if valid, removes a dependency from the Topology.
pub fn remove_dependency(
&mut self,
element_id: &ElementID,
dependency_type: DependencyType,
dependent_level: PowerLevel,
requires_token: Token,
requires_level: PowerLevel,
) -> Result<(), ModifyDependencyError> {
let Some(requires_cred) = self.lookup_credentials(requires_token) else {
return Err(ModifyDependencyError::NotAuthorized);
};
let dependency = Dependency {
dependent: ElementLevel { element_id: element_id.clone(), level: dependent_level },
requires: ElementLevel {
element_id: requires_cred.get_element().clone(),
level: requires_level,
},
};
match dependency_type {
DependencyType::Active => {
if !requires_cred.contains(Permissions::MODIFY_ACTIVE_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.remove_active_dependency(&dependency)
}
DependencyType::Passive => {
if !requires_cred.contains(Permissions::MODIFY_PASSIVE_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.remove_passive_dependency(&dependency)
}
}
}
}
pub type LeaseID = String;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct Lease {
pub id: LeaseID,
pub element_id: ElementID,
pub level: PowerLevel,
}
impl Lease {
fn new(element_id: &ElementID, level: PowerLevel) -> Self {
let id = LeaseID::from(Uuid::new_v4().as_simple().to_string());
Lease { id: id.clone(), element_id: element_id.clone(), level: level.clone() }
}
}
type ClaimID = String;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
struct Claim {
pub id: ClaimID,
dependency: Dependency,
pub lease_id: LeaseID,
}
impl Claim {
fn new(dependency: Dependency, lease_id: &LeaseID) -> Self {
Claim {
id: ClaimID::from(Uuid::new_v4().as_simple().to_string()),
dependency,
lease_id: lease_id.clone(),
}
}
fn dependent(&self) -> &ElementLevel {
&self.dependency.dependent
}
fn requires(&self) -> &ElementLevel {
&self.dependency.requires
}
}
/// Returns a Vec of unique ElementIDs required by claims.
fn element_ids_required_by_claims(claims: &Vec<Claim>) -> Vec<&ElementID> {
claims.into_iter().map(|c| &c.requires().element_id).unique().collect()
}
#[derive(Debug)]
struct Catalog {
topology: Topology,
leases: HashMap<LeaseID, Lease>,
lease_status: SubscribeMap<LeaseID, LeaseStatus>,
/// Active claims affect the required level communicated by Power Broker.
/// All dependencies of their required element are met.
active_claims: ClaimTracker,
/// Pending claims do not yet affect the required level communicated by
/// Power Broker. Some dependencies of their required element are not met.
/// Once all dependencies are met, they will be moved to active claims.
pending_claims: ClaimTracker,
/// Passive claims must be satisfied in order for their lease to be
/// satisfied but since they do not affect the required levels of the
/// element they depend upon, something else must raise the required
/// element's level in order for a passive claim to be satisfied (either
/// another active claim, or an unmanaged element raising its own level).
passive_claims: ClaimTracker,
}
impl Catalog {
fn new() -> Self {
Catalog {
topology: Topology::new(),
leases: HashMap::new(),
lease_status: SubscribeMap::new(),
active_claims: ClaimTracker::new(),
pending_claims: ClaimTracker::new(),
passive_claims: ClaimTracker::new(),
}
}
/// Calculates the required level for each element, according to the
/// Minimum Power Level Policy. Only active claims are considered here.
fn calculate_required_level(&self, element_id: &ElementID) -> PowerLevel {
let no_claims = Vec::new();
let element_claims = self
.active_claims
.claims_by_required_element_id
.get(element_id)
// Treat both missing key and empty vec as no claims.
.unwrap_or(&no_claims)
.iter()
.filter_map(|id| self.active_claims.claims.get(id));
// Return the maximum of all active claims:
if let Some(required_level) = element_claims.map(|x| x.requires().level).max() {
required_level
} else {
// No claims, return default level:
if let Some(default_level) = self.topology.minimum_level(element_id) {
default_level
} else {
// TODO(https://fxbug.dev/333930405): This can be `tracing::error!` again if
// dropping a lease after its corresponding element is gone is handled properly.
// See `session-manager-integration-test.cm` that exercises that case.
tracing::warn!("calculate_required_level: no minimum_level for {:?}", element_id);
BinaryPowerLevel::Off.into_primitive()
}
}
}
/// Creates a new lease for the given element and level along with all
/// claims necessary to satisfy this lease and adds them to pending_claims.
/// Returns the new lease, and a Vec of pending claims created.
fn create_lease_and_claims(
&mut self,
element_id: &ElementID,
level: PowerLevel,
) -> (Lease, Vec<Claim>) {
tracing::debug!("acquire({:?}, {:?})", &element_id, &level);
// TODO: Add lease validation and control.
let lease = Lease::new(&element_id, level);
self.leases.insert(lease.id.clone(), lease.clone());
let mut pending_claims = Vec::new();
let element_level = ElementLevel { element_id: element_id.clone(), level: level.clone() };
let (active_dependencies, passive_dependencies) =
self.topology.all_active_and_passive_dependencies(&element_level);
// Create pending claims for each of the active dependencies.
for dependency in active_dependencies {
let pending_claim = Claim::new(dependency, &lease.id);
pending_claims.push(pending_claim);
}
for claim in pending_claims.iter() {
tracing::debug!("adding pending claim: {:?}", &claim);
self.pending_claims.add(claim.clone());
}
// Create passive claims for each of the passive dependencies.
for dependency in passive_dependencies {
let passive_claim = Claim::new(dependency, &lease.id);
self.passive_claims.add(passive_claim);
}
(lease, pending_claims)
}
/// Drops an existing lease, and initiates process of releasing all
/// associated claims.
/// Returns the dropped lease, and a Vec of all active claims that have
/// been marked to drop.
fn drop(&mut self, lease_id: &LeaseID) -> Result<(Lease, Vec<Claim>), Error> {
tracing::debug!("drop({:?})", &lease_id);
let lease = self.leases.remove(lease_id).ok_or(anyhow!("{lease_id} not found"))?;
let active_claims = self.active_claims.for_lease(&lease.id);
tracing::debug!("active_claim_ids: {:?}", &active_claims);
let pending_claims = self.pending_claims.for_lease(&lease.id);
tracing::debug!("pending_claim_ids: {:?}", &pending_claims);
let passive_claims = self.passive_claims.for_lease(&lease.id);
tracing::debug!("pending_claim_ids: {:?}", &passive_claims);
// Pending claims should be dropped immediately.
for claim in pending_claims {
if let Some(removed) = self.pending_claims.remove(&claim.id) {
tracing::debug!("removing pending claim: {:?}", &removed);
} else {
tracing::error!("cannot remove pending claim: not found: {}", claim.id);
}
}
// Passive claims should be dropped immediately.
for claim in passive_claims {
if let Some(removed) = self.passive_claims.remove(&claim.id) {
tracing::debug!("removing passive claim: {:?}", &removed);
} else {
tracing::error!("cannot remove passive claim: not found: {}", claim.id);
}
}
// Active claims should be marked to drop in an orderly sequence.
let claims_marked_to_drop = self.active_claims.mark_lease_claims_to_drop(&lease.id);
Ok((lease, claims_marked_to_drop))
}
/// Activates a pending claim, moving it to active_claims.
fn activate_pending_claim(&mut self, claim_id: &ClaimID) {
tracing::debug!("activate_claim: {:?}", claim_id);
self.pending_claims.move_to(&claim_id, &mut self.active_claims);
}
/// Drops an active claim, removing it from active_claims.
fn drop_active_claim(&mut self, claim_id: &ClaimID) {
tracing::debug!("drop_claim: {:?}", claim_id);
self.active_claims.remove(&claim_id);
}
#[cfg(test)]
pub fn get_lease_status(&mut self, lease_id: &LeaseID) -> Option<LeaseStatus> {
self.lease_status.get(lease_id)
}
fn watch_lease_status(&mut self, lease_id: &LeaseID) -> UnboundedReceiver<Option<LeaseStatus>> {
self.lease_status.subscribe(lease_id)
}
}
#[derive(Debug)]
struct ClaimTracker {
claims: HashMap<ClaimID, Claim>,
claims_by_required_element_id: HashMap<ElementID, Vec<ClaimID>>,
claims_by_lease: HashMap<LeaseID, Vec<ClaimID>>,
claims_to_drop_by_element_id: HashMap<ElementID, Vec<ClaimID>>,
}
impl ClaimTracker {
fn new() -> Self {
ClaimTracker {
claims: HashMap::new(),
claims_by_required_element_id: HashMap::new(),
claims_by_lease: HashMap::new(),
claims_to_drop_by_element_id: HashMap::new(),
}
}
fn add(&mut self, claim: Claim) {
self.claims_by_required_element_id
.entry(claim.requires().element_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
self.claims_by_lease
.entry(claim.lease_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
self.claims.insert(claim.id.clone(), claim);
}
fn remove(&mut self, id: &ClaimID) -> Option<Claim> {
let Some(claim) = self.claims.remove(id) else {
return None;
};
if let Some(claim_ids) =
self.claims_by_required_element_id.get_mut(&claim.requires().element_id)
{
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_by_required_element_id.remove(&claim.requires().element_id);
}
}
if let Some(claim_ids) = self.claims_by_lease.get_mut(&claim.lease_id) {
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_by_lease.remove(&claim.lease_id);
}
}
if let Some(claim_ids) =
self.claims_to_drop_by_element_id.get_mut(&claim.dependent().element_id)
{
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_to_drop_by_element_id.remove(&claim.dependent().element_id);
}
}
Some(claim)
}
/// Marks all claims associated with this lease to drop. They will be
/// removed in an orderly sequence (each claim will be removed only once
/// all claims dependent on it have already been dropped).
/// Returns a Vec of Claims marked to drop.
fn mark_lease_claims_to_drop(&mut self, lease_id: &LeaseID) -> Vec<Claim> {
let claims_marked = self.for_lease(lease_id);
for claim in &claims_marked {
self.claims_to_drop_by_element_id
.entry(claim.dependent().element_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
}
claims_marked
}
/// Removes claim from this tracker, and adds it to recipient.
fn move_to(&mut self, id: &ClaimID, recipient: &mut ClaimTracker) {
if let Some(claim) = self.remove(id) {
recipient.add(claim);
}
}
fn for_claim_ids(&self, claim_ids: &Vec<ClaimID>) -> Vec<Claim> {
claim_ids.iter().map(|id| self.claims.get(id)).filter_map(|f| f).cloned().collect()
}
fn for_required_element(&self, element_id: &ElementID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_by_required_element_id.get(element_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
fn for_lease(&self, lease_id: &LeaseID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_by_lease.get(lease_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
fn to_drop_for_element(&self, element_id: &ElementID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_to_drop_by_element_id.get(element_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
}
/// SubscribeMap is a wrapper around a HashMap that stores values V by key K
/// and allows subscribers to register a channel on which they will receive
/// updates whenever the value stored changes.
#[derive(Debug)]
struct SubscribeMap<K: Clone + Hash + Eq, V: Clone + PartialEq> {
values: HashMap<K, V>,
senders: HashMap<K, Vec<UnboundedSender<Option<V>>>>,
}
impl<K: Clone + Hash + Eq, V: Clone + PartialEq> SubscribeMap<K, V> {
fn new() -> Self {
SubscribeMap { values: HashMap::new(), senders: HashMap::new() }
}
fn get(&self, key: &K) -> Option<V> {
self.values.get(key).cloned()
}
fn update(&mut self, key: &K, value: V) {
// If the value hasn't changed, this is a no-op.
if let Some(current) = self.get(key) {
if current == value {
return;
}
}
self.values.insert(key.clone(), value.clone());
let mut senders_to_retain = Vec::new();
if let Some(senders) = self.senders.remove(&key) {
for sender in senders {
if let Err(err) = sender.unbounded_send(Some(value.clone())) {
if err.is_disconnected() {
continue;
}
}
senders_to_retain.push(sender);
}
}
// Prune invalid senders.
self.senders.insert(key.clone(), senders_to_retain);
}
fn subscribe(&mut self, key: &K) -> UnboundedReceiver<Option<V>> {
let (sender, receiver) = unbounded::<Option<V>>();
sender.unbounded_send(self.get(key)).expect("initial send failed");
self.senders.entry(key.clone()).or_insert(Vec::new()).push(sender);
receiver
}
fn remove(&mut self, key: &K) {
self.values.remove(key);
self.senders.remove(key);
}
}
/// A PowerLevel satisfies a required PowerLevel if it is
/// greater than or equal to it on the same scale.
trait SatisfyPowerLevel {
fn satisfies(&self, required: PowerLevel) -> bool;
}
impl SatisfyPowerLevel for PowerLevel {
fn satisfies(&self, required: PowerLevel) -> bool {
self >= &required
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_power_broker::DependencyToken;
use fuchsia_zircon::{self as zx, HandleBased};
use power_broker_client::BINARY_POWER_LEVELS;
#[fuchsia::test]
fn test_binary_satisfy_power_level() {
for (level, required, want) in [
(BinaryPowerLevel::Off.into_primitive(), BinaryPowerLevel::On.into_primitive(), false),
(BinaryPowerLevel::Off.into_primitive(), BinaryPowerLevel::Off.into_primitive(), true),
(BinaryPowerLevel::On.into_primitive(), BinaryPowerLevel::Off.into_primitive(), true),
(BinaryPowerLevel::On.into_primitive(), BinaryPowerLevel::On.into_primitive(), true),
] {
let got = level.satisfies(required);
assert_eq!(
got, want,
"{:?}.satisfies({:?}) = {:?}, want {:?}",
level, required, got, want
);
}
}
#[fuchsia::test]
fn test_user_defined_satisfy_power_level() {
for (level, required, want) in [
(0, 1, false),
(0, 0, true),
(1, 0, true),
(1, 1, true),
(255, 0, true),
(255, 1, true),
(255, 255, true),
(1, 255, false),
(35, 36, false),
(35, 35, true),
] {
let got = level.satisfies(required);
assert_eq!(
got, want,
"{:?}.satisfies({:?}) = {:?}, want {:?}",
level, required, got, want
);
}
}
#[fuchsia::test]
fn test_levels() {
let mut levels = SubscribeMap::<ElementID, PowerLevel>::new();
levels.update(&"A".into(), BinaryPowerLevel::On.into_primitive());
assert_eq!(levels.get(&"A".into()), Some(BinaryPowerLevel::On.into_primitive()));
assert_eq!(levels.get(&"B".into()), None);
levels.update(&"A".into(), BinaryPowerLevel::Off.into_primitive());
levels.update(&"B".into(), BinaryPowerLevel::On.into_primitive());
assert_eq!(levels.get(&"A".into()), Some(BinaryPowerLevel::Off.into_primitive()));
assert_eq!(levels.get(&"B".into()), Some(BinaryPowerLevel::On.into_primitive()));
levels.update(&"UD1".into(), 145);
assert_eq!(levels.get(&"UD1".into()), Some(145));
assert_eq!(levels.get(&"UD2".into()), None);
}
#[fuchsia::test]
fn test_levels_subscribe() {
let mut levels = SubscribeMap::<ElementID, PowerLevel>::new();
let mut receiver_a = levels.subscribe(&"A".into());
let mut receiver_b = levels.subscribe(&"B".into());
levels.update(&"A".into(), BinaryPowerLevel::On.into_primitive());
assert_eq!(levels.get(&"A".into()), Some(BinaryPowerLevel::On.into_primitive()));
assert_eq!(levels.get(&"B".into()), None);
levels.update(&"A".into(), BinaryPowerLevel::Off.into_primitive());
levels.update(&"B".into(), BinaryPowerLevel::On.into_primitive());
assert_eq!(levels.get(&"A".into()), Some(BinaryPowerLevel::Off.into_primitive()));
assert_eq!(levels.get(&"B".into()), Some(BinaryPowerLevel::On.into_primitive()));
let mut received_a = Vec::new();
while let Ok(Some(level)) = receiver_a.try_next() {
received_a.push(level)
}
assert_eq!(
received_a,
vec![
None,
Some(BinaryPowerLevel::On.into_primitive()),
Some(BinaryPowerLevel::Off.into_primitive())
]
);
let mut received_b = Vec::new();
while let Ok(Some(level)) = receiver_b.try_next() {
received_b.push(level)
}
assert_eq!(received_b, vec![None, Some(BinaryPowerLevel::On.into_primitive())]);
}
#[fuchsia::test]
fn test_initialize_current_and_required_levels() {
let mut broker = Broker::new();
let latinum = broker
.add_element(
"Latinum",
7,
vec![5, 2, 7], // unsorted, should still choose the minimum value
vec![],
vec![],
vec![],
)
.expect("add_element failed");
assert_eq!(broker.get_current_level(&latinum), Some(7));
assert_eq!(broker.get_required_level(&latinum), Some(2));
}
#[fuchsia::test]
fn test_add_element_dependency_never_and_unregistered() {
let mut broker = Broker::new();
let token_mithril = DependencyToken::create();
let never_registered_token = DependencyToken::create();
let mithril = broker
.add_element(
"Mithril",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
// This should fail, because the token was never registered.
let add_element_not_authorized_res = broker.add_element(
"Silver",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: never_registered_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
);
assert!(matches!(add_element_not_authorized_res, Err(AddElementError::NotAuthorized)));
// Add element with a valid token should succeed.
broker
.add_element(
"Silver",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
)
.expect("add_element failed");
// Unregister token_mithril, then try to add again, which should fail.
broker
.unregister_dependency_token(
&mithril,
token_mithril.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup failed").into(),
)
.expect("unregister_dependency_token failed");
let add_element_not_authorized_res: Result<ElementID, AddElementError> = broker
.add_element(
"Silver",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
);
assert!(matches!(add_element_not_authorized_res, Err(AddElementError::NotAuthorized)));
}
#[fuchsia::test]
fn test_remove_element() {
let mut broker = Broker::new();
let unobtanium = broker
.add_element(
"Unobtainium",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![],
vec![],
)
.expect("add_element failed");
assert_eq!(broker.element_exists(&unobtanium), true);
broker.remove_element(&unobtanium);
assert_eq!(broker.element_exists(&unobtanium), false);
}
#[fuchsia::test]
fn test_broker_lease_direct() {
// Create a topology of a child element with two direct dependencies.
// P1 <- C -> P2
let mut broker = Broker::new();
let parent1_token = DependencyToken::create();
let parent1: ElementID = broker
.add_element(
"P1",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent1_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent2_token = DependencyToken::create();
let parent2: ElementID = broker
.add_element(
"P2",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent2_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child = broker
.add_element(
"C",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: parent1_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
},
fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: parent2_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
},
],
vec![],
vec![],
)
.expect("add_element failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent1),
BinaryPowerLevel::Off.into_primitive(),
"Parent 1 should start with required level OFF"
);
assert_eq!(
broker.catalog.calculate_required_level(&parent2),
BinaryPowerLevel::Off.into_primitive(),
"Parent 2 should start with required level OFF"
);
// Acquire the lease, which should result in two claims, one
// for each dependency.
let lease = broker
.acquire_lease(&child, BinaryPowerLevel::On.into_primitive())
.expect("acquire failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent1),
BinaryPowerLevel::On.into_primitive(),
"Parent 1 should now have required level ON from direct claim"
);
assert_eq!(
broker.catalog.calculate_required_level(&parent2),
BinaryPowerLevel::On.into_primitive(),
"Parent 2 should now have required level ON from direct claim"
);
// Now drop the lease and verify both claims are also dropped.
broker.drop_lease(&lease.id).expect("drop failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent1),
BinaryPowerLevel::Off.into_primitive(),
"Parent 1 should now have required level OFF from dropped claim"
);
assert_eq!(
broker.catalog.calculate_required_level(&parent2),
BinaryPowerLevel::Off.into_primitive(),
"Parent 2 should now have required level OFF from dropped claim"
);
// Try dropping the lease one more time, which should result in an error.
let extra_drop = broker.drop_lease(&lease.id);
assert!(extra_drop.is_err());
}
#[fuchsia::test]
fn test_broker_lease_transitive() {
// Create a topology of a child element with two chained transitive
// dependencies.
// C -> P -> GP
let mut broker = Broker::new();
let grandparent_token = DependencyToken::create();
let grandparent: ElementID = broker
.add_element(
"GP",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent_token = DependencyToken::create();
let parent: ElementID = broker
.add_element(
"P",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child = broker
.add_element(
"C",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
)
.expect("add_element failed");
broker
.add_dependency(
&parent,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
)
.expect("add_dependency failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
BinaryPowerLevel::Off.into_primitive(),
"Parent should start with required level OFF"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
BinaryPowerLevel::Off.into_primitive(),
"Grandparent should start with required level OFF"
);
// Acquire the lease, which should result in two claims, one
// for the direct parent dependency, and one for the transitive
// grandparent dependency.
let lease = broker
.acquire_lease(&child, BinaryPowerLevel::On.into_primitive())
.expect("acquire failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
BinaryPowerLevel::Off.into_primitive(),
"Parent should now have required level OFF, waiting on Grandparent to turn ON"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
BinaryPowerLevel::On.into_primitive(),
"Grandparent should now have required level ON because of no dependencies"
);
// Raise Grandparent power level to ON, now Parent claim should be active.
broker
.update_current_level(&grandparent, BinaryPowerLevel::On.into_primitive())
.expect("update_current_level failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
BinaryPowerLevel::On.into_primitive(),
"Parent should now have required level ON"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
BinaryPowerLevel::On.into_primitive(),
"Grandparent should now have required level ON"
);
// Now drop the lease and verify Parent claim is dropped, but
// Grandparent claim is not yet dropped.
broker.drop_lease(&lease.id).expect("drop failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
BinaryPowerLevel::Off.into_primitive(),
"Parent should now have required level OFF after lease drop"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
BinaryPowerLevel::On.into_primitive(),
"Grandparent should still have required level ON"
);
// Lower Parent power level to OFF, now Grandparent claim should be
// dropped and should have required level OFF.
broker
.update_current_level(&parent, BinaryPowerLevel::Off.into_primitive())
.expect("update_current_level failed");
tracing::info!("catalog after update_current_level: {:?}", &broker.catalog);
assert_eq!(
broker.catalog.calculate_required_level(&parent),
BinaryPowerLevel::Off.into_primitive(),
"Parent should have required level OFF"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
BinaryPowerLevel::Off.into_primitive(),
"Grandparent should now have required level OFF"
);
}
#[fuchsia::test]
fn test_broker_lease_shared() {
// Create a topology of two child elements with a shared
// parent and grandparent
// C1 \
// > P -> GP
// C2 /
// Child 1 requires Parent at 50 to support its own level of 5.
// Parent requires Grandparent at 200 to support its own level of 50.
// C1 -> P -> GP
// 5 -> 50 -> 200
// Child 2 requires Parent at 30 to support its own level of 3.
// Parent requires Grandparent at 90 to support its own level of 30.
// C2 -> P -> GP
// 3 -> 30 -> 90
// Grandparent has a minimum required level of 10.
// All other elements have a minimum of 0.
let mut broker = Broker::new();
let grandparent_token = DependencyToken::create();
let grandparent: ElementID = broker
.add_element(
"GP",
10,
vec![10, 90, 200],
vec![],
vec![grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent_token = DependencyToken::create();
let parent: ElementID = broker
.add_element(
"P",
0,
vec![0, 30, 50],
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: 50,
requires_token: grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: 200,
},
fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: 30,
requires_token: grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: 90,
},
],
vec![parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child1 = broker
.add_element(
"C1",
0,
vec![0, 5],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: 5,
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: 50,
}],
vec![],
vec![],
)
.expect("add_element failed");
let child2 = broker
.add_element(
"C2",
0,
vec![0, 3],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: 3,
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level: 30,
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initially, Grandparent should have a default required level of 10
// and Parent should have a default required level of 0.
assert_eq!(
broker.catalog.calculate_required_level(&parent),
0,
"Parent should start with required level 0"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
10,
"Grandparent should start with required level at its default of 10"
);
// Acquire lease for Child 1. Initially, Grandparent should have
// required level 200 and Parent should have required level 0
// because Child 1 has a dependency on Parent and Parent has a
// dependency on Grandparent. Grandparent has no dependencies so its
// level should be raised first.
let lease1 = broker.acquire_lease(&child1, 5).expect("acquire failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
0,
"Parent should now have required level 0, waiting on Grandparent to reach required level"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
200,
"Grandparent should now have required level 100 because of parent dependency and it has no dependencies of its own"
);
// Raise Grandparent's current level to 200. Now Parent claim should
// be active, because its dependency on Grandparent is unblocked
// raising its required level to 50.
broker.update_current_level(&grandparent, 200).expect("update_current_level failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
50,
"Parent should now have required level 50"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
200,
"Grandparent should still have required level 200"
);
// Update Parent's current level to 50.
// Parent and Grandparent should have required levels of 50 and 200.
broker.update_current_level(&parent, 50).expect("update_current_level failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
50,
"Parent should now have required level 50"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
200,
"Grandparent should still have required level 200"
);
// Acquire a lease for Child 2. Though Child 2 has nominal
// requirements of Parent at 30 and Grandparent at 100, they are
// superseded by Child 1's requirements of 50 and 200.
let lease2 = broker.acquire_lease(&child2, 3).expect("acquire failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
50,
"Parent should still have required level 50"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
200,
"Grandparent should still have required level 100"
);
// Drop lease for Child 1. Parent's required level should immediately
// drop to 30. Grandparent's required level will remain at 200 for now.
broker.drop_lease(&lease1.id).expect("drop failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
30,
"Parent should still have required level 2 from the second claim"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
200,
"Grandparent should still have required level 100 from the second claim"
);
// Lower Parent's current level to 30. Now Grandparent's required level
// should drop to 90.
broker.update_current_level(&parent, 30).expect("update_current_level failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
30,
"Parent should have required level 30"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
90,
"Grandparent should now have required level 90"
);
// Drop lease for Child 2, Parent should have required level 0.
// Grandparent should still have required level 90.
broker.drop_lease(&lease2.id).expect("drop failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
0,
"Parent should now have required level 0"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
90,
"Grandparent should still have required level 90"
);
// Lower Parent's current level to 0. Grandparent claim should now be
// dropped and have its default required level of 10.
broker.update_current_level(&parent, 0).expect("update_current_level failed");
assert_eq!(
broker.catalog.calculate_required_level(&parent),
0,
"Parent should have required level 0"
);
assert_eq!(
broker.catalog.calculate_required_level(&grandparent),
10,
"Grandparent should now have required level 10"
);
}
#[fuchsia::test]
async fn test_broker_lease_passive() {
// B has an active dependency on A.
// C has a passive dependency on B (and transitively, a passive dependency on A).
// D has an active dependency on B (and transitively, an active dependency on A).
// A B C D
// ON <= ON
// ON <- ON
// ON <======= ON
let mut broker = Broker::new();
let token_a = DependencyToken::create();
let element_a = broker
.add_element(
"A",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup failed").into()],
vec![],
)
.expect("add_element failed");
let token_b_active = DependencyToken::create();
let token_b_passive = DependencyToken::create();
let element_b = broker
.add_element(
"B",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: token_a
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![token_b_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_b_passive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Passive,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: token_b_passive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Active,
dependent_level: BinaryPowerLevel::On.into_primitive(),
requires_token: token_b_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level: BinaryPowerLevel::On.into_primitive(),
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for A and B should be OFF.
// Set A and B's current level to OFF.
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::Off.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::Off.into_primitive())
);
broker
.update_current_level(&element_a, BinaryPowerLevel::Off.into_primitive())
.expect("update_current_level failed");
broker
.update_current_level(&element_b, BinaryPowerLevel::Off.into_primitive())
.expect("update_current_level failed");
// Lease C, A & B's required levels should remain OFF because of
// passive claim.
// Lease C should remain unsatisfied.
// TODO(b/311419716): When we have lease rejection, reject this lease.
let lease_c = broker
.acquire_lease(&element_c, BinaryPowerLevel::On.into_primitive())
.expect("acquire failed");
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::Off.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::Off.into_primitive())
);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
// Lease D, A should have required level ON because of D's transitive active claim.
// Lease C & D should become satisfied.
let lease_d = broker
.acquire_lease(&element_d, BinaryPowerLevel::On.into_primitive())
.expect("acquire failed");
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::On.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::Off.into_primitive())
);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
// Update A's current level to ON.
// B should now have required level ON because of D's active claim and
// its dependency on A being satisfied.
broker
.update_current_level(&element_a, BinaryPowerLevel::On.into_primitive())
.expect("update_current_level failed");
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::On.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::On.into_primitive())
);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
// Update B's current level to ON.
// Lease C & D should become satisfied.
broker
.update_current_level(&element_b, BinaryPowerLevel::On.into_primitive())
.expect("update_current_level failed");
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
// Drop Lease on D, B's required level should become OFF.
broker.drop_lease(&lease_d.id).expect("drop_lease failed");
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::On.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::Off.into_primitive())
);
// TODO(b/308659273): When we have lease revocation, verify lease C was revoked.
// Update B's current level to OFF. A's required level should become OFF.
broker
.update_current_level(&element_b, BinaryPowerLevel::Off.into_primitive())
.expect("update_current_level failed");
assert_eq!(
broker.get_required_level(&element_a),
Some(BinaryPowerLevel::Off.into_primitive())
);
assert_eq!(
broker.get_required_level(&element_b),
Some(BinaryPowerLevel::Off.into_primitive())
);
}
#[fuchsia::test]
fn test_add_remove_dependency() {
let mut broker = Broker::new();
let token_active_adamantium = DependencyToken::create();
let token_passive_adamantium = DependencyToken::create();
let token_active_vibranium = DependencyToken::create();
let token_passive_vibranium = DependencyToken::create();
let adamantium = broker
.add_element(
"Adamantium",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_active_adamantium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_passive_adamantium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
broker
.add_element(
"Vibranium",
BinaryPowerLevel::Off.into_primitive(),
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_active_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
// Adding should return NotAuthorized if token is not registered
let unregistered_token = DependencyToken::create();
let res_add_not_authorized = broker.add_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
unregistered_token.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(matches!(res_add_not_authorized, Err(ModifyDependencyError::NotAuthorized)));
// Adding should return NotAuthorized if token is of wrong dependency type
let res_add_wrong_type = broker.add_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(matches!(res_add_wrong_type, Err(ModifyDependencyError::NotAuthorized)));
// Add of invalid dependent level should fail.
const INVALID_POWER_LEVEL: PowerLevel = 5;
let res_add_invalid_dep_level = broker.add_dependency(
&adamantium,
DependencyType::Active,
INVALID_POWER_LEVEL,
token_active_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(
matches!(res_add_invalid_dep_level, Err(ModifyDependencyError::Invalid)),
"{:?}",
res_add_invalid_dep_level
);
// Add of invalid required level should fail.
let res_add_invalid_req_level = broker.add_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
token_active_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
INVALID_POWER_LEVEL,
);
assert!(
matches!(res_add_invalid_req_level, Err(ModifyDependencyError::Invalid)),
"{:?}",
res_add_invalid_req_level
);
// Valid add of active type should succeed
broker
.add_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
token_active_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
)
.expect("add_dependency failed");
// Adding an overlapping dependency with a different type should fail
let res_add_overlap = broker.add_dependency(
&adamantium,
DependencyType::Passive,
BinaryPowerLevel::On.into_primitive(),
token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(
matches!(res_add_overlap, Err(ModifyDependencyError::AlreadyExists)),
"{res_add_overlap:?}"
);
// Removing should return NotAuthorized if token is not registered
let unregistered_token = DependencyToken::create();
let res_remove_not_authorized = broker.remove_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
unregistered_token.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(matches!(res_remove_not_authorized, Err(ModifyDependencyError::NotAuthorized)));
// Removing should return NotAuthorized if token is of wrong type
let res_remove_wrong_type = broker.remove_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
);
assert!(matches!(res_remove_wrong_type, Err(ModifyDependencyError::NotAuthorized)));
// Valid remove of active type should succeed
broker
.remove_dependency(
&adamantium,
DependencyType::Active,
BinaryPowerLevel::On.into_primitive(),
token_active_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
)
.expect("remove_dependency failed");
// Valid add of passive type should succeed
broker
.add_dependency(
&adamantium,
DependencyType::Passive,
BinaryPowerLevel::On.into_primitive(),
token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
)
.expect("add_dependency failed");
// Valid remove of passive type should succeed
broker
.remove_dependency(
&adamantium,
DependencyType::Passive,
BinaryPowerLevel::On.into_primitive(),
token_passive_vibranium
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
BinaryPowerLevel::On.into_primitive(),
)
.expect("remove_dependency failed");
}
}