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 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 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 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 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 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 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(_)) => {} _ => panic!("Unexpected result"),
541 }
542 };
543 let stream_fut = async move {
544 match stream.next().await.unwrap() {
545 Ok(InstallerRequest::StartUpdate { .. }) => {
546 }
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 }
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 }
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 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}