Skip to main content

fidl_fuchsia_update_installer_ext/
lib.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.
4
5#![deny(missing_docs)]
6
7//! `fidl_fuchsia_update_installer_ext` contains wrapper types around the auto-generated
8//! `fidl_fuchsia_update_installer` bindings.
9
10pub mod state;
11pub use state::{
12    FailFetchData, FailStageData, FetchFailureReason, PrepareFailureReason, Progress,
13    StageFailureReason, State, StateId, UpdateInfo, UpdateInfoAndProgress,
14};
15
16pub mod options;
17pub use options::{Initiator, Options};
18
19use fdomain_client::fidl::Proxy;
20use fdomain_fuchsia_update_installer as fd_installer;
21use fidl::endpoints::{ClientEnd, ServerEnd};
22use fidl_fuchsia_update_installer::{
23    InstallerProxy, MonitorMarker, MonitorRequest, MonitorRequestStream, RebootControllerMarker,
24    UpdateNotStartedReason,
25};
26use futures::prelude::*;
27use futures::task::{Context, Poll};
28use log::info;
29use pin_project::pin_project;
30use std::fmt;
31use std::pin::Pin;
32use std::sync::Arc;
33use thiserror::Error;
34
35/// Describes the errors encountered by UpdateAttempt.
36#[derive(Debug, Error)]
37pub enum UpdateAttemptError {
38    /// Fidl error.
39    #[error("FIDL error")]
40    FIDL(#[source] fidl::Error),
41
42    /// Install already in progress.
43    #[error("an installation was already in progress")]
44    InstallInProgress,
45}
46
47/// Describes the errors encountered by the UpdateAttempt's monitor stream.
48#[derive(Debug, Error)]
49pub enum MonitorUpdateAttemptError {
50    /// Fidl error.
51    #[error("FIDL error")]
52    FIDL(#[source] fidl::Error),
53
54    /// Error while decoding a [`fidl_fuchsia_update_installer::State`].
55    #[error("unable to decode State")]
56    DecodeState(#[source] state::DecodeStateError),
57}
58
59/// An update attempt.
60#[pin_project(project = UpdateAttemptProj)]
61#[derive(Debug)]
62pub struct UpdateAttempt {
63    /// UUID identifying the update attempt.
64    attempt_id: String,
65
66    /// The monitor for this update attempt.
67    #[pin]
68    monitor: UpdateAttemptMonitor,
69}
70
71/// A remote update attempt.
72#[pin_project(project = UpdateAttemptFDomainProj)]
73#[derive(Debug)]
74pub struct UpdateAttemptFDomain {
75    /// UUID identifying the update attempt.
76    attempt_id: String,
77
78    /// The monitor for this update attempt.
79    #[pin]
80    monitor: UpdateAttemptMonitorFDomain,
81}
82
83/// A monitor of an update attempt.
84#[pin_project(project = UpdateAttemptMonitorFDomainProj)]
85pub struct UpdateAttemptMonitorFDomain {
86    /// Server end of a fdomain_fuchsia_update_installer.Monitor protocol.
87    #[pin]
88    stream: fd_installer::MonitorRequestStream,
89}
90
91/// A monitor of an update attempt.
92#[pin_project(project = UpdateAttemptMonitorProj)]
93pub struct UpdateAttemptMonitor {
94    /// Server end of a fidl_fuchsia_update_installer.Monitor protocol.
95    #[pin]
96    stream: MonitorRequestStream,
97}
98
99impl UpdateAttempt {
100    /// Getter for the attempt_id.
101    pub fn attempt_id(&self) -> &str {
102        &self.attempt_id
103    }
104}
105
106impl UpdateAttemptFDomain {
107    /// Getter for the attempt_id.
108    pub fn attempt_id(&self) -> &str {
109        &self.attempt_id
110    }
111}
112
113impl UpdateAttemptMonitorFDomain {
114    fn new(
115        client: Arc<fdomain_client::Client>,
116    ) -> Result<(fdomain_client::fidl::ClientEnd<fd_installer::MonitorMarker>, Self), fidl::Error>
117    {
118        let (monitor_client_end, stream) =
119            client.create_request_stream::<fd_installer::MonitorMarker>();
120
121        Ok((monitor_client_end, Self { stream }))
122    }
123
124    /// Create a new UpdateAttemptMonitorFDomain using the given stream.
125    pub fn from_stream(stream: fd_installer::MonitorRequestStream) -> Self {
126        Self { stream }
127    }
128}
129
130impl UpdateAttemptMonitor {
131    fn new() -> Result<(ClientEnd<MonitorMarker>, Self), fidl::Error> {
132        let (monitor_client_end, stream) =
133            fidl::endpoints::create_request_stream::<MonitorMarker>();
134
135        Ok((monitor_client_end, Self { stream }))
136    }
137
138    /// Create a new UpdateAttemptMonitor using the given stream.
139    pub fn from_stream(stream: MonitorRequestStream) -> Self {
140        Self { stream }
141    }
142}
143
144/// Checks if an update can be started and returns the UpdateAttempt containing
145/// the attempt_id and MonitorRequestStream to the client.
146pub async fn start_update(
147    update_url: &http::Uri,
148    options: Options,
149    installer_proxy: &InstallerProxy,
150    reboot_controller_server_end: Option<ServerEnd<RebootControllerMarker>>,
151) -> Result<UpdateAttempt, UpdateAttemptError> {
152    let url = fidl_fuchsia_pkg::PackageUrl { url: update_url.to_string() };
153    let (monitor_client_end, monitor) =
154        UpdateAttemptMonitor::new().map_err(UpdateAttemptError::FIDL)?;
155
156    let attempt_id = installer_proxy
157        .start_update(&url, &options.into(), monitor_client_end, reboot_controller_server_end)
158        .await
159        .map_err(UpdateAttemptError::FIDL)?
160        .map_err(|reason| match reason {
161            UpdateNotStartedReason::AlreadyInProgress => UpdateAttemptError::InstallInProgress,
162        })?;
163
164    info!("Update started with attempt id: {}", attempt_id);
165    Ok(UpdateAttempt { attempt_id, monitor })
166}
167
168/// Checks if an update can be started and returns the UpdateAttempt containing
169/// the attempt_id and MonitorRequestStream to the client.
170pub async fn start_update_fdomain(
171    update_url: &http::Uri,
172    options: Options,
173    installer_proxy: &fd_installer::InstallerProxy,
174    reboot_controller_server_end: Option<
175        fdomain_client::fidl::ServerEnd<fd_installer::RebootControllerMarker>,
176    >,
177) -> Result<UpdateAttemptFDomain, UpdateAttemptError> {
178    let url = fidl_fuchsia_pkg::PackageUrl { url: update_url.to_string() };
179    let (monitor_client_end, monitor) = UpdateAttemptMonitorFDomain::new(installer_proxy.domain())
180        .map_err(UpdateAttemptError::FIDL)?;
181
182    let attempt_id = installer_proxy
183        .start_update(&url, &options.into(), monitor_client_end, reboot_controller_server_end)
184        .await
185        .map_err(UpdateAttemptError::FIDL)?
186        .map_err(|reason| match reason {
187            UpdateNotStartedReason::AlreadyInProgress => UpdateAttemptError::InstallInProgress,
188        })?;
189
190    info!("Update started with attempt id: {}", attempt_id);
191    Ok(UpdateAttemptFDomain { attempt_id, monitor })
192}
193
194/// Monitors the running update attempt given by `attempt_id`, or any running update attempt if no
195/// `attempt_id` is provided.
196pub async fn monitor_update(
197    attempt_id: Option<&str>,
198    installer_proxy: &InstallerProxy,
199) -> Result<Option<UpdateAttemptMonitor>, fidl::Error> {
200    let (monitor_client_end, monitor) = UpdateAttemptMonitor::new()?;
201
202    let attached = installer_proxy.monitor_update(attempt_id, monitor_client_end).await?;
203
204    if attached { Ok(Some(monitor)) } else { Ok(None) }
205}
206
207impl fmt::Debug for UpdateAttemptMonitor {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_struct("UpdateAttemptMonitor").field("stream", &"MonitorRequestStream").finish()
210    }
211}
212
213impl Stream for UpdateAttemptMonitor {
214    type Item = Result<State, MonitorUpdateAttemptError>;
215
216    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
217        let UpdateAttemptMonitorProj { stream } = self.project();
218        let poll_res = match stream.poll_next(cx) {
219            Poll::Ready(None) => return Poll::Ready(None),
220            Poll::Ready(Some(res)) => res.map_err(MonitorUpdateAttemptError::FIDL)?,
221            Poll::Pending => return Poll::Pending,
222        };
223        let MonitorRequest::OnState { state, responder } = poll_res;
224        let _ = responder.send();
225        let state = state.try_into().map_err(MonitorUpdateAttemptError::DecodeState)?;
226        Poll::Ready(Some(Ok(state)))
227    }
228}
229
230impl fmt::Debug for UpdateAttemptMonitorFDomain {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        f.debug_struct("UpdateAttemptMonitor").field("stream", &"MonitorRequestStream").finish()
233    }
234}
235
236impl Stream for UpdateAttemptMonitorFDomain {
237    type Item = Result<State, MonitorUpdateAttemptError>;
238
239    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
240        let UpdateAttemptMonitorFDomainProj { stream } = self.project();
241        let poll_res = match stream.poll_next(cx) {
242            Poll::Ready(None) => return Poll::Ready(None),
243            Poll::Ready(Some(res)) => res.map_err(MonitorUpdateAttemptError::FIDL)?,
244            Poll::Pending => return Poll::Pending,
245        };
246        let fd_installer::MonitorRequest::OnState { state, responder } = poll_res;
247        let _ = responder.send();
248        let state = state.try_into().map_err(MonitorUpdateAttemptError::DecodeState)?;
249        Poll::Ready(Some(Ok(state)))
250    }
251}
252
253impl Stream for UpdateAttempt {
254    type Item = Result<State, MonitorUpdateAttemptError>;
255
256    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
257        let UpdateAttemptProj { attempt_id: _, monitor } = self.project();
258        monitor.poll_next(cx)
259    }
260}
261
262impl Stream for UpdateAttemptFDomain {
263    type Item = Result<State, MonitorUpdateAttemptError>;
264
265    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
266        let UpdateAttemptFDomainProj { attempt_id: _, monitor } = self.project();
267        monitor.poll_next(cx)
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use assert_matches::assert_matches;
275    use fidl_fuchsia_update_installer::{
276        InstallationProgress, InstallerMarker, InstallerRequest, MonitorProxy,
277    };
278    use futures::stream::StreamExt;
279
280    const TEST_URL: &str = "fuchsia-pkg://fuchsia.com/update/0";
281
282    impl UpdateAttemptMonitor {
283        /// Returns an UpdateAttemptMonitor and a TestAttempt that can be used to send states to
284        /// the monitor.
285        fn new_test() -> (TestAttempt, Self) {
286            let (monitor_client_end, monitor) = Self::new().unwrap();
287
288            (TestAttempt::new(monitor_client_end), monitor)
289        }
290    }
291
292    struct TestAttempt {
293        proxy: MonitorProxy,
294    }
295
296    impl TestAttempt {
297        /// Wraps the given monitor proxy in a helper type that verifies sending state to the
298        /// remote end of the Monitor results in state being acknowledged as expected.
299        fn new(monitor_client_end: ClientEnd<MonitorMarker>) -> Self {
300            let proxy = monitor_client_end.into_proxy();
301
302            Self { proxy }
303        }
304
305        async fn send_state_and_recv_ack(&mut self, state: State) {
306            self.send_raw_state_and_recv_ack(state.into()).await;
307        }
308
309        async fn send_raw_state_and_recv_ack(
310            &mut self,
311            state: fidl_fuchsia_update_installer::State,
312        ) {
313            let () = self.proxy.on_state(&state).await.unwrap();
314        }
315    }
316
317    struct TestAttemptFDomain {
318        proxy: fd_installer::MonitorProxy,
319    }
320
321    impl TestAttemptFDomain {
322        /// Wraps the given monitor proxy in a helper type that verifies sending state to the
323        /// remote end of the Monitor results in state being acknowledged as expected.
324        fn new(
325            monitor_client_end: fdomain_client::fidl::ClientEnd<fd_installer::MonitorMarker>,
326        ) -> Self {
327            let proxy = monitor_client_end.into_proxy();
328
329            Self { proxy }
330        }
331
332        async fn send_state_and_recv_ack(&mut self, state: State) {
333            self.send_raw_state_and_recv_ack(state.into()).await;
334        }
335
336        async fn send_raw_state_and_recv_ack(
337            &mut self,
338            state: fidl_fuchsia_update_installer::State,
339        ) {
340            let () = self.proxy.on_state(&state).await.unwrap();
341        }
342
343        async fn close(self) {
344            use fdomain_client::HandleBased;
345            let channel = self.proxy.into_channel().expect("into_channel failed");
346            let _ = channel.close().await;
347        }
348    }
349
350    impl UpdateAttemptMonitorFDomain {
351        /// Returns an UpdateAttemptMonitorFDomain and a TestAttemptFDomain that can be used to send states to
352        /// the monitor.
353        fn new_test() -> (TestAttemptFDomain, Self, Arc<fdomain_client::Client>) {
354            let client = fdomain_local::local_client_empty();
355            let (monitor_client_end, monitor) = Self::new(client.clone()).unwrap();
356
357            (TestAttemptFDomain::new(monitor_client_end), monitor, client)
358        }
359    }
360
361    #[fuchsia::test]
362    async fn update_attempt_monitor_forwards_and_acks_progress() {
363        let (mut send, monitor) = UpdateAttemptMonitor::new_test();
364
365        let expected_fetch_state = &State::Fetch(
366            UpdateInfoAndProgress::builder()
367                .info(UpdateInfo::builder().download_size(1000).build())
368                .progress(Progress::builder().fraction_completed(0.5).bytes_downloaded(500).build())
369                .build(),
370        );
371
372        let client_fut = async move {
373            assert_eq!(
374                monitor.try_collect::<Vec<State>>().await.unwrap(),
375                vec![State::Prepare, expected_fetch_state.clone()]
376            );
377        };
378
379        let server_fut = async move {
380            send.send_state_and_recv_ack(State::Prepare).await;
381            send.send_state_and_recv_ack(expected_fetch_state.clone()).await;
382        };
383
384        future::join(client_fut, server_fut).await;
385    }
386
387    #[fuchsia::test]
388    async fn update_attempt_monitor_rejects_invalid_state() {
389        let (mut send, mut monitor) = UpdateAttemptMonitor::new_test();
390
391        let client_fut = async move {
392            assert_matches!(
393                monitor.next().await.unwrap(),
394                Err(MonitorUpdateAttemptError::DecodeState(_))
395            );
396            assert_matches!(monitor.next().await, Some(Ok(State::Prepare)));
397        };
398
399        let server_fut = async move {
400            send.send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
401                fidl_fuchsia_update_installer::FetchData {
402                    info: Some(fidl_fuchsia_update_installer::UpdateInfo {
403                        download_size: None,
404                        ..Default::default()
405                    }),
406                    progress: Some(InstallationProgress {
407                        fraction_completed: Some(2.0),
408                        bytes_downloaded: None,
409                        ..Default::default()
410                    }),
411                    ..Default::default()
412                },
413            ))
414            .await;
415
416            // Even though the previous state was invalid and the monitor stream yielded an error,
417            // further states will continue to be processed by the client.
418            send.send_state_and_recv_ack(State::Prepare).await;
419        };
420
421        future::join(client_fut, server_fut).await;
422    }
423
424    #[fuchsia::test]
425    async fn start_update_forwards_args_and_returns_attempt_id() {
426        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
427
428        let opts = Options {
429            initiator: Initiator::User,
430            allow_attach_to_existing_attempt: false,
431            should_write_recovery: true,
432            manifest_range: None,
433        };
434
435        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
436
437        let (_reboot_controller, reboot_controller_server_end) =
438            fidl::endpoints::create_proxy::<RebootControllerMarker>();
439
440        let installer_fut = async move {
441            let returned_update_attempt =
442                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
443                    .await
444                    .unwrap();
445            assert_eq!(
446                returned_update_attempt.attempt_id(),
447                "00000000-0000-0000-0000-000000000001"
448            );
449        };
450
451        let stream_fut = async move {
452            match stream.next().await.unwrap() {
453                Ok(InstallerRequest::StartUpdate {
454                    url,
455                    options:
456                        fidl_fuchsia_update_installer::Options {
457                            initiator,
458                            should_write_recovery,
459                            allow_attach_to_existing_attempt,
460                            ..
461                        },
462                    monitor: _,
463                    reboot_controller,
464                    responder,
465                }) => {
466                    assert_eq!(url.url, TEST_URL);
467                    assert_eq!(initiator, Some(fidl_fuchsia_update_installer::Initiator::User));
468                    assert_matches!(reboot_controller, Some(_));
469                    assert_eq!(should_write_recovery, Some(true));
470                    assert_eq!(allow_attach_to_existing_attempt, Some(false));
471                    responder.send(Ok("00000000-0000-0000-0000-000000000001")).unwrap();
472                }
473                request => panic!("Unexpected request: {request:?}"),
474            }
475        };
476        future::join(installer_fut, stream_fut).await;
477    }
478
479    #[fuchsia::test]
480    async fn test_install_error() {
481        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
482
483        let opts = Options {
484            initiator: Initiator::User,
485            allow_attach_to_existing_attempt: false,
486            should_write_recovery: true,
487            manifest_range: None,
488        };
489
490        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
491
492        let (_reboot_controller, reboot_controller_server_end) =
493            fidl::endpoints::create_proxy::<RebootControllerMarker>();
494
495        let installer_fut = async move {
496            let returned_update_attempt =
497                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
498                    .await
499                    .unwrap();
500
501            assert_eq!(
502                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
503                vec![State::FailPrepare(PrepareFailureReason::Internal)]
504            );
505        };
506
507        let stream_fut = async move {
508            match stream.next().await.unwrap() {
509                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
510                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
511
512                    let mut attempt = TestAttempt::new(monitor);
513                    attempt
514                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
515                        .await;
516                }
517                request => panic!("Unexpected request: {request:?}"),
518            }
519        };
520        future::join(installer_fut, stream_fut).await;
521    }
522
523    #[fuchsia::test]
524    async fn start_update_forwards_fidl_error() {
525        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
526
527        let opts = Options {
528            initiator: Initiator::User,
529            allow_attach_to_existing_attempt: false,
530            should_write_recovery: true,
531            manifest_range: None,
532        };
533
534        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
535
536        let installer_fut = async move {
537            match start_update(&pkgurl, opts, &proxy, None).await {
538                Err(UpdateAttemptError::FIDL(_)) => {} // expected
539                _ => panic!("Unexpected result"),
540            }
541        };
542        let stream_fut = async move {
543            match stream.next().await.unwrap() {
544                Ok(InstallerRequest::StartUpdate { .. }) => {
545                    // Don't send attempt id.
546                }
547                request => panic!("Unexpected request: {request:?}"),
548            }
549        };
550        future::join(installer_fut, stream_fut).await;
551    }
552
553    #[fuchsia::test]
554    async fn test_state_decode_error() {
555        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
556
557        let opts = Options {
558            initiator: Initiator::User,
559            allow_attach_to_existing_attempt: false,
560            should_write_recovery: true,
561            manifest_range: None,
562        };
563
564        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
565
566        let (_reboot_controller, reboot_controller_server_end) =
567            fidl::endpoints::create_proxy::<RebootControllerMarker>();
568
569        let installer_fut = async move {
570            let mut returned_update_attempt =
571                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
572                    .await
573                    .unwrap();
574            assert_matches!(
575                returned_update_attempt.next().await,
576                Some(Err(MonitorUpdateAttemptError::DecodeState(
577                    state::DecodeStateError::DecodeProgress(
578                        state::DecodeProgressError::FractionCompletedOutOfRange
579                    )
580                )))
581            );
582        };
583
584        let stream_fut = async move {
585            match stream.next().await.unwrap() {
586                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
587                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
588
589                    let mut monitor = TestAttempt::new(monitor);
590                    monitor
591                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
592                            fidl_fuchsia_update_installer::FetchData {
593                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
594                                    download_size: None,
595                                    ..Default::default()
596                                }),
597                                progress: Some(InstallationProgress {
598                                    fraction_completed: Some(2.0),
599                                    bytes_downloaded: None,
600                                    ..Default::default()
601                                }),
602                                ..Default::default()
603                            },
604                        ))
605                        .await;
606                }
607                request => panic!("Unexpected request: {request:?}"),
608            }
609        };
610        future::join(installer_fut, stream_fut).await;
611    }
612
613    #[fuchsia::test]
614    async fn test_server_close_unexpectedly() {
615        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
616
617        let opts = Options {
618            initiator: Initiator::User,
619            allow_attach_to_existing_attempt: false,
620            should_write_recovery: true,
621            manifest_range: None,
622        };
623
624        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
625
626        let (_reboot_controller, reboot_controller_server_end) =
627            fidl::endpoints::create_proxy::<RebootControllerMarker>();
628
629        let expected_states = vec![
630            State::Prepare,
631            State::Fetch(
632                UpdateInfoAndProgress::builder()
633                    .info(UpdateInfo::builder().download_size(0).build())
634                    .progress(
635                        Progress::builder().fraction_completed(0.0).bytes_downloaded(0).build(),
636                    )
637                    .build(),
638            ),
639        ];
640
641        let installer_fut = async move {
642            let returned_update_attempt =
643                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
644                    .await
645                    .unwrap();
646
647            assert_eq!(
648                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
649                expected_states,
650            );
651        };
652        let stream_fut = async move {
653            match stream.next().await.unwrap() {
654                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
655                    responder.send(Ok("00000000-0000-0000-0000-000000000003")).unwrap();
656
657                    let mut monitor = TestAttempt::new(monitor);
658                    monitor.send_state_and_recv_ack(State::Prepare).await;
659                    monitor
660                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
661                            fidl_fuchsia_update_installer::FetchData {
662                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
663                                    download_size: None,
664                                    ..Default::default()
665                                }),
666                                progress: Some(InstallationProgress {
667                                    fraction_completed: Some(0.0),
668                                    bytes_downloaded: None,
669                                    ..Default::default()
670                                }),
671                                ..Default::default()
672                            },
673                        ))
674                        .await;
675
676                    // monitor never sends a terminal state, but the client stream doesn't mind.
677                    // Higher layers of the system (ex. omaha-client/system-update-checker) convert
678                    // this situation into an error.
679                }
680                request => panic!("Unexpected request: {request:?}"),
681            }
682        };
683        future::join(installer_fut, stream_fut).await;
684    }
685
686    #[fuchsia::test]
687    async fn monitor_update_uses_provided_attempt_id() {
688        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
689
690        let client_fut = async move {
691            let _ = monitor_update(Some("id"), &proxy).await;
692        };
693
694        let server_fut = async move {
695            match stream.next().await.unwrap().unwrap() {
696                InstallerRequest::MonitorUpdate { attempt_id, .. } => {
697                    assert_eq!(attempt_id.as_deref(), Some("id"));
698                }
699                request => panic!("Unexpected request: {request:?}"),
700            }
701        };
702
703        future::join(client_fut, server_fut).await;
704    }
705
706    #[fuchsia::test]
707    async fn monitor_update_handles_no_update_in_progress() {
708        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
709
710        let client_fut = async move {
711            assert_matches!(monitor_update(None, &proxy).await, Ok(None));
712        };
713
714        let server_fut = async move {
715            match stream.next().await.unwrap().unwrap() {
716                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
717                    assert_eq!(attempt_id, None);
718                    drop(monitor);
719                    responder.send(false).unwrap();
720                }
721                request => panic!("Unexpected request: {request:?}"),
722            }
723            assert_matches!(stream.next().await, None);
724        };
725
726        future::join(client_fut, server_fut).await;
727    }
728
729    #[fuchsia::test]
730    async fn monitor_update_forwards_fidl_error() {
731        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
732
733        let client_fut = async move {
734            assert_matches!(monitor_update(None, &proxy).await, Err(_));
735        };
736        let server_fut = async move {
737            match stream.next().await.unwrap() {
738                Ok(InstallerRequest::MonitorUpdate { .. }) => {
739                    // Close the channel instead of sending a response.
740                }
741                request => panic!("Unexpected request: {request:?}"),
742            }
743        };
744        future::join(client_fut, server_fut).await;
745    }
746
747    #[fuchsia::test]
748    async fn monitor_update_forwards_and_acks_progress() {
749        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
750
751        let client_fut = async move {
752            let monitor = monitor_update(None, &proxy).await.unwrap().unwrap();
753
754            assert_eq!(
755                monitor.try_collect::<Vec<State>>().await.unwrap(),
756                vec![State::Prepare, State::FailPrepare(PrepareFailureReason::Internal)]
757            );
758        };
759
760        let server_fut = async move {
761            match stream.next().await.unwrap().unwrap() {
762                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
763                    assert_eq!(attempt_id, None);
764                    responder.send(true).unwrap();
765                    let mut monitor = TestAttempt::new(monitor);
766
767                    monitor.send_state_and_recv_ack(State::Prepare).await;
768                    monitor
769                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
770                        .await;
771                }
772                request => panic!("Unexpected request: {request:?}"),
773            }
774            assert_matches!(stream.next().await, None);
775        };
776
777        future::join(client_fut, server_fut).await;
778    }
779    #[fuchsia::test]
780    async fn update_attempt_monitor_fdomain_forwards_and_acks_progress() {
781        let (mut send, monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
782
783        let expected_fetch_state = &State::Fetch(
784            UpdateInfoAndProgress::builder()
785                .info(UpdateInfo::builder().download_size(1000).build())
786                .progress(Progress::builder().fraction_completed(0.5).bytes_downloaded(500).build())
787                .build(),
788        );
789
790        let client_fut = async move {
791            assert_eq!(
792                monitor.try_collect::<Vec<State>>().await.unwrap(),
793                vec![State::Prepare, expected_fetch_state.clone()]
794            );
795        };
796
797        let server_fut = async move {
798            send.send_state_and_recv_ack(State::Prepare).await;
799            send.send_state_and_recv_ack(expected_fetch_state.clone()).await;
800            send.close().await;
801        };
802
803        future::join(client_fut, server_fut).await;
804    }
805
806    #[fuchsia::test]
807    async fn update_attempt_monitor_fdomain_rejects_invalid_state() {
808        let (mut send, mut monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
809
810        let client_fut = async move {
811            assert_matches!(
812                monitor.next().await.unwrap(),
813                Err(MonitorUpdateAttemptError::DecodeState(_))
814            );
815            assert_matches!(monitor.next().await, Some(Ok(State::Prepare)));
816        };
817
818        let server_fut = async move {
819            send.send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
820                fidl_fuchsia_update_installer::FetchData {
821                    info: Some(fidl_fuchsia_update_installer::UpdateInfo {
822                        download_size: None,
823                        ..Default::default()
824                    }),
825                    progress: Some(InstallationProgress {
826                        fraction_completed: Some(2.0),
827                        bytes_downloaded: None,
828                        ..Default::default()
829                    }),
830                    ..Default::default()
831                },
832            ))
833            .await;
834
835            // Even though the previous state was invalid and the monitor stream yielded an error,
836            // further states will continue to be processed by the client.
837            send.send_state_and_recv_ack(State::Prepare).await;
838        };
839
840        future::join(client_fut, server_fut).await;
841    }
842
843    #[fuchsia::test]
844    async fn start_update_fdomain_forwards_args_and_returns_attempt_id() {
845        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
846
847        let opts = Options {
848            initiator: Initiator::User,
849            allow_attach_to_existing_attempt: false,
850            should_write_recovery: true,
851            manifest_range: None,
852        };
853
854        let client = fdomain_local::local_client_empty();
855        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
856
857        let (_reboot_controller, reboot_controller_server_end) =
858            client.create_proxy::<fd_installer::RebootControllerMarker>();
859
860        let installer_fut = async move {
861            let returned_update_attempt =
862                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
863                    .await
864                    .unwrap();
865            assert_eq!(
866                returned_update_attempt.attempt_id(),
867                "00000000-0000-0000-0000-000000000001"
868            );
869        };
870
871        let stream_fut = async move {
872            match stream.next().await.unwrap() {
873                Ok(fd_installer::InstallerRequest::StartUpdate {
874                    url,
875                    options:
876                        fidl_fuchsia_update_installer::Options {
877                            initiator,
878                            should_write_recovery,
879                            allow_attach_to_existing_attempt,
880                            ..
881                        },
882                    monitor: _,
883                    reboot_controller,
884                    responder,
885                }) => {
886                    assert_eq!(url.url, TEST_URL);
887                    assert_eq!(initiator, Some(fidl_fuchsia_update_installer::Initiator::User));
888                    assert_matches!(reboot_controller, Some(_));
889                    assert_eq!(should_write_recovery, Some(true));
890                    assert_eq!(allow_attach_to_existing_attempt, Some(false));
891                    responder.send(Ok("00000000-0000-0000-0000-000000000001")).unwrap();
892                }
893                request => panic!("Unexpected request: {request:?}"),
894            }
895        };
896        future::join(installer_fut, stream_fut).await;
897    }
898
899    #[fuchsia::test]
900    async fn test_install_error_fdomain() {
901        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
902
903        let opts = Options {
904            initiator: Initiator::User,
905            allow_attach_to_existing_attempt: false,
906            should_write_recovery: true,
907            manifest_range: None,
908        };
909
910        let client = fdomain_local::local_client_empty();
911        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
912
913        let (_reboot_controller, reboot_controller_server_end) =
914            client.create_proxy::<fd_installer::RebootControllerMarker>();
915
916        let installer_fut = async move {
917            let returned_update_attempt =
918                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
919                    .await
920                    .unwrap();
921
922            assert_eq!(
923                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
924                vec![State::FailPrepare(PrepareFailureReason::Internal)]
925            );
926        };
927
928        let stream_fut = async move {
929            match stream.next().await.unwrap() {
930                Ok(fd_installer::InstallerRequest::StartUpdate { monitor, responder, .. }) => {
931                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
932
933                    let mut attempt = TestAttemptFDomain::new(monitor);
934                    attempt
935                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
936                        .await;
937                }
938                request => panic!("Unexpected request: {request:?}"),
939            }
940        };
941        future::join(installer_fut, stream_fut).await;
942    }
943}