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