1#![deny(missing_docs)]
6
7pub 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#[derive(Debug, Error)]
37pub enum UpdateAttemptError {
38 #[error("FIDL error")]
40 FIDL(#[source] fidl::Error),
41
42 #[error("an installation was already in progress")]
44 InstallInProgress,
45}
46
47#[derive(Debug, Error)]
49pub enum MonitorUpdateAttemptError {
50 #[error("FIDL error")]
52 FIDL(#[source] fidl::Error),
53
54 #[error("unable to decode State")]
56 DecodeState(#[source] state::DecodeStateError),
57}
58
59#[pin_project(project = UpdateAttemptProj)]
61#[derive(Debug)]
62pub struct UpdateAttempt {
63 attempt_id: String,
65
66 #[pin]
68 monitor: UpdateAttemptMonitor,
69}
70
71#[pin_project(project = UpdateAttemptFDomainProj)]
73#[derive(Debug)]
74pub struct UpdateAttemptFDomain {
75 attempt_id: String,
77
78 #[pin]
80 monitor: UpdateAttemptMonitorFDomain,
81}
82
83#[pin_project(project = UpdateAttemptMonitorFDomainProj)]
85pub struct UpdateAttemptMonitorFDomain {
86 #[pin]
88 stream: fd_installer::MonitorRequestStream,
89}
90
91#[pin_project(project = UpdateAttemptMonitorProj)]
93pub struct UpdateAttemptMonitor {
94 #[pin]
96 stream: MonitorRequestStream,
97}
98
99impl UpdateAttempt {
100 pub fn attempt_id(&self) -> &str {
102 &self.attempt_id
103 }
104}
105
106impl UpdateAttemptFDomain {
107 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 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 pub fn from_stream(stream: MonitorRequestStream) -> Self {
140 Self { stream }
141 }
142}
143
144pub 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
168pub 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
194pub 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 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 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 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 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 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(_)) => {} _ => panic!("Unexpected result"),
543 }
544 };
545 let stream_fut = async move {
546 match stream.next().await.unwrap() {
547 Ok(InstallerRequest::StartUpdate { .. }) => {
548 }
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 }
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 }
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 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}