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