blob: b58b4d7ec9021d45aa9f6032d79db8da45ea5646 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_bluetooth_power::{Information, ReporterRequest, ReporterRequestStream};
use fuchsia_bluetooth::types::PeerId;
use fuchsia_zircon as zx;
use futures::{Future, TryStreamExt};
use std::sync::Arc;
use tracing::debug;
use crate::error::Error;
use crate::peripheral_state::{peer_id_from_identifier, BatteryInfo, PeripheralState};
/// Represents a handler for a client connection to the `fuchsia.bluetooth.power.Reporter` FIDL
/// capability.
pub struct Reporter {
shared_state: Arc<PeripheralState>,
}
impl Reporter {
pub fn new(shared_state: Arc<PeripheralState>) -> Self {
Self { shared_state }
}
fn validate_information(info: Information) -> Result<(PeerId, BatteryInfo), Error> {
// The `identifier` and `battery_info` are mandatory fields.
let identifier = info.identifier.clone().ok_or(Error::from(&info))?;
let battery_info =
BatteryInfo::try_from(info.battery_info.clone().ok_or(Error::from(&info))?)?;
// Currently, there is nothing to do if the local_device is specified. This component only
// handles updates about peripherals.
let id = peer_id_from_identifier(&identifier)?;
Ok((id, battery_info))
}
fn handle_power_report_request(&self, report: ReporterRequest) -> Result<(), Error> {
debug!("Received Reporter::Report request: {:?}", report);
// There is only one method in the `power.Reporter` protocol.
let (report, responder) = report.into_report().expect("Reporter::Report request");
let (id, battery_info) = match Self::validate_information(report) {
Ok((id, b)) => {
let _ = responder.send(Ok(()))?;
(id, b)
}
Err(e) => {
let _ = responder.send(Err(zx::Status::from(&e).into_raw()))?;
return Ok(());
}
};
self.shared_state.record_power_update(id, battery_info);
Ok(())
}
pub fn run(self, stream: ReporterRequestStream) -> impl Future<Output = Result<(), Error>> {
stream.map_err(Into::into).try_for_each(move |report| {
futures::future::ready(self.handle_power_report_request(report))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fidl_fuchsia_bluetooth_power::{Identifier, ReporterMarker, ReporterProxy};
use futures::{select, FutureExt};
use std::pin::pin;
fn make_reporter_task(
) -> (impl Future<Output = Result<(), Error>>, ReporterProxy, Arc<PeripheralState>) {
let shared_state = Arc::new(PeripheralState::new());
let server = Reporter::new(shared_state.clone());
let (c, s) = fidl::endpoints::create_proxy_and_stream::<ReporterMarker>().unwrap();
let server_task = server.run(s);
(server_task, c, shared_state)
}
#[fuchsia::test]
async fn server_task_finishes_when_client_end_closes() {
let (reporter_task, reporter_proxy, _state) = make_reporter_task();
drop(reporter_proxy);
let result = reporter_task.await;
assert_matches!(result, Ok(_));
}
#[fuchsia::test]
async fn invalid_request_is_error() {
let (reporter_task, reporter_proxy, _state) = make_reporter_task();
let mut server_task = pin!(reporter_task.fuse());
let info = Information {
identifier: Some(Identifier::PeerId(PeerId(123).into())),
battery_info: Some(fidl_fuchsia_power_battery::BatteryInfo::default()),
..Default::default()
};
let mut report_request_fut = pin!(reporter_proxy.report(&info));
select! {
_ = server_task => panic!("Server shouldn't terminate"),
result = report_request_fut => {
assert_eq!(result.expect("valid fidl response"), Err(zx::Status::INVALID_ARGS.into_raw()));
}
}
}
#[fuchsia::test]
async fn valid_power_report_is_saved() {
let (reporter_task, reporter_proxy, state) = make_reporter_task();
let mut server_task = pin!(reporter_task.fuse());
let id = PeerId(123);
let info = Information {
identifier: Some(Identifier::PeerId(id.into())),
battery_info: Some(fidl_fuchsia_power_battery::BatteryInfo {
level_percent: Some(5.0f32),
..Default::default()
}),
..Default::default()
};
let mut report_request_fut = pin!(reporter_proxy.report(&info));
select! {
_ = server_task => panic!("Server shouldn't terminate"),
result = report_request_fut => {
assert_matches!(result.expect("valid fidl response"), Ok(_));
}
}
assert!(state.contains_entry(&id));
}
}