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            manifest_headers: vec![],
434            should_commit: true,
435        };
436
437        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
438
439        let (_reboot_controller, reboot_controller_server_end) =
440            fidl::endpoints::create_proxy::<RebootControllerMarker>();
441
442        let installer_fut = async move {
443            let returned_update_attempt =
444                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
445                    .await
446                    .unwrap();
447            assert_eq!(
448                returned_update_attempt.attempt_id(),
449                "00000000-0000-0000-0000-000000000001"
450            );
451        };
452
453        let stream_fut = async move {
454            match stream.next().await.unwrap() {
455                Ok(InstallerRequest::StartUpdate {
456                    url,
457                    options:
458                        fidl_fuchsia_update_installer::Options {
459                            initiator,
460                            should_write_recovery,
461                            allow_attach_to_existing_attempt,
462                            ..
463                        },
464                    monitor: _,
465                    reboot_controller,
466                    responder,
467                }) => {
468                    assert_eq!(url.url, TEST_URL);
469                    assert_eq!(initiator, Some(fidl_fuchsia_update_installer::Initiator::User));
470                    assert_matches!(reboot_controller, Some(_));
471                    assert_eq!(should_write_recovery, Some(true));
472                    assert_eq!(allow_attach_to_existing_attempt, Some(false));
473                    responder.send(Ok("00000000-0000-0000-0000-000000000001")).unwrap();
474                }
475                request => panic!("Unexpected request: {request:?}"),
476            }
477        };
478        future::join(installer_fut, stream_fut).await;
479    }
480
481    #[fuchsia::test]
482    async fn test_install_error() {
483        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
484
485        let opts = Options {
486            initiator: Initiator::User,
487            allow_attach_to_existing_attempt: false,
488            should_write_recovery: true,
489            manifest_range: None,
490            manifest_headers: vec![],
491            should_commit: true,
492        };
493
494        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
495
496        let (_reboot_controller, reboot_controller_server_end) =
497            fidl::endpoints::create_proxy::<RebootControllerMarker>();
498
499        let installer_fut = async move {
500            let returned_update_attempt =
501                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
502                    .await
503                    .unwrap();
504
505            assert_eq!(
506                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
507                vec![State::FailPrepare(PrepareFailureReason::Internal)]
508            );
509        };
510
511        let stream_fut = async move {
512            match stream.next().await.unwrap() {
513                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
514                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
515
516                    let mut attempt = TestAttempt::new(monitor);
517                    attempt
518                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
519                        .await;
520                }
521                request => panic!("Unexpected request: {request:?}"),
522            }
523        };
524        future::join(installer_fut, stream_fut).await;
525    }
526
527    #[fuchsia::test]
528    async fn start_update_forwards_fidl_error() {
529        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
530
531        let opts = Options {
532            initiator: Initiator::User,
533            allow_attach_to_existing_attempt: false,
534            should_write_recovery: true,
535            manifest_range: None,
536            manifest_headers: vec![],
537            should_commit: true,
538        };
539
540        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
541
542        let installer_fut = async move {
543            match start_update(&pkgurl, opts, &proxy, None).await {
544                Err(UpdateAttemptError::FIDL(_)) => {} // expected
545                _ => panic!("Unexpected result"),
546            }
547        };
548        let stream_fut = async move {
549            match stream.next().await.unwrap() {
550                Ok(InstallerRequest::StartUpdate { .. }) => {
551                    // Don't send attempt id.
552                }
553                request => panic!("Unexpected request: {request:?}"),
554            }
555        };
556        future::join(installer_fut, stream_fut).await;
557    }
558
559    #[fuchsia::test]
560    async fn test_state_decode_error() {
561        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
562
563        let opts = Options {
564            initiator: Initiator::User,
565            allow_attach_to_existing_attempt: false,
566            should_write_recovery: true,
567            manifest_range: None,
568            manifest_headers: vec![],
569            should_commit: true,
570        };
571
572        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
573
574        let (_reboot_controller, reboot_controller_server_end) =
575            fidl::endpoints::create_proxy::<RebootControllerMarker>();
576
577        let installer_fut = async move {
578            let mut returned_update_attempt =
579                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
580                    .await
581                    .unwrap();
582            assert_matches!(
583                returned_update_attempt.next().await,
584                Some(Err(MonitorUpdateAttemptError::DecodeState(
585                    state::DecodeStateError::DecodeProgress(
586                        state::DecodeProgressError::FractionCompletedOutOfRange
587                    )
588                )))
589            );
590        };
591
592        let stream_fut = async move {
593            match stream.next().await.unwrap() {
594                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
595                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
596
597                    let mut monitor = TestAttempt::new(monitor);
598                    monitor
599                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
600                            fidl_fuchsia_update_installer::FetchData {
601                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
602                                    download_size: None,
603                                    ..Default::default()
604                                }),
605                                progress: Some(InstallationProgress {
606                                    fraction_completed: Some(2.0),
607                                    bytes_downloaded: None,
608                                    ..Default::default()
609                                }),
610                                ..Default::default()
611                            },
612                        ))
613                        .await;
614                }
615                request => panic!("Unexpected request: {request:?}"),
616            }
617        };
618        future::join(installer_fut, stream_fut).await;
619    }
620
621    #[fuchsia::test]
622    async fn test_server_close_unexpectedly() {
623        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
624
625        let opts = Options {
626            initiator: Initiator::User,
627            allow_attach_to_existing_attempt: false,
628            should_write_recovery: true,
629            manifest_range: None,
630            manifest_headers: vec![],
631            should_commit: true,
632        };
633
634        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
635
636        let (_reboot_controller, reboot_controller_server_end) =
637            fidl::endpoints::create_proxy::<RebootControllerMarker>();
638
639        let expected_states = vec![
640            State::Prepare,
641            State::Fetch(
642                UpdateInfoAndProgress::builder()
643                    .info(UpdateInfo::builder().download_size(0).build())
644                    .progress(
645                        Progress::builder().fraction_completed(0.0).bytes_downloaded(0).build(),
646                    )
647                    .build(),
648            ),
649        ];
650
651        let installer_fut = async move {
652            let returned_update_attempt =
653                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
654                    .await
655                    .unwrap();
656
657            assert_eq!(
658                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
659                expected_states,
660            );
661        };
662        let stream_fut = async move {
663            match stream.next().await.unwrap() {
664                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
665                    responder.send(Ok("00000000-0000-0000-0000-000000000003")).unwrap();
666
667                    let mut monitor = TestAttempt::new(monitor);
668                    monitor.send_state_and_recv_ack(State::Prepare).await;
669                    monitor
670                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
671                            fidl_fuchsia_update_installer::FetchData {
672                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
673                                    download_size: None,
674                                    ..Default::default()
675                                }),
676                                progress: Some(InstallationProgress {
677                                    fraction_completed: Some(0.0),
678                                    bytes_downloaded: None,
679                                    ..Default::default()
680                                }),
681                                ..Default::default()
682                            },
683                        ))
684                        .await;
685
686                    // monitor never sends a terminal state, but the client stream doesn't mind.
687                    // Higher layers of the system (ex. omaha-client/system-update-checker) convert
688                    // this situation into an error.
689                }
690                request => panic!("Unexpected request: {request:?}"),
691            }
692        };
693        future::join(installer_fut, stream_fut).await;
694    }
695
696    #[fuchsia::test]
697    async fn monitor_update_uses_provided_attempt_id() {
698        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
699
700        let client_fut = async move {
701            let _ = monitor_update(Some("id"), &proxy).await;
702        };
703
704        let server_fut = async move {
705            match stream.next().await.unwrap().unwrap() {
706                InstallerRequest::MonitorUpdate { attempt_id, .. } => {
707                    assert_eq!(attempt_id.as_deref(), Some("id"));
708                }
709                request => panic!("Unexpected request: {request:?}"),
710            }
711        };
712
713        future::join(client_fut, server_fut).await;
714    }
715
716    #[fuchsia::test]
717    async fn monitor_update_handles_no_update_in_progress() {
718        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
719
720        let client_fut = async move {
721            assert_matches!(monitor_update(None, &proxy).await, Ok(None));
722        };
723
724        let server_fut = async move {
725            match stream.next().await.unwrap().unwrap() {
726                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
727                    assert_eq!(attempt_id, None);
728                    drop(monitor);
729                    responder.send(false).unwrap();
730                }
731                request => panic!("Unexpected request: {request:?}"),
732            }
733            assert_matches!(stream.next().await, None);
734        };
735
736        future::join(client_fut, server_fut).await;
737    }
738
739    #[fuchsia::test]
740    async fn monitor_update_forwards_fidl_error() {
741        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
742
743        let client_fut = async move {
744            assert_matches!(monitor_update(None, &proxy).await, Err(_));
745        };
746        let server_fut = async move {
747            match stream.next().await.unwrap() {
748                Ok(InstallerRequest::MonitorUpdate { .. }) => {
749                    // Close the channel instead of sending a response.
750                }
751                request => panic!("Unexpected request: {request:?}"),
752            }
753        };
754        future::join(client_fut, server_fut).await;
755    }
756
757    #[fuchsia::test]
758    async fn monitor_update_forwards_and_acks_progress() {
759        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
760
761        let client_fut = async move {
762            let monitor = monitor_update(None, &proxy).await.unwrap().unwrap();
763
764            assert_eq!(
765                monitor.try_collect::<Vec<State>>().await.unwrap(),
766                vec![State::Prepare, State::FailPrepare(PrepareFailureReason::Internal)]
767            );
768        };
769
770        let server_fut = async move {
771            match stream.next().await.unwrap().unwrap() {
772                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
773                    assert_eq!(attempt_id, None);
774                    responder.send(true).unwrap();
775                    let mut monitor = TestAttempt::new(monitor);
776
777                    monitor.send_state_and_recv_ack(State::Prepare).await;
778                    monitor
779                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
780                        .await;
781                }
782                request => panic!("Unexpected request: {request:?}"),
783            }
784            assert_matches!(stream.next().await, None);
785        };
786
787        future::join(client_fut, server_fut).await;
788    }
789    #[fuchsia::test]
790    async fn update_attempt_monitor_fdomain_forwards_and_acks_progress() {
791        let (mut send, monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
792
793        let expected_fetch_state = &State::Fetch(
794            UpdateInfoAndProgress::builder()
795                .info(UpdateInfo::builder().download_size(1000).build())
796                .progress(Progress::builder().fraction_completed(0.5).bytes_downloaded(500).build())
797                .build(),
798        );
799
800        let client_fut = async move {
801            assert_eq!(
802                monitor.try_collect::<Vec<State>>().await.unwrap(),
803                vec![State::Prepare, expected_fetch_state.clone()]
804            );
805        };
806
807        let server_fut = async move {
808            send.send_state_and_recv_ack(State::Prepare).await;
809            send.send_state_and_recv_ack(expected_fetch_state.clone()).await;
810            send.close().await;
811        };
812
813        future::join(client_fut, server_fut).await;
814    }
815
816    #[fuchsia::test]
817    async fn update_attempt_monitor_fdomain_rejects_invalid_state() {
818        let (mut send, mut monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
819
820        let client_fut = async move {
821            assert_matches!(
822                monitor.next().await.unwrap(),
823                Err(MonitorUpdateAttemptError::DecodeState(_))
824            );
825            assert_matches!(monitor.next().await, Some(Ok(State::Prepare)));
826        };
827
828        let server_fut = async move {
829            send.send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
830                fidl_fuchsia_update_installer::FetchData {
831                    info: Some(fidl_fuchsia_update_installer::UpdateInfo {
832                        download_size: None,
833                        ..Default::default()
834                    }),
835                    progress: Some(InstallationProgress {
836                        fraction_completed: Some(2.0),
837                        bytes_downloaded: None,
838                        ..Default::default()
839                    }),
840                    ..Default::default()
841                },
842            ))
843            .await;
844
845            // Even though the previous state was invalid and the monitor stream yielded an error,
846            // further states will continue to be processed by the client.
847            send.send_state_and_recv_ack(State::Prepare).await;
848        };
849
850        future::join(client_fut, server_fut).await;
851    }
852
853    #[fuchsia::test]
854    async fn start_update_fdomain_forwards_args_and_returns_attempt_id() {
855        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
856
857        let opts = Options {
858            initiator: Initiator::User,
859            allow_attach_to_existing_attempt: false,
860            should_write_recovery: true,
861            manifest_range: None,
862            manifest_headers: vec![],
863            should_commit: true,
864        };
865
866        let client = fdomain_local::local_client_empty();
867        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
868
869        let (_reboot_controller, reboot_controller_server_end) =
870            client.create_proxy::<fd_installer::RebootControllerMarker>();
871
872        let installer_fut = async move {
873            let returned_update_attempt =
874                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
875                    .await
876                    .unwrap();
877            assert_eq!(
878                returned_update_attempt.attempt_id(),
879                "00000000-0000-0000-0000-000000000001"
880            );
881        };
882
883        let stream_fut = async move {
884            match stream.next().await.unwrap() {
885                Ok(fd_installer::InstallerRequest::StartUpdate {
886                    url,
887                    options:
888                        fidl_fuchsia_update_installer::Options {
889                            initiator,
890                            should_write_recovery,
891                            allow_attach_to_existing_attempt,
892                            ..
893                        },
894                    monitor: _,
895                    reboot_controller,
896                    responder,
897                }) => {
898                    assert_eq!(url.url, TEST_URL);
899                    assert_eq!(initiator, Some(fidl_fuchsia_update_installer::Initiator::User));
900                    assert_matches!(reboot_controller, Some(_));
901                    assert_eq!(should_write_recovery, Some(true));
902                    assert_eq!(allow_attach_to_existing_attempt, Some(false));
903                    responder.send(Ok("00000000-0000-0000-0000-000000000001")).unwrap();
904                }
905                request => panic!("Unexpected request: {request:?}"),
906            }
907        };
908        future::join(installer_fut, stream_fut).await;
909    }
910
911    #[fuchsia::test]
912    async fn test_install_error_fdomain() {
913        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
914
915        let opts = Options {
916            initiator: Initiator::User,
917            allow_attach_to_existing_attempt: false,
918            should_write_recovery: true,
919            manifest_range: None,
920            manifest_headers: vec![],
921            should_commit: true,
922        };
923
924        let client = fdomain_local::local_client_empty();
925        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
926
927        let (_reboot_controller, reboot_controller_server_end) =
928            client.create_proxy::<fd_installer::RebootControllerMarker>();
929
930        let installer_fut = async move {
931            let returned_update_attempt =
932                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
933                    .await
934                    .unwrap();
935
936            assert_eq!(
937                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
938                vec![State::FailPrepare(PrepareFailureReason::Internal)]
939            );
940        };
941
942        let stream_fut = async move {
943            match stream.next().await.unwrap() {
944                Ok(fd_installer::InstallerRequest::StartUpdate { monitor, responder, .. }) => {
945                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
946
947                    let mut attempt = TestAttemptFDomain::new(monitor);
948                    attempt
949                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
950                        .await;
951                }
952                request => panic!("Unexpected request: {request:?}"),
953            }
954        };
955        future::join(installer_fut, stream_fut).await;
956    }
957}