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