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