power_broker_client/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use anyhow::{anyhow, Result};
5use fidl::endpoints::create_proxy;
6use fuchsia_inspect::Property;
7use futures::future::{FutureExt, LocalBoxFuture};
8use std::sync::Arc;
9use zx::{HandleBased, Rights};
10use {fidl_fuchsia_power_broker as fbroker, fuchsia_async as fasync};
11
12/// A well-known set of PowerLevels to be specified as the valid_levels for a
13/// power element. This is the set of levels in fbroker::BinaryPowerLevel.
14pub const BINARY_POWER_LEVELS: [fbroker::PowerLevel; 2] = [
15    fbroker::BinaryPowerLevel::Off.into_primitive(),
16    fbroker::BinaryPowerLevel::On.into_primitive(),
17];
18
19pub struct PowerElementContext {
20    pub element_control: fbroker::ElementControlProxy,
21    pub lessor: fbroker::LessorProxy,
22    pub required_level: fbroker::RequiredLevelProxy,
23    pub current_level: fbroker::CurrentLevelProxy,
24    assertive_dependency_token: Option<fbroker::DependencyToken>,
25    opportunistic_dependency_token: Option<fbroker::DependencyToken>,
26    name: String,
27}
28
29impl PowerElementContext {
30    pub fn builder<'a>(
31        topology: &'a fbroker::TopologyProxy,
32        element_name: &'a str,
33        valid_levels: &'a [fbroker::PowerLevel],
34    ) -> PowerElementContextBuilder<'a> {
35        PowerElementContextBuilder::new(topology, element_name, valid_levels)
36    }
37
38    pub fn assertive_dependency_token(&self) -> Option<fbroker::DependencyToken> {
39        self.assertive_dependency_token.as_ref().and_then(|token| {
40            Some(token.duplicate_handle(Rights::SAME_RIGHTS).expect("failed to duplicate token"))
41        })
42    }
43
44    pub fn opportunistic_dependency_token(&self) -> Option<fbroker::DependencyToken> {
45        self.opportunistic_dependency_token.as_ref().and_then(|token| {
46            Some(token.duplicate_handle(Rights::SAME_RIGHTS).expect("failed to duplicate token"))
47        })
48    }
49
50    pub fn name(&self) -> &str {
51        &self.name
52    }
53}
54
55pub struct PowerElementContextBuilder<'a> {
56    topology: &'a fbroker::TopologyProxy,
57    element_name: &'a str,
58    initial_current_level: fbroker::PowerLevel,
59    valid_levels: &'a [fbroker::PowerLevel],
60    dependencies: Vec<fbroker::LevelDependency>,
61    register_dependency_tokens: bool,
62}
63
64impl<'a> PowerElementContextBuilder<'a> {
65    pub fn new(
66        topology: &'a fbroker::TopologyProxy,
67        element_name: &'a str,
68        valid_levels: &'a [fbroker::PowerLevel],
69    ) -> Self {
70        Self {
71            topology,
72            element_name,
73            valid_levels,
74            initial_current_level: Default::default(),
75            dependencies: Default::default(),
76            register_dependency_tokens: true,
77        }
78    }
79
80    pub fn initial_current_level(mut self, value: fbroker::PowerLevel) -> Self {
81        self.initial_current_level = value;
82        self
83    }
84
85    pub fn dependencies(mut self, value: Vec<fbroker::LevelDependency>) -> Self {
86        self.dependencies = value;
87        self
88    }
89
90    pub fn register_dependency_tokens(mut self, enable: bool) -> Self {
91        self.register_dependency_tokens = enable;
92        self
93    }
94
95    pub async fn build(self) -> Result<PowerElementContext> {
96        let (current_level, current_level_server_end) =
97            create_proxy::<fbroker::CurrentLevelMarker>();
98        let (required_level, required_level_server_end) =
99            create_proxy::<fbroker::RequiredLevelMarker>();
100        let (lessor, lessor_server_end) = create_proxy::<fbroker::LessorMarker>();
101        let (element_control, element_control_server_end) =
102            create_proxy::<fbroker::ElementControlMarker>();
103        self.topology
104            .add_element(fbroker::ElementSchema {
105                element_name: Some(self.element_name.into()),
106                initial_current_level: Some(self.initial_current_level),
107                valid_levels: Some(self.valid_levels.to_vec()),
108                dependencies: Some(self.dependencies),
109                level_control_channels: Some(fbroker::LevelControlChannels {
110                    current: current_level_server_end,
111                    required: required_level_server_end,
112                }),
113                lessor_channel: Some(lessor_server_end),
114                element_control: Some(element_control_server_end),
115                ..Default::default()
116            })
117            .await?
118            .map_err(|d| anyhow::anyhow!("{d:?}"))?;
119
120        let assertive_dependency_token = match self.register_dependency_tokens {
121            true => {
122                let token = fbroker::DependencyToken::create();
123                let _ = element_control
124                    .register_dependency_token(
125                        token
126                            .duplicate_handle(Rights::SAME_RIGHTS)
127                            .expect("failed to duplicate token"),
128                        fbroker::DependencyType::Assertive,
129                    )
130                    .await?
131                    .expect("register assertive dependency token");
132                Some(token)
133            }
134            false => None,
135        };
136
137        let opportunistic_dependency_token = match self.register_dependency_tokens {
138            true => {
139                let token = fbroker::DependencyToken::create();
140                let _ = element_control
141                    .register_dependency_token(
142                        token
143                            .duplicate_handle(Rights::SAME_RIGHTS)
144                            .expect("failed to duplicate token"),
145                        fbroker::DependencyType::Opportunistic,
146                    )
147                    .await?
148                    .expect("register opportunistic dependency token");
149                Some(token)
150            }
151            false => None,
152        };
153
154        Ok(PowerElementContext {
155            element_control,
156            lessor,
157            required_level,
158            current_level,
159            assertive_dependency_token,
160            opportunistic_dependency_token,
161            name: self.element_name.to_string(),
162        })
163    }
164}
165
166/// Creates an update function for run_power_element that only updates `power_element``.
167///
168/// This helper function can be used to create an update function that has no side effects or
169/// conditions when the power level of the given power element changes.
170pub fn basic_update_fn_factory<'a>(
171    power_element: &'a PowerElementContext,
172) -> Box<dyn Fn(fbroker::PowerLevel) -> LocalBoxFuture<'a, ()> + 'a> {
173    Box::new(move |new_power_level: fbroker::PowerLevel| {
174        async move {
175            let element_name = power_element.name();
176
177            log::debug!(
178                element_name:?,
179                new_power_level:?;
180                "basic_update_fn_factory: updating current level"
181            );
182
183            let res = power_element.current_level.update(new_power_level).await;
184            if let Err(error) = res {
185                log::warn!(
186                    element_name:?,
187                    error:?;
188                    "basic_update_fn_factory: updating current level failed"
189                );
190            }
191        }
192        .boxed_local()
193    })
194}
195
196/// Runs a procedure that calls an update function when the required power level changes.
197///
198/// The power element's power level is expected to be updated in `update_fn``.
199/// A minimal update function can be created by calling `basic_update_fn_factory`.
200pub async fn run_power_element<'a>(
201    element_name: &'a str,
202    required_level_proxy: &'a fbroker::RequiredLevelProxy,
203    initial_level: fbroker::PowerLevel,
204    inspect_node: Option<fuchsia_inspect::Node>,
205    update_fn: Box<dyn Fn(fbroker::PowerLevel) -> LocalBoxFuture<'a, ()> + 'a>,
206) {
207    let mut last_required_level = initial_level;
208    let power_level_node = inspect_node
209        .as_ref()
210        .map(|node| node.create_uint("power_level", last_required_level.into()));
211
212    loop {
213        log::debug!(
214            element_name:?,
215            last_required_level:?;
216            "run_power_element: waiting for new level"
217        );
218        match required_level_proxy.watch().await {
219            Ok(Ok(required_level)) => {
220                log::debug!(
221                    element_name:?,
222                    required_level:?,
223                    last_required_level:?;
224                    "run_power_element: new level requested"
225                );
226                if required_level == last_required_level {
227                    log::debug!(
228                        element_name:?,
229                        required_level:?,
230                        last_required_level:?;
231                        "run_power_element: required level has not changed, skipping."
232                    );
233                    continue;
234                }
235
236                update_fn(required_level).await;
237                if let Some(ref power_level_node) = power_level_node {
238                    power_level_node.set(required_level.into());
239                }
240                last_required_level = required_level;
241            }
242            error => {
243                log::warn!(element_name:?, error:?; "run_power_element: watch_required_level failed");
244                return;
245            }
246        }
247    }
248}
249
250/// A dependency for a lease. It is equivalent to an fbroker::LevelDependency with the dependent
251/// fields omitted.
252pub struct LeaseDependency {
253    pub dependency_type: fbroker::DependencyType,
254    pub requires_token: fbroker::DependencyToken,
255    pub requires_level_by_preference: Vec<fbroker::PowerLevel>,
256}
257
258/// Helper for acquiring leases. Instantiate with LeaseControl::new(), and then acquire a lease with
259/// the lease() method. The lease() call will return only once the lease is satisfied.
260///
261/// A single LeaseHelper may be reused to create leases an arbitrary number of times.
262pub struct LeaseHelper {
263    lessor: fbroker::LessorProxy,
264    _element_runner: fasync::Task<()>,
265}
266
267pub struct Lease {
268    /// This may be used to further monitor the lease status, if desired, beyond the
269    /// await-until-satisfied behavior of LeaseHelper::lease().
270    pub control_proxy: fbroker::LeaseControlProxy,
271
272    // The originating LeaseHelper must be kept alive as long as the lease to keep its associated
273    // power element running.
274    _helper: Arc<LeaseHelper>,
275}
276
277impl Lease {
278    pub async fn wait_until_satisfied(&self) -> Result<(), fidl::Error> {
279        let mut status = fbroker::LeaseStatus::Unknown;
280        loop {
281            match self.control_proxy.watch_status(status).await? {
282                fbroker::LeaseStatus::Satisfied => break Ok(()),
283                new_status @ _ => status = new_status,
284            }
285        }
286    }
287}
288
289impl LeaseHelper {
290    /// Creates a new LeaseHelper. Returns an error upon failure to register the to-be-leased power
291    /// element with Power Broker.
292    pub async fn new<'a>(
293        topology: &'a fbroker::TopologyProxy,
294        name: &'a str,
295        lease_dependencies: Vec<LeaseDependency>,
296    ) -> Result<Arc<Self>> {
297        let level_dependencies = lease_dependencies
298            .into_iter()
299            .map(|d| fbroker::LevelDependency {
300                dependency_type: d.dependency_type,
301                dependent_level: BINARY_POWER_LEVELS[1],
302                requires_token: d.requires_token,
303                requires_level_by_preference: d.requires_level_by_preference,
304            })
305            .collect();
306
307        let element_context = PowerElementContext::builder(topology, name, &BINARY_POWER_LEVELS)
308            .dependencies(level_dependencies)
309            .initial_current_level(BINARY_POWER_LEVELS[0])
310            .build()
311            .await?;
312
313        let lessor = element_context.lessor.clone();
314
315        let _element_runner = fasync::Task::local(async move {
316            run_power_element(
317                &element_context.name(),
318                &element_context.required_level,
319                BINARY_POWER_LEVELS[0], /* initial_level */
320                None,                   /* inspect_node */
321                basic_update_fn_factory(&element_context),
322            )
323            .await;
324        });
325
326        Ok(Arc::new(Self { lessor, _element_runner }))
327    }
328
329    /// Acquires a lease, completing only once the lease is satisfied. Returns an error if the
330    /// underlying `Lessor.Lease` or `LeaseControl.WatchStatus` call fails.
331    pub async fn create_lease_and_wait_until_satisfied(self: &Arc<Self>) -> Result<Lease> {
332        let lease = self.create_lease().await?;
333        lease.wait_until_satisfied().await?;
334        Ok(lease)
335    }
336
337    pub async fn create_lease(self: &Arc<Self>) -> Result<Lease> {
338        let lease = self
339            .lessor
340            .lease(BINARY_POWER_LEVELS[1])
341            .await?
342            .map_err(|e| anyhow!("PowerBroker::LeaseError({e:?})"))?;
343        Ok(Lease { control_proxy: lease.into_proxy(), _helper: self.clone() })
344    }
345}
346
347// TODO(https://fxbug.dev/349841776): Use this as a demo case for test library support for faking
348// Power Broker interfaces.
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use diagnostics_assertions::assert_data_tree;
353    use fuchsia_async as fasync;
354    use futures::channel::mpsc;
355    use futures::StreamExt;
356    use std::cell::RefCell;
357    use std::rc::Rc;
358
359    fn run_required_level_server(
360        mut required_power_levels: Vec<fbroker::PowerLevel>,
361    ) -> fbroker::RequiredLevelProxy {
362        let (required_level_proxy, mut required_level_stream) =
363            fidl::endpoints::create_proxy_and_stream::<fbroker::RequiredLevelMarker>();
364
365        fasync::Task::local(async move {
366            while let Some(Ok(request)) = required_level_stream.next().await {
367                match request {
368                    fbroker::RequiredLevelRequest::Watch { responder } => {
369                        responder
370                            .send(
371                                required_power_levels
372                                    .pop()
373                                    .ok_or_else(|| fbroker::RequiredLevelError::Internal),
374                            )
375                            .unwrap();
376                    }
377                    _ => unreachable!("Unexpected method call"),
378                }
379            }
380        })
381        .detach();
382
383        required_level_proxy
384    }
385
386    #[fuchsia::test]
387    async fn basic_update_fn_factory_performs_update() -> Result<()> {
388        let (element_control, _element_control_stream) =
389            fidl::endpoints::create_proxy_and_stream::<fbroker::ElementControlMarker>();
390        let (lessor, _lessor_stream) =
391            fidl::endpoints::create_proxy_and_stream::<fbroker::LessorMarker>();
392        let (required_level, _required_level_stream) =
393            fidl::endpoints::create_proxy_and_stream::<fbroker::RequiredLevelMarker>();
394        let (current_level, mut current_level_stream) =
395            fidl::endpoints::create_proxy_and_stream::<fbroker::CurrentLevelMarker>();
396
397        let power_element = PowerElementContext {
398            element_control,
399            lessor,
400            required_level,
401            current_level,
402            assertive_dependency_token: Some(fbroker::DependencyToken::create()),
403            opportunistic_dependency_token: Some(fbroker::DependencyToken::create()),
404            name: "test_name".to_string(),
405        };
406
407        fasync::Task::local(async move {
408            let update_fn = basic_update_fn_factory(&power_element);
409            update_fn(100).await;
410        })
411        .detach();
412
413        let Some(Ok(fbroker::CurrentLevelRequest::Update { current_level, responder })) =
414            current_level_stream.next().await
415        else {
416            unreachable!();
417        };
418
419        responder.send(Ok(())).unwrap();
420        assert_eq!(100, current_level);
421        Ok(())
422    }
423
424    #[fuchsia::test]
425    async fn run_power_element_passes_required_level_to_update_fn() -> Result<()> {
426        let (tx, mut rx) = mpsc::channel(5);
427        let required_level_proxy = run_required_level_server(vec![1, 2]);
428
429        run_power_element(
430            "test_element",
431            &required_level_proxy,
432            0,
433            None,
434            Box::new(|power_level| {
435                let mut tx = tx.clone();
436                async move {
437                    tx.start_send(power_level).unwrap();
438                }
439                .boxed_local()
440            }),
441        )
442        .await;
443
444        assert_eq!(2, rx.next().await.unwrap());
445        assert_eq!(1, rx.next().await.unwrap());
446        Ok(())
447    }
448
449    #[fuchsia::test]
450    async fn run_power_element_skips_update_on_same_level() -> Result<()> {
451        let (tx, mut rx) = mpsc::channel(5);
452        let initial_level = 5;
453        let required_level_proxy = run_required_level_server(vec![3, 1, 1, 2, 2, initial_level]);
454
455        run_power_element(
456            "test_element",
457            &required_level_proxy,
458            initial_level,
459            None,
460            Box::new(|power_level| {
461                let mut tx = tx.clone();
462                async move {
463                    tx.start_send(power_level).unwrap();
464                }
465                .boxed_local()
466            }),
467        )
468        .await;
469
470        assert_eq!(2, rx.next().await.unwrap());
471        assert_eq!(1, rx.next().await.unwrap());
472        assert_eq!(3, rx.next().await.unwrap());
473        Ok(())
474    }
475
476    #[fuchsia::test]
477    async fn run_power_element_updates_inspect_node() -> Result<()> {
478        let inspector = fuchsia_inspect::Inspector::default();
479        let (mut tx, rx) = mpsc::channel(5);
480        let (tx2, mut rx2) = mpsc::channel(5);
481        let rx = Rc::new(RefCell::new(rx));
482        let required_level_proxy = run_required_level_server(vec![1, 4, 0, 3]);
483
484        let root = inspector.root().clone_weak();
485        fasync::Task::local(async move {
486            run_power_element(
487                "test_element",
488                &required_level_proxy,
489                0,
490                Some(root),
491                Box::new(|_| {
492                    let rx = rx.clone();
493                    let mut tx2 = tx2.clone();
494                    async move {
495                        tx2.start_send(()).unwrap();
496                        rx.borrow_mut().next().await.unwrap();
497                    }
498                    .boxed_local()
499                }),
500            )
501            .await;
502        })
503        .detach();
504
505        // The first communication hasn't updated the tree yet.
506        rx2.next().await.unwrap();
507        tx.start_send(()).unwrap();
508
509        // Now that the update function has been called twice, the inspect tree
510        // should show the first power level. This pattern continues
511        rx2.next().await.unwrap();
512        assert_data_tree!(inspector, root: {
513            power_level: 3u64
514        });
515        tx.start_send(()).unwrap();
516
517        rx2.next().await.unwrap();
518        assert_data_tree!(inspector, root: {
519            power_level: 0u64
520        });
521        tx.start_send(()).unwrap();
522
523        rx2.next().await.unwrap();
524        assert_data_tree!(inspector, root: {
525            power_level: 4u64
526        });
527        Ok(())
528    }
529}