settings/handler/
setting_handler.rs

1// Copyright 2020 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 crate::base::{HasSettingType, SettingInfo, SettingType};
5use crate::handler::base::{Context, ControllerGenerateResult, Request};
6use crate::message::base::Audience;
7use crate::service::message::{MessageClient, Messenger, Signature};
8use crate::service_context::ServiceContext;
9use crate::{payload_convert, trace, trace_guard};
10use async_trait::async_trait;
11use core::convert::TryFrom;
12use fuchsia_async as fasync;
13use futures::future::LocalBoxFuture;
14use futures::lock::Mutex;
15use settings_storage::device_storage::DeviceStorage;
16use settings_storage::storage_factory::StorageFactory as StorageFactoryTrait;
17use std::borrow::Cow;
18use std::marker::PhantomData;
19use std::rc::Rc;
20use thiserror::Error;
21
22pub type ExitResult = Result<(), ControllerError>;
23pub type SettingHandlerResult = Result<Option<SettingInfo>, ControllerError>;
24/// Return type from a controller after handling a state change.
25pub type ControllerStateResult = Result<(), ControllerError>;
26
27// The types of data that can be sent to and from a setting controller.
28#[derive(Clone, Debug, PartialEq)]
29pub enum Payload {
30    // Sent to the controller to request an action is taken.
31    Command(Command),
32    // Sent from the controller adhoc to indicate an event has happened.
33    Event(Event),
34    // Sent in response to a request.
35    Result(SettingHandlerResult),
36}
37
38payload_convert!(Controller, Payload);
39
40/// An command sent to the controller to take a particular action.
41#[derive(Debug, Clone, PartialEq)]
42pub enum Command {
43    HandleRequest(Request),
44    ChangeState(State),
45}
46
47impl TryFrom<crate::handler::setting_handler::Payload> for Command {
48    type Error = &'static str;
49
50    fn try_from(value: crate::handler::setting_handler::Payload) -> Result<Self, Self::Error> {
51        match value {
52            crate::handler::setting_handler::Payload::Command(command) => Ok(command),
53            _ => Err("wrong payload type"),
54        }
55    }
56}
57
58#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
59pub enum State {
60    /// State of a controller immediately after it is created. Intended
61    /// to initialize state on the controller.
62    Startup,
63
64    /// State of a controller when at least one client is listening on
65    /// changes to the setting state.
66    Listen,
67
68    /// State of a controller when there are no more clients listening
69    /// on changes to the setting state.
70    EndListen,
71
72    /// State of a controller when there are no requests or listeners on
73    /// the setting type. Intended to tear down state before taking down
74    /// the controller.
75    Teardown,
76}
77
78/// Events are sent from the setting handler back to the parent
79/// proxy to indicate changes that happen out-of-band (happening
80/// outside of response to a Command above). They indicate a
81/// change in the handler that should potentially be handled by
82/// the proxy.
83#[derive(Clone, Debug, PartialEq)]
84pub enum Event {
85    // Sent when the publicly perceived values of the setting
86    // handler have been changed.
87    Changed(SettingInfo),
88    Exited(ExitResult),
89    StateChanged(State),
90}
91
92#[allow(dead_code)]
93pub(crate) trait StorageFactory: StorageFactoryTrait {}
94impl<T: StorageFactoryTrait> StorageFactory for T {}
95
96#[derive(Error, Debug, Clone, PartialEq)]
97pub enum ControllerError {
98    #[error("Unimplemented Request:{1:?} for setting type: {0:?}")]
99    UnimplementedRequest(SettingType, Request),
100    #[error("Write failed. setting type: {0:?}")]
101    WriteFailure(SettingType),
102    #[error("Initialization failure: cause {0:?}")]
103    InitFailure(Cow<'static, str>),
104    #[error("Restoration of setting on controller startup failed: cause {0:?}")]
105    RestoreFailure(Cow<'static, str>),
106    #[error(
107        "Call to an external dependency {1:?} for setting type {0:?} failed. \
108         Request:{2:?}: Error:{3}"
109    )]
110    ExternalFailure(SettingType, Cow<'static, str>, Cow<'static, str>, Cow<'static, str>),
111    #[error("Invalid input argument for setting type: {0:?} argument:{1:?} value:{2:?}")]
112    InvalidArgument(SettingType, Cow<'static, str>, Cow<'static, str>),
113    #[error(
114        "Incompatible argument values passed: {setting_type:?} argument:{main_arg:?} cannot be \
115         combined with arguments:[{other_args:?}] with respective values:[{values:?}]. {reason:?}"
116    )]
117    IncompatibleArguments {
118        setting_type: SettingType,
119        main_arg: Cow<'static, str>,
120        other_args: Cow<'static, str>,
121        values: Cow<'static, str>,
122        reason: Cow<'static, str>,
123    },
124    #[error("Unhandled type: {0:?}")]
125    UnhandledType(SettingType),
126    #[error("Unexpected error: {0:?}")]
127    UnexpectedError(Cow<'static, str>),
128    #[error("Undeliverable Request:{1:?} for setting type: {0:?}")]
129    UndeliverableError(SettingType, Request),
130    #[error("Unsupported request for setting type: {0:?}")]
131    UnsupportedError(SettingType),
132    #[error("Delivery error for type: {0:?} received by: {1:?}")]
133    DeliveryError(SettingType, SettingType),
134    #[error("Irrecoverable error")]
135    IrrecoverableError,
136    #[error("Timeout occurred")]
137    TimeoutError,
138    #[error("Exit occurred")]
139    ExitError,
140}
141
142pub(crate) type BoxedController = Box<dyn controller::Handle>;
143pub(crate) type BoxedControllerResult = Result<BoxedController, ControllerError>;
144
145pub(crate) type GenerateController =
146    Box<dyn Fn(Rc<ClientImpl>) -> LocalBoxFuture<'static, BoxedControllerResult>>;
147
148pub(crate) mod controller {
149    use super::*;
150
151    #[async_trait(?Send)]
152    #[cfg(test)]
153    pub(crate) trait Create: Sized {
154        async fn create(client: Rc<ClientImpl>) -> Result<Self, ControllerError>;
155    }
156
157    #[async_trait(?Send)]
158    pub(crate) trait Handle {
159        /// Handles an incoming request and returns its result. If the request is not supported
160        /// by this implementation, then None is returned.
161        async fn handle(&self, request: Request) -> Option<SettingHandlerResult>;
162
163        /// Handles a state change, and returns the new state if it was updated, else returns None.
164        async fn change_state(&mut self, _state: State) -> Option<ControllerStateResult> {
165            None
166        }
167    }
168}
169
170pub struct ClientImpl {
171    // TODO(https://fxbug.dev/42166874): Use AtomicBool or Cell.
172    notify: Mutex<bool>,
173    messenger: Messenger,
174    notifier_signature: Signature,
175    service_context: Rc<ServiceContext>,
176    setting_type: SettingType,
177}
178
179impl ClientImpl {
180    fn new(context: &Context) -> Self {
181        Self {
182            messenger: context.messenger.clone(),
183            setting_type: context.setting_type,
184            notifier_signature: context.notifier_signature,
185            notify: Mutex::new(false),
186            service_context: Rc::clone(&context.environment.service_context),
187        }
188    }
189
190    /// Test constructor that doesn't require creating a whole [Context].
191    #[cfg(test)]
192    pub fn for_test(
193        notify: Mutex<bool>,
194        messenger: Messenger,
195        notifier_signature: Signature,
196        service_context: Rc<ServiceContext>,
197        setting_type: SettingType,
198    ) -> Self {
199        Self { notify, messenger, notifier_signature, service_context, setting_type }
200    }
201
202    async fn process_request(
203        setting_type: SettingType,
204        controller: &BoxedController,
205        request: Request,
206    ) -> SettingHandlerResult {
207        let result = controller.handle(request.clone()).await;
208        match result {
209            Some(response_result) => response_result,
210            None => Err(ControllerError::UnimplementedRequest(setting_type, request)),
211        }
212    }
213
214    pub(crate) async fn create(
215        mut context: Context,
216        generate_controller: GenerateController,
217    ) -> ControllerGenerateResult {
218        let client = Rc::new(Self::new(&context));
219
220        let mut controller = generate_controller(Rc::clone(&client)).await?;
221
222        // Process MessageHub requests
223        fasync::Task::local(async move {
224            let _ = &context;
225            let id = fuchsia_trace::Id::new();
226            trace!(
227                id,
228                c"setting handler",
229                "setting_type" => format!("{:?}", client.setting_type).as_str()
230            );
231            while let Ok((payload, message_client)) = context.receptor.next_of::<Payload>().await {
232                let setting_type = client.setting_type;
233
234                // Setting handlers should only expect commands
235                match Command::try_from(payload).expect("should only receive commands") {
236                    // Rebroadcasting requires special handling. The handler will request the
237                    // current value from controller and then notify the caller as if it was a
238                    // change in value.
239                    Command::HandleRequest(Request::Rebroadcast) => {
240                        trace!(id, c"handle rebroadcast");
241                        // Fetch the current value
242                        let controller_reply =
243                            Self::process_request(setting_type, &controller, Request::Get).await;
244
245                        // notify proxy of value
246                        if let Ok(Some(info)) = &controller_reply {
247                            client.notify(Event::Changed(info.clone())).await;
248                        }
249
250                        reply(message_client, controller_reply);
251                    }
252                    Command::HandleRequest(request) => {
253                        trace!(id, c"handle request");
254                        reply(
255                            message_client,
256                            Self::process_request(setting_type, &controller, request.clone()).await,
257                        );
258                    }
259                    Command::ChangeState(state) => {
260                        trace!(
261                            id,
262                            c"change state",
263                            "state" => format!("{state:?}").as_str()
264                        );
265                        match state {
266                            State::Startup => {
267                                if let Some(Err(e)) = controller.change_state(state).await {
268                                    log::error!(
269                                        "Failed startup phase for SettingType {:?} {}",
270                                        setting_type,
271                                        e
272                                    );
273                                }
274                                reply(message_client, Ok(None));
275                                continue;
276                            }
277                            State::Listen => {
278                                *client.notify.lock().await = true;
279                            }
280                            State::EndListen => {
281                                *client.notify.lock().await = false;
282                            }
283                            State::Teardown => {
284                                if let Some(Err(e)) = controller.change_state(state).await {
285                                    log::error!(
286                                        "Failed teardown phase for SettingType {:?} {}",
287                                        setting_type,
288                                        e
289                                    );
290                                }
291                                reply(message_client, Ok(None));
292                                continue;
293                            }
294                        }
295
296                        // Ignore whether the state change had any effect.
297                        let _ = controller.change_state(state).await;
298                    }
299                }
300            }
301        })
302        .detach();
303
304        Ok(())
305    }
306
307    pub(crate) fn get_service_context(&self) -> Rc<ServiceContext> {
308        Rc::clone(&self.service_context)
309    }
310
311    pub(crate) async fn notify(&self, event: Event) {
312        let notify = self.notify.lock().await;
313        if *notify {
314            // Ignore the receptor result.
315            let _ = self.messenger.message(
316                Payload::Event(event).into(),
317                Audience::Messenger(self.notifier_signature),
318            );
319        }
320    }
321
322    #[cfg(test)]
323    pub(crate) fn emit_state_event(&self, state: State) {
324        let event = Payload::Event(Event::StateChanged(state));
325        let _ = self.messenger.message(event.into(), Audience::EventSink);
326    }
327}
328
329/// `IntoHandlerResult` helps with converting a value into the result of a setting request.
330pub(crate) trait IntoHandlerResult {
331    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/42069089)
332    /// Converts `Self` into a `SettingHandlerResult` for use in a `Controller`.
333    fn into_handler_result(self) -> SettingHandlerResult;
334}
335
336impl IntoHandlerResult for SettingInfo {
337    fn into_handler_result(self) -> SettingHandlerResult {
338        Ok(Some(self))
339    }
340}
341
342pub mod persist {
343    use super::{ClientImpl as BaseProxy, *};
344    use crate::trace;
345    use fuchsia_trace as ftrace;
346    use settings_storage::device_storage::DeviceStorageConvertible;
347    use settings_storage::UpdateState;
348
349    pub trait Storage: DeviceStorageConvertible + Into<SettingInfo> {}
350    impl<T: DeviceStorageConvertible + Into<SettingInfo>> Storage for T {}
351
352    pub(crate) mod controller {
353        use super::*;
354
355        #[async_trait(?Send)]
356        pub(crate) trait CreateWithAsync: Sized {
357            type Data;
358
359            /// Creates the controller with additional data.
360            async fn create_with(
361                handler: ClientProxy,
362                data: Self::Data,
363            ) -> Result<Self, ControllerError>;
364        }
365    }
366
367    pub struct ClientProxy {
368        base: Rc<BaseProxy>,
369        setting_type: SettingType,
370    }
371
372    impl Clone for ClientProxy {
373        fn clone(&self) -> Self {
374            Self { base: Rc::clone(&self.base), setting_type: self.setting_type }
375        }
376    }
377
378    impl ClientProxy {
379        pub(crate) async fn new(base_proxy: Rc<BaseProxy>, setting_type: SettingType) -> Self {
380            Self { base: base_proxy, setting_type }
381        }
382
383        pub(crate) fn get_service_context(&self) -> Rc<ServiceContext> {
384            self.base.get_service_context()
385        }
386
387        pub(crate) async fn notify(&self, event: Event) {
388            self.base.notify(event).await;
389        }
390
391        pub(crate) async fn storage_write<T>(
392            &self,
393            store: &DeviceStorage,
394            info: T,
395            id: ftrace::Id,
396        ) -> Result<UpdateState, ControllerError>
397        where
398            T: Into<SettingInfo> + HasSettingType + DeviceStorageConvertible,
399        {
400            let setting_type = T::SETTING_TYPE;
401            let fst = format!("{setting_type:?}");
402            let guard = trace_guard!(
403                id,
404                c"write_setting send",
405                "setting_type" => fst.as_str()
406            );
407            let result = store.write::<T>(&info).await;
408            drop(guard);
409
410            trace!(
411                id,
412                c"write_setting receive",
413                "setting_type" => fst.as_str()
414            );
415            if let Ok(UpdateState::Updated) = result {
416                trace!(
417                    id,
418                    c"write_setting notify",
419                    "setting_type" => fst.as_str()
420                );
421                self.notify(Event::Changed(info.into())).await;
422            }
423
424            result.map_err(|e| {
425                log::error!("Failed to write setting: {:?}", e);
426                ControllerError::WriteFailure(setting_type)
427            })
428        }
429    }
430
431    impl IntoHandlerResult for Result<UpdateState, ControllerError> {
432        fn into_handler_result(self) -> SettingHandlerResult {
433            self.map(|_| None)
434        }
435    }
436
437    pub(crate) struct Handler<C> {
438        _data: PhantomData<C>,
439    }
440
441    impl<'a, C, O> Handler<C>
442    where
443        C: controller::CreateWithAsync<Data = O> + super::controller::Handle + 'static,
444        O: Clone + 'static,
445    {
446        pub(crate) fn spawn_with_async(
447            context: Context,
448            data: O,
449        ) -> LocalBoxFuture<'static, ControllerGenerateResult> {
450            Box::pin(async move {
451                let setting_type = context.setting_type;
452
453                ClientImpl::create(
454                    context,
455                    Box::new({
456                        let data = data.clone();
457                        move |proxy| {
458                            let data = data.clone();
459                            Box::pin(async move {
460                                let proxy = ClientProxy::new(proxy, setting_type).await;
461                                let controller_result = C::create_with(proxy, data).await;
462
463                                match controller_result {
464                                    Err(err) => Err(err),
465                                    Ok(controller) => Ok(Box::new(controller) as BoxedController),
466                                }
467                            })
468                        }
469                    }),
470                )
471                .await
472            })
473        }
474    }
475}
476
477pub(crate) fn reply(client: MessageClient, result: SettingHandlerResult) {
478    let _ = client.reply(Payload::Result(result).into());
479}