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 fuchsia_async as fasync;
279    use futures::stream::StreamExt;
280
281    const TEST_URL: &str = "fuchsia-pkg://fuchsia.com/update/0";
282
283    impl UpdateAttemptMonitor {
284        /// Returns an UpdateAttemptMonitor and a TestAttempt that can be used to send states to
285        /// the monitor.
286        fn new_test() -> (TestAttempt, Self) {
287            let (monitor_client_end, monitor) = Self::new().unwrap();
288
289            (TestAttempt::new(monitor_client_end), monitor)
290        }
291    }
292
293    struct TestAttempt {
294        proxy: MonitorProxy,
295    }
296
297    impl TestAttempt {
298        /// Wraps the given monitor proxy in a helper type that verifies sending state to the
299        /// remote end of the Monitor results in state being acknowledged as expected.
300        fn new(monitor_client_end: ClientEnd<MonitorMarker>) -> Self {
301            let proxy = monitor_client_end.into_proxy();
302
303            Self { proxy }
304        }
305
306        async fn send_state_and_recv_ack(&mut self, state: State) {
307            self.send_raw_state_and_recv_ack(state.into()).await;
308        }
309
310        async fn send_raw_state_and_recv_ack(
311            &mut self,
312            state: fidl_fuchsia_update_installer::State,
313        ) {
314            let () = self.proxy.on_state(&state).await.unwrap();
315        }
316    }
317
318    struct TestAttemptFDomain {
319        proxy: fd_installer::MonitorProxy,
320    }
321
322    impl TestAttemptFDomain {
323        /// Wraps the given monitor proxy in a helper type that verifies sending state to the
324        /// remote end of the Monitor results in state being acknowledged as expected.
325        fn new(
326            monitor_client_end: fdomain_client::fidl::ClientEnd<fd_installer::MonitorMarker>,
327        ) -> Self {
328            let proxy = monitor_client_end.into_proxy();
329
330            Self { proxy }
331        }
332
333        async fn send_state_and_recv_ack(&mut self, state: State) {
334            self.send_raw_state_and_recv_ack(state.into()).await;
335        }
336
337        async fn send_raw_state_and_recv_ack(
338            &mut self,
339            state: fidl_fuchsia_update_installer::State,
340        ) {
341            let () = self.proxy.on_state(&state).await.unwrap();
342        }
343
344        async fn close(self) {
345            use fdomain_client::HandleBased;
346            let channel = self.proxy.into_channel().expect("into_channel failed");
347            let _ = channel.close().await;
348        }
349    }
350
351    impl UpdateAttemptMonitorFDomain {
352        /// Returns an UpdateAttemptMonitorFDomain and a TestAttemptFDomain that can be used to send states to
353        /// the monitor.
354        fn new_test() -> (TestAttemptFDomain, Self, Arc<fdomain_client::Client>) {
355            let client = fdomain_local::local_client_empty();
356            let (monitor_client_end, monitor) = Self::new(client.clone()).unwrap();
357
358            (TestAttemptFDomain::new(monitor_client_end), monitor, client)
359        }
360    }
361
362    #[fasync::run_singlethreaded(test)]
363    async fn update_attempt_monitor_forwards_and_acks_progress() {
364        let (mut send, monitor) = UpdateAttemptMonitor::new_test();
365
366        let expected_fetch_state = &State::Fetch(
367            UpdateInfoAndProgress::builder()
368                .info(UpdateInfo::builder().download_size(1000).build())
369                .progress(Progress::builder().fraction_completed(0.5).bytes_downloaded(500).build())
370                .build(),
371        );
372
373        let client_fut = async move {
374            assert_eq!(
375                monitor.try_collect::<Vec<State>>().await.unwrap(),
376                vec![State::Prepare, expected_fetch_state.clone()]
377            );
378        };
379
380        let server_fut = async move {
381            send.send_state_and_recv_ack(State::Prepare).await;
382            send.send_state_and_recv_ack(expected_fetch_state.clone()).await;
383        };
384
385        future::join(client_fut, server_fut).await;
386    }
387
388    #[fasync::run_singlethreaded(test)]
389    async fn update_attempt_monitor_rejects_invalid_state() {
390        let (mut send, mut monitor) = UpdateAttemptMonitor::new_test();
391
392        let client_fut = async move {
393            assert_matches!(
394                monitor.next().await.unwrap(),
395                Err(MonitorUpdateAttemptError::DecodeState(_))
396            );
397            assert_matches!(monitor.next().await, Some(Ok(State::Prepare)));
398        };
399
400        let server_fut = async move {
401            send.send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
402                fidl_fuchsia_update_installer::FetchData {
403                    info: Some(fidl_fuchsia_update_installer::UpdateInfo {
404                        download_size: None,
405                        ..Default::default()
406                    }),
407                    progress: Some(InstallationProgress {
408                        fraction_completed: Some(2.0),
409                        bytes_downloaded: None,
410                        ..Default::default()
411                    }),
412                    ..Default::default()
413                },
414            ))
415            .await;
416
417            // Even though the previous state was invalid and the monitor stream yielded an error,
418            // further states will continue to be processed by the client.
419            send.send_state_and_recv_ack(State::Prepare).await;
420        };
421
422        future::join(client_fut, server_fut).await;
423    }
424
425    #[fasync::run_singlethreaded(test)]
426    async fn start_update_forwards_args_and_returns_attempt_id() {
427        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
428
429        let opts = Options {
430            initiator: Initiator::User,
431            allow_attach_to_existing_attempt: false,
432            should_write_recovery: true,
433            manifest_range: None,
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    #[fasync::run_singlethreaded(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        };
490
491        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
492
493        let (_reboot_controller, reboot_controller_server_end) =
494            fidl::endpoints::create_proxy::<RebootControllerMarker>();
495
496        let installer_fut = async move {
497            let returned_update_attempt =
498                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
499                    .await
500                    .unwrap();
501
502            assert_eq!(
503                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
504                vec![State::FailPrepare(PrepareFailureReason::Internal)]
505            );
506        };
507
508        let stream_fut = async move {
509            match stream.next().await.unwrap() {
510                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
511                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
512
513                    let mut attempt = TestAttempt::new(monitor);
514                    attempt
515                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
516                        .await;
517                }
518                request => panic!("Unexpected request: {request:?}"),
519            }
520        };
521        future::join(installer_fut, stream_fut).await;
522    }
523
524    #[fasync::run_singlethreaded(test)]
525    async fn start_update_forwards_fidl_error() {
526        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
527
528        let opts = Options {
529            initiator: Initiator::User,
530            allow_attach_to_existing_attempt: false,
531            should_write_recovery: true,
532            manifest_range: None,
533        };
534
535        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
536
537        let installer_fut = async move {
538            match start_update(&pkgurl, opts, &proxy, None).await {
539                Err(UpdateAttemptError::FIDL(_)) => {} // expected
540                _ => panic!("Unexpected result"),
541            }
542        };
543        let stream_fut = async move {
544            match stream.next().await.unwrap() {
545                Ok(InstallerRequest::StartUpdate { .. }) => {
546                    // Don't send attempt id.
547                }
548                request => panic!("Unexpected request: {request:?}"),
549            }
550        };
551        future::join(installer_fut, stream_fut).await;
552    }
553
554    #[fasync::run_singlethreaded(test)]
555    async fn test_state_decode_error() {
556        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
557
558        let opts = Options {
559            initiator: Initiator::User,
560            allow_attach_to_existing_attempt: false,
561            should_write_recovery: true,
562            manifest_range: None,
563        };
564
565        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
566
567        let (_reboot_controller, reboot_controller_server_end) =
568            fidl::endpoints::create_proxy::<RebootControllerMarker>();
569
570        let installer_fut = async move {
571            let mut returned_update_attempt =
572                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
573                    .await
574                    .unwrap();
575            assert_matches!(
576                returned_update_attempt.next().await,
577                Some(Err(MonitorUpdateAttemptError::DecodeState(
578                    state::DecodeStateError::DecodeProgress(
579                        state::DecodeProgressError::FractionCompletedOutOfRange
580                    )
581                )))
582            );
583        };
584
585        let stream_fut = async move {
586            match stream.next().await.unwrap() {
587                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
588                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
589
590                    let mut monitor = TestAttempt::new(monitor);
591                    monitor
592                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
593                            fidl_fuchsia_update_installer::FetchData {
594                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
595                                    download_size: None,
596                                    ..Default::default()
597                                }),
598                                progress: Some(InstallationProgress {
599                                    fraction_completed: Some(2.0),
600                                    bytes_downloaded: None,
601                                    ..Default::default()
602                                }),
603                                ..Default::default()
604                            },
605                        ))
606                        .await;
607                }
608                request => panic!("Unexpected request: {request:?}"),
609            }
610        };
611        future::join(installer_fut, stream_fut).await;
612    }
613
614    #[fasync::run_singlethreaded(test)]
615    async fn test_server_close_unexpectedly() {
616        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
617
618        let opts = Options {
619            initiator: Initiator::User,
620            allow_attach_to_existing_attempt: false,
621            should_write_recovery: true,
622            manifest_range: None,
623        };
624
625        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
626
627        let (_reboot_controller, reboot_controller_server_end) =
628            fidl::endpoints::create_proxy::<RebootControllerMarker>();
629
630        let expected_states = vec![
631            State::Prepare,
632            State::Fetch(
633                UpdateInfoAndProgress::builder()
634                    .info(UpdateInfo::builder().download_size(0).build())
635                    .progress(
636                        Progress::builder().fraction_completed(0.0).bytes_downloaded(0).build(),
637                    )
638                    .build(),
639            ),
640        ];
641
642        let installer_fut = async move {
643            let returned_update_attempt =
644                start_update(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
645                    .await
646                    .unwrap();
647
648            assert_eq!(
649                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
650                expected_states,
651            );
652        };
653        let stream_fut = async move {
654            match stream.next().await.unwrap() {
655                Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
656                    responder.send(Ok("00000000-0000-0000-0000-000000000003")).unwrap();
657
658                    let mut monitor = TestAttempt::new(monitor);
659                    monitor.send_state_and_recv_ack(State::Prepare).await;
660                    monitor
661                        .send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
662                            fidl_fuchsia_update_installer::FetchData {
663                                info: Some(fidl_fuchsia_update_installer::UpdateInfo {
664                                    download_size: None,
665                                    ..Default::default()
666                                }),
667                                progress: Some(InstallationProgress {
668                                    fraction_completed: Some(0.0),
669                                    bytes_downloaded: None,
670                                    ..Default::default()
671                                }),
672                                ..Default::default()
673                            },
674                        ))
675                        .await;
676
677                    // monitor never sends a terminal state, but the client stream doesn't mind.
678                    // Higher layers of the system (ex. omaha-client/system-update-checker) convert
679                    // this situation into an error.
680                }
681                request => panic!("Unexpected request: {request:?}"),
682            }
683        };
684        future::join(installer_fut, stream_fut).await;
685    }
686
687    #[fasync::run_singlethreaded(test)]
688    async fn monitor_update_uses_provided_attempt_id() {
689        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
690
691        let client_fut = async move {
692            let _ = monitor_update(Some("id"), &proxy).await;
693        };
694
695        let server_fut = async move {
696            match stream.next().await.unwrap().unwrap() {
697                InstallerRequest::MonitorUpdate { attempt_id, .. } => {
698                    assert_eq!(attempt_id.as_deref(), Some("id"));
699                }
700                request => panic!("Unexpected request: {request:?}"),
701            }
702        };
703
704        future::join(client_fut, server_fut).await;
705    }
706
707    #[fasync::run_singlethreaded(test)]
708    async fn monitor_update_handles_no_update_in_progress() {
709        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
710
711        let client_fut = async move {
712            assert_matches!(monitor_update(None, &proxy).await, Ok(None));
713        };
714
715        let server_fut = async move {
716            match stream.next().await.unwrap().unwrap() {
717                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
718                    assert_eq!(attempt_id, None);
719                    drop(monitor);
720                    responder.send(false).unwrap();
721                }
722                request => panic!("Unexpected request: {request:?}"),
723            }
724            assert_matches!(stream.next().await, None);
725        };
726
727        future::join(client_fut, server_fut).await;
728    }
729
730    #[fasync::run_singlethreaded(test)]
731    async fn monitor_update_forwards_fidl_error() {
732        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
733
734        let client_fut = async move {
735            assert_matches!(monitor_update(None, &proxy).await, Err(_));
736        };
737        let server_fut = async move {
738            match stream.next().await.unwrap() {
739                Ok(InstallerRequest::MonitorUpdate { .. }) => {
740                    // Close the channel instead of sending a response.
741                }
742                request => panic!("Unexpected request: {request:?}"),
743            }
744        };
745        future::join(client_fut, server_fut).await;
746    }
747
748    #[fasync::run_singlethreaded(test)]
749    async fn monitor_update_forwards_and_acks_progress() {
750        let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<InstallerMarker>();
751
752        let client_fut = async move {
753            let monitor = monitor_update(None, &proxy).await.unwrap().unwrap();
754
755            assert_eq!(
756                monitor.try_collect::<Vec<State>>().await.unwrap(),
757                vec![State::Prepare, State::FailPrepare(PrepareFailureReason::Internal)]
758            );
759        };
760
761        let server_fut = async move {
762            match stream.next().await.unwrap().unwrap() {
763                InstallerRequest::MonitorUpdate { attempt_id, monitor, responder } => {
764                    assert_eq!(attempt_id, None);
765                    responder.send(true).unwrap();
766                    let mut monitor = TestAttempt::new(monitor);
767
768                    monitor.send_state_and_recv_ack(State::Prepare).await;
769                    monitor
770                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
771                        .await;
772                }
773                request => panic!("Unexpected request: {request:?}"),
774            }
775            assert_matches!(stream.next().await, None);
776        };
777
778        future::join(client_fut, server_fut).await;
779    }
780    #[fasync::run_singlethreaded(test)]
781    async fn update_attempt_monitor_fdomain_forwards_and_acks_progress() {
782        let (mut send, monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
783
784        let expected_fetch_state = &State::Fetch(
785            UpdateInfoAndProgress::builder()
786                .info(UpdateInfo::builder().download_size(1000).build())
787                .progress(Progress::builder().fraction_completed(0.5).bytes_downloaded(500).build())
788                .build(),
789        );
790
791        let client_fut = async move {
792            assert_eq!(
793                monitor.try_collect::<Vec<State>>().await.unwrap(),
794                vec![State::Prepare, expected_fetch_state.clone()]
795            );
796        };
797
798        let server_fut = async move {
799            send.send_state_and_recv_ack(State::Prepare).await;
800            send.send_state_and_recv_ack(expected_fetch_state.clone()).await;
801            send.close().await;
802        };
803
804        future::join(client_fut, server_fut).await;
805    }
806
807    #[fasync::run_singlethreaded(test)]
808    async fn update_attempt_monitor_fdomain_rejects_invalid_state() {
809        let (mut send, mut monitor, _client) = UpdateAttemptMonitorFDomain::new_test();
810
811        let client_fut = async move {
812            assert_matches!(
813                monitor.next().await.unwrap(),
814                Err(MonitorUpdateAttemptError::DecodeState(_))
815            );
816            assert_matches!(monitor.next().await, Some(Ok(State::Prepare)));
817        };
818
819        let server_fut = async move {
820            send.send_raw_state_and_recv_ack(fidl_fuchsia_update_installer::State::Fetch(
821                fidl_fuchsia_update_installer::FetchData {
822                    info: Some(fidl_fuchsia_update_installer::UpdateInfo {
823                        download_size: None,
824                        ..Default::default()
825                    }),
826                    progress: Some(InstallationProgress {
827                        fraction_completed: Some(2.0),
828                        bytes_downloaded: None,
829                        ..Default::default()
830                    }),
831                    ..Default::default()
832                },
833            ))
834            .await;
835
836            // Even though the previous state was invalid and the monitor stream yielded an error,
837            // further states will continue to be processed by the client.
838            send.send_state_and_recv_ack(State::Prepare).await;
839        };
840
841        future::join(client_fut, server_fut).await;
842    }
843
844    #[fasync::run_singlethreaded(test)]
845    async fn start_update_fdomain_forwards_args_and_returns_attempt_id() {
846        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
847
848        let opts = Options {
849            initiator: Initiator::User,
850            allow_attach_to_existing_attempt: false,
851            should_write_recovery: true,
852            manifest_range: None,
853        };
854
855        let client = fdomain_local::local_client_empty();
856        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
857
858        let (_reboot_controller, reboot_controller_server_end) =
859            client.create_proxy::<fd_installer::RebootControllerMarker>();
860
861        let installer_fut = async move {
862            let returned_update_attempt =
863                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
864                    .await
865                    .unwrap();
866            assert_eq!(
867                returned_update_attempt.attempt_id(),
868                "00000000-0000-0000-0000-000000000001"
869            );
870        };
871
872        let stream_fut = async move {
873            match stream.next().await.unwrap() {
874                Ok(fd_installer::InstallerRequest::StartUpdate {
875                    url,
876                    options:
877                        fidl_fuchsia_update_installer::Options {
878                            initiator,
879                            should_write_recovery,
880                            allow_attach_to_existing_attempt,
881                            ..
882                        },
883                    monitor: _,
884                    reboot_controller,
885                    responder,
886                }) => {
887                    assert_eq!(url.url, TEST_URL);
888                    assert_eq!(initiator, Some(fidl_fuchsia_update_installer::Initiator::User));
889                    assert_matches!(reboot_controller, Some(_));
890                    assert_eq!(should_write_recovery, Some(true));
891                    assert_eq!(allow_attach_to_existing_attempt, Some(false));
892                    responder.send(Ok("00000000-0000-0000-0000-000000000001")).unwrap();
893                }
894                request => panic!("Unexpected request: {request:?}"),
895            }
896        };
897        future::join(installer_fut, stream_fut).await;
898    }
899
900    #[fasync::run_singlethreaded(test)]
901    async fn test_install_error_fdomain() {
902        let pkgurl = "fuchsia-pkg://fuchsia.com/update/0".parse().unwrap();
903
904        let opts = Options {
905            initiator: Initiator::User,
906            allow_attach_to_existing_attempt: false,
907            should_write_recovery: true,
908            manifest_range: None,
909        };
910
911        let client = fdomain_local::local_client_empty();
912        let (proxy, mut stream) = client.create_proxy_and_stream::<fd_installer::InstallerMarker>();
913
914        let (_reboot_controller, reboot_controller_server_end) =
915            client.create_proxy::<fd_installer::RebootControllerMarker>();
916
917        let installer_fut = async move {
918            let returned_update_attempt =
919                start_update_fdomain(&pkgurl, opts, &proxy, Some(reboot_controller_server_end))
920                    .await
921                    .unwrap();
922
923            assert_eq!(
924                returned_update_attempt.try_collect::<Vec<State>>().await.unwrap(),
925                vec![State::FailPrepare(PrepareFailureReason::Internal)]
926            );
927        };
928
929        let stream_fut = async move {
930            match stream.next().await.unwrap() {
931                Ok(fd_installer::InstallerRequest::StartUpdate { monitor, responder, .. }) => {
932                    responder.send(Ok("00000000-0000-0000-0000-000000000002")).unwrap();
933
934                    let mut attempt = TestAttemptFDomain::new(monitor);
935                    attempt
936                        .send_state_and_recv_ack(State::FailPrepare(PrepareFailureReason::Internal))
937                        .await;
938                }
939                request => panic!("Unexpected request: {request:?}"),
940            }
941        };
942        future::join(installer_fut, stream_fut).await;
943    }
944}