1use fidl::endpoints::ProtocolMarker as _;
8use fidl::{HandleBased, Rights};
9use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
10use thiserror::Error;
11use {
12 fidl_fuchsia_net_interfaces as fnet_interfaces,
13 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
14};
15
16#[derive(Error, Debug)]
18pub enum AddressStateProviderError {
19 #[error("address removed: {0:?}")]
21 AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
22 #[error("fidl error")]
24 Fidl(#[from] fidl::Error),
25 #[error("AddressStateProvider channel closed")]
27 ChannelClosed,
28}
29
30impl From<TerminalError<fnet_interfaces_admin::AddressRemovalReason>>
31 for AddressStateProviderError
32{
33 fn from(e: TerminalError<fnet_interfaces_admin::AddressRemovalReason>) -> Self {
34 match e {
35 TerminalError::Fidl(e) => AddressStateProviderError::Fidl(e),
36 TerminalError::Terminal(r) => AddressStateProviderError::AddressRemoved(r),
37 }
38 }
39}
40
41pub async fn wait_for_address_added_event(
45 event_stream: &mut fnet_interfaces_admin::AddressStateProviderEventStream,
46) -> Result<(), AddressStateProviderError> {
47 let event = event_stream
48 .next()
49 .await
50 .ok_or(AddressStateProviderError::ChannelClosed)?
51 .map_err(AddressStateProviderError::Fidl)?;
52 match event {
53 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(()),
54 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
55 Err(AddressStateProviderError::AddressRemoved(error))
56 }
57 }
58}
59
60pub fn assignment_state_stream(
73 address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
74) -> impl Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
75{
76 let event_fut = address_state_provider
77 .take_event_stream()
78 .filter_map(|event| {
79 futures::future::ready(match event {
80 Ok(event) => match event {
81 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => None,
82 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
83 error,
84 } => Some(AddressStateProviderError::AddressRemoved(error)),
85 },
86 Err(e) => Some(AddressStateProviderError::Fidl(e)),
87 })
88 })
89 .into_future()
90 .map(|(event, _stream)| event.unwrap_or(AddressStateProviderError::ChannelClosed));
91 futures::stream::try_unfold(
92 (address_state_provider, event_fut),
93 |(address_state_provider, event_fut)| {
94 futures::future::select(
99 address_state_provider.watch_address_assignment_state(),
100 event_fut,
101 )
102 .then(|s| match s {
103 futures::future::Either::Left((state_result, event_fut)) => match state_result {
104 Ok(state) => {
105 futures::future::ok(Some((state, (address_state_provider, event_fut))))
106 .left_future()
107 }
108 Err(e) if e.is_closed() => event_fut.map(Result::Err).right_future(),
109 Err(e) => {
110 futures::future::err(AddressStateProviderError::Fidl(e)).left_future()
111 }
112 },
113 futures::future::Either::Right((error, _state_fut)) => {
114 futures::future::err(error).left_future()
115 }
116 })
117 },
118 )
119}
120
121pub async fn wait_assignment_state<S>(
129 stream: S,
130 want: fnet_interfaces::AddressAssignmentState,
131) -> Result<(), AddressStateProviderError>
132where
133 S: Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
134 + Unpin,
135{
136 stream
137 .try_filter_map(|state| futures::future::ok((state == want).then_some(())))
138 .try_next()
139 .await
140 .and_then(|opt| opt.ok_or_else(|| AddressStateProviderError::ChannelClosed))
141}
142
143type ControlEventStreamFutureToReason =
144 fn(
145 (
146 Option<Result<fnet_interfaces_admin::ControlEvent, fidl::Error>>,
147 fnet_interfaces_admin::ControlEventStream,
148 ),
149 ) -> Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>;
150
151pub fn proof_from_grant(
161 grant: &fnet_interfaces_admin::GrantForInterfaceAuthorization,
162) -> fnet_interfaces_admin::ProofOfInterfaceAuthorization {
163 let fnet_interfaces_admin::GrantForInterfaceAuthorization { interface_id, token } = grant;
164
165 fnet_interfaces_admin::ProofOfInterfaceAuthorization {
170 interface_id: *interface_id,
171 token: token.duplicate_handle(Rights::TRANSFER).unwrap(),
172 }
173}
174
175#[derive(Clone)]
178pub struct Control {
179 proxy: fnet_interfaces_admin::ControlProxy,
180 terminal_event_fut: futures::future::Shared<
188 futures::future::Map<
189 futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
190 ControlEventStreamFutureToReason,
191 >,
192 >,
193}
194
195async fn or_terminal_event<QR, QF, TR>(
198 query_fut: QF,
199 terminal_event_fut: TR,
200) -> Result<QR, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>
201where
202 QR: Unpin,
203 QF: Unpin + Future<Output = Result<QR, fidl::Error>>,
204 TR: Unpin
205 + Future<Output = Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>>,
206{
207 match futures::future::select(query_fut, terminal_event_fut).await {
208 futures::future::Either::Left((query_result, terminal_event_fut)) => match query_result {
209 Ok(ok) => Ok(ok),
210 Err(e) if e.is_closed() => match terminal_event_fut.await {
211 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
212 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
213 },
214 Err(e) => Err(TerminalError::Fidl(e)),
215 },
216 futures::future::Either::Right((event, query_fut)) => {
217 if let Some(query_result) = query_fut.now_or_never() {
233 match query_result {
234 Ok(ok) => Ok(ok),
235 Err(e) if e.is_closed() => match event {
236 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
237 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
238 },
239 Err(e) => Err(TerminalError::Fidl(e)),
240 }
241 } else {
242 match event.map_err(|e| TerminalError::Fidl(e))? {
243 Some(removal_reason) => Err(TerminalError::Terminal(removal_reason)),
244 None => Err(TerminalError::Fidl(fidl::Error::ClientChannelClosed {
245 status: zx::Status::PEER_CLOSED,
246 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
247 #[cfg(not(target_os = "fuchsia"))]
248 reason: None,
249 epitaph: None,
250 })),
251 }
252 }
253 }
254 }
255}
256
257impl Control {
258 pub fn add_address(
260 &self,
261 address: &fidl_fuchsia_net::Subnet,
262 parameters: &fnet_interfaces_admin::AddressParameters,
263 address_state_provider: fidl::endpoints::ServerEnd<
264 fnet_interfaces_admin::AddressStateProviderMarker,
265 >,
266 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
267 self.or_terminal_event_no_return(self.proxy.add_address(
268 address,
269 parameters,
270 address_state_provider,
271 ))
272 }
273
274 pub async fn get_id(
276 &self,
277 ) -> Result<u64, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
278 self.or_terminal_event(self.proxy.get_id()).await
279 }
280
281 pub async fn remove_address(
283 &self,
284 address: &fidl_fuchsia_net::Subnet,
285 ) -> Result<
286 fnet_interfaces_admin::ControlRemoveAddressResult,
287 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
288 > {
289 self.or_terminal_event(self.proxy.remove_address(address)).await
290 }
291
292 pub async fn set_configuration(
294 &self,
295 config: &fnet_interfaces_admin::Configuration,
296 ) -> Result<
297 fnet_interfaces_admin::ControlSetConfigurationResult,
298 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
299 > {
300 self.or_terminal_event(self.proxy.set_configuration(config)).await
301 }
302
303 pub async fn get_configuration(
305 &self,
306 ) -> Result<
307 fnet_interfaces_admin::ControlGetConfigurationResult,
308 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
309 > {
310 self.or_terminal_event(self.proxy.get_configuration()).await
311 }
312
313 pub async fn get_authorization_for_interface(
315 &self,
316 ) -> Result<
317 fnet_interfaces_admin::GrantForInterfaceAuthorization,
318 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
319 > {
320 self.or_terminal_event(self.proxy.get_authorization_for_interface()).await
321 }
322
323 pub async fn enable(
325 &self,
326 ) -> Result<
327 fnet_interfaces_admin::ControlEnableResult,
328 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
329 > {
330 self.or_terminal_event(self.proxy.enable()).await
331 }
332
333 pub async fn remove(
335 &self,
336 ) -> Result<
337 fnet_interfaces_admin::ControlRemoveResult,
338 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
339 > {
340 self.or_terminal_event(self.proxy.remove()).await
341 }
342
343 pub async fn disable(
345 &self,
346 ) -> Result<
347 fnet_interfaces_admin::ControlDisableResult,
348 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
349 > {
350 self.or_terminal_event(self.proxy.disable()).await
351 }
352
353 pub fn detach(
355 &self,
356 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
357 self.or_terminal_event_no_return(self.proxy.detach())
358 }
359
360 pub fn new(proxy: fnet_interfaces_admin::ControlProxy) -> Self {
362 let terminal_event_fut = proxy
363 .take_event_stream()
364 .into_future()
365 .map::<_, ControlEventStreamFutureToReason>(|(event, _stream)| {
366 event
367 .map(|r| {
368 r.map(
369 |fnet_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
370 reason
371 },
372 )
373 })
374 .transpose()
375 })
376 .shared();
377 Self { proxy, terminal_event_fut }
378 }
379
380 pub async fn wait_termination(
382 self,
383 ) -> TerminalError<fnet_interfaces_admin::InterfaceRemovedReason> {
384 let Self { proxy: _, terminal_event_fut } = self;
385 match terminal_event_fut.await {
386 Ok(Some(event)) => TerminalError::Terminal(event),
387 Ok(None) => TerminalError::Fidl(fidl::Error::ClientChannelClosed {
388 status: zx::Status::PEER_CLOSED,
389 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
390 #[cfg(not(target_os = "fuchsia"))]
391 reason: None,
392 epitaph: None,
393 }),
394 Err(e) => TerminalError::Fidl(e),
395 }
396 }
397
398 pub fn create_endpoints(
400 ) -> Result<(Self, fidl::endpoints::ServerEnd<fnet_interfaces_admin::ControlMarker>), fidl::Error>
401 {
402 let (proxy, server_end) = fidl::endpoints::create_proxy();
403 Ok((Self::new(proxy), server_end))
404 }
405
406 async fn or_terminal_event<R: Unpin, F: Unpin + Future<Output = Result<R, fidl::Error>>>(
407 &self,
408 fut: F,
409 ) -> Result<R, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
410 or_terminal_event(fut, self.terminal_event_fut.clone()).await
411 }
412
413 fn or_terminal_event_no_return(
414 &self,
415 r: Result<(), fidl::Error>,
416 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
417 r.map_err(|err| {
418 if !err.is_closed() {
419 return TerminalError::Fidl(err);
420 }
421 match self.terminal_event_fut.clone().now_or_never() {
429 Some(Ok(Some(terminal_event))) => TerminalError::Terminal(terminal_event),
430 Some(Err(e)) => {
431 let _: fidl::Error = e;
433 TerminalError::Fidl(err)
434 }
435 None | Some(Ok(None)) => TerminalError::Fidl(err),
436 }
437 })
438 }
439}
440
441impl std::fmt::Debug for Control {
442 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 let Self { proxy, terminal_event_fut: _ } = self;
444 fmt.debug_struct("Control").field("proxy", proxy).finish()
445 }
446}
447
448#[derive(Debug)]
450pub enum TerminalError<E> {
451 Terminal(E),
453 Fidl(fidl::Error),
455}
456
457impl<E> std::fmt::Display for TerminalError<E>
458where
459 E: std::fmt::Debug,
460{
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 match self {
463 TerminalError::Terminal(e) => write!(f, "terminal event: {:?}", e),
464 TerminalError::Fidl(e) => write!(f, "fidl error: {}", e),
465 }
466 }
467}
468
469impl<E: std::fmt::Debug> std::error::Error for TerminalError<E> {}
470
471#[cfg(test)]
472mod test {
473 use std::task::Poll;
474
475 use super::{
476 assignment_state_stream, or_terminal_event, proof_from_grant, AddressStateProviderError,
477 TerminalError,
478 };
479 use assert_matches::assert_matches;
480 use fidl::prelude::*;
481 use fidl::Rights;
482 use fnet_interfaces_admin::InterfaceRemovedReason;
483 use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
484 use test_case::test_case;
485 use {
486 fidl_fuchsia_net_interfaces as fnet_interfaces,
487 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
488 };
489
490 #[fuchsia_async::run_singlethreaded(test)]
492 async fn test_assignment_state_stream() {
493 let (address_state_provider, server_end) =
494 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
495 let state_stream = assignment_state_stream(address_state_provider);
496 futures::pin_mut!(state_stream);
497
498 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
499 fnet_interfaces_admin::AddressRemovalReason::Invalid;
500 {
501 let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
502
503 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
504 fnet_interfaces::AddressAssignmentState::Assigned;
505 let state_fut = state_stream.try_next().map(|r| {
506 assert_eq!(
507 r.expect("state stream error").expect("state stream ended"),
508 ASSIGNMENT_STATE_ASSIGNED
509 )
510 });
511 let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
512 fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
513 let () = responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
514 }
515 req => panic!("unexpected method called: {:?}", req),
516 });
517 let ((), ()) = futures::join!(state_fut, handle_fut);
518
519 let () = control_handle
520 .send_on_address_removed(REMOVAL_REASON_INVALID)
521 .expect("failed to send fake INVALID address removal reason event");
522 }
523
524 assert_matches::assert_matches!(
525 state_stream.try_collect::<Vec<_>>().await,
526 Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
527 );
528 }
529
530 #[fuchsia_async::run_singlethreaded(test)]
533 async fn test_assignment_state_stream_single_error() {
534 let (address_state_provider, server_end) =
535 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
536 let state_stream = assignment_state_stream(address_state_provider);
537
538 let () = server_end
539 .close_with_epitaph(fidl::Status::INTERNAL)
540 .expect("failed to send INTERNAL epitaph");
541
542 assert_matches::assert_matches!(
545 state_stream
546 .collect::<Vec<_>>()
547 .now_or_never()
548 .expect("state stream not immediately ready")
549 .as_slice(),
550 [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
551 status: fidl::Status::INTERNAL,
552 #[cfg(not(target_os = "fuchsia"))]
553 reason: None,
554 ..
555 }))]
556 );
557 }
558
559 #[fuchsia_async::run_singlethreaded(test)]
562 async fn assignment_state_stream_state_before_event() {
563 let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
564 fnet_interfaces_admin::AddressStateProviderMarker,
565 >();
566
567 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
568 fnet_interfaces::AddressAssignmentState::Assigned;
569 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
570 fnet_interfaces_admin::AddressRemovalReason::Invalid;
571
572 let ((), ()) = futures::future::join(
573 async move {
574 let () = request_stream
575 .try_next()
576 .await
577 .expect("request stream error")
578 .expect("request stream ended")
579 .into_watch_address_assignment_state()
580 .expect("unexpected request")
581 .send(ASSIGNMENT_STATE_ASSIGNED)
582 .expect("failed to send stubbed assignment state");
583 let () = request_stream
584 .control_handle()
585 .send_on_address_removed(REMOVAL_REASON_INVALID)
586 .expect("failed to send fake INVALID address removal reason event");
587 },
588 async move {
589 let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
590 assert_matches::assert_matches!(
591 got.as_slice(),
592 &[
593 Ok(got_state),
594 Err(AddressStateProviderError::AddressRemoved(got_reason)),
595 ] => {
596 assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
597 assert_eq!(got_reason, REMOVAL_REASON_INVALID);
598 }
599 );
600 },
601 )
602 .await;
603 }
604
605 #[fuchsia_async::run_singlethreaded(test)]
607 async fn control_terminal_event() {
608 let (control, mut request_stream) =
609 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
610 let control = super::Control::new(control);
611 const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
612 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
613 const ID: u64 = 15;
614 let ((), ()) = futures::future::join(
615 async move {
616 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
617 assert_matches::assert_matches!(
618 control.get_id().await,
619 Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
620 );
621 },
622 async move {
623 let responder = request_stream
624 .try_next()
625 .await
626 .expect("operating request stream")
627 .expect("stream ended unexpectedly")
628 .into_get_id()
629 .expect("unexpected request");
630 let () = responder.send(ID).expect("failed to send response");
631 let () = request_stream
632 .control_handle()
633 .send_on_interface_removed(EXPECTED_EVENT)
634 .expect("sending terminal event");
635 },
636 )
637 .await;
638 }
639
640 #[fuchsia_async::run_singlethreaded(test)]
643 async fn control_missing_terminal_event() {
644 let (control, mut request_stream) =
645 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
646 let control = super::Control::new(control);
647 let ((), ()) = futures::future::join(
648 async move {
649 assert_matches::assert_matches!(
650 control.get_id().await,
651 Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
652 status: zx::Status::PEER_CLOSED,
653 protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
654 #[cfg(not(target_os = "fuchsia"))]
655 reason: None,
656 ..
657 }))
658 );
659 },
660 async move {
661 match request_stream
662 .try_next()
663 .await
664 .expect("operating request stream")
665 .expect("stream ended unexpectedly")
666 {
667 fnet_interfaces_admin::ControlRequest::GetId { responder } => {
668 std::mem::drop(responder);
670 }
671 request => panic!("unexpected request {:?}", request),
672 }
673 },
674 )
675 .await;
676 }
677
678 #[fuchsia_async::run_singlethreaded(test)]
679 async fn control_pipelined_error() {
680 let (control, request_stream) =
681 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
682 let control = super::Control::new(control);
683 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
684 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
685 let () = request_stream
686 .control_handle()
687 .send_on_interface_removed(CLOSE_REASON)
688 .expect("send terminal event");
689 std::mem::drop(request_stream);
690 assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
691 assert_matches::assert_matches!(
692 control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
693 zx::Status::INTERNAL.into()
694 ))),
695 Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
696 fidl::TransportError::Status(zx::Status::INTERNAL)
697 )))
698 );
699 #[cfg(target_os = "fuchsia")]
700 assert_matches::assert_matches!(
701 control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
702 status: zx::Status::PEER_CLOSED,
703 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
704 epitaph: None,
705 })),
706 Err(super::TerminalError::Terminal(CLOSE_REASON))
707 );
708 }
709
710 #[fuchsia_async::run_singlethreaded(test)]
711 async fn control_wait_termination() {
712 let (control, request_stream) =
713 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
714 let control = super::Control::new(control);
715 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
716 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
717 let () = request_stream
718 .control_handle()
719 .send_on_interface_removed(CLOSE_REASON)
720 .expect("send terminal event");
721 std::mem::drop(request_stream);
722 assert_matches::assert_matches!(
723 control.wait_termination().await,
724 super::TerminalError::Terminal(CLOSE_REASON)
725 );
726 }
727
728 #[fuchsia_async::run_singlethreaded(test)]
729 async fn control_respond_and_drop() {
730 const ID: u64 = 15;
731 let (control, mut request_stream) =
732 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
733 let control = super::Control::new(control);
734 let ((), ()) = futures::future::join(
735 async move {
736 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
737 },
738 async move {
739 let responder = request_stream
740 .try_next()
741 .await
742 .expect("operating request stream")
743 .expect("stream ended unexpectedly")
744 .into_get_id()
745 .expect("unexpected request");
746 let () = responder.send(ID).expect("failed to send response");
747 },
748 )
749 .await;
750 }
751
752 #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
757 #[test_case(
758 Err(fidl::Error::InvalidHeader),
759 Ok(Some(InterfaceRemovedReason::User)),
760 Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
761 "returns query error when not closed"
762 )]
763 #[test_case(
764 Err(fidl::Error::ClientChannelClosed {
765 status: zx::Status::PEER_CLOSED,
766 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
767 #[cfg(not(target_os = "fuchsia"))]
768 reason: None,
769 epitaph: None,
770 }),
771 Ok(Some(InterfaceRemovedReason::User)),
772 Err(TerminalError::Terminal(InterfaceRemovedReason::User));
773 "returns terminal error when channel closed"
774 )]
775 #[test_case(
776 Err(fidl::Error::ClientChannelClosed {
777 status: zx::Status::PEER_CLOSED,
778 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
779 #[cfg(not(target_os = "fuchsia"))]
780 reason: None,
781 epitaph: None,
782 }),
783 Ok(None),
784 Err(TerminalError::Fidl(
785 fidl::Error::ClientChannelClosed {
786 status: zx::Status::PEER_CLOSED,
787 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
788 #[cfg(not(target_os = "fuchsia"))]
789 reason: None,
790 epitaph: None,
791 }
792 ));
793 "returns query error when no terminal error"
794 )]
795 #[test_case(
796 Err(fidl::Error::ClientChannelClosed {
797 status: zx::Status::PEER_CLOSED,
798 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
799 #[cfg(not(target_os = "fuchsia"))]
800 reason: None,
801 epitaph: None,
802 }),
803 Err(fidl::Error::InvalidHeader),
804 Err(TerminalError::Fidl(
805 fidl::Error::ClientChannelClosed {
806 status: zx::Status::PEER_CLOSED,
807 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
808 #[cfg(not(target_os = "fuchsia"))]
809 reason: None,
810 epitaph: None,
811 }
812 ));
813 "returns query error when terminal event returns a fidl error"
814 )]
815 #[fuchsia_async::run_singlethreaded(test)]
816 async fn control_polling_race(
817 left_future_result: Result<(), fidl::Error>,
818 right_future_result: Result<
819 Option<fnet_interfaces_admin::InterfaceRemovedReason>,
820 fidl::Error,
821 >,
822 expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
823 ) {
824 let mut polled = false;
825 let first_future = std::future::poll_fn(|_cx| {
826 if polled {
827 Poll::Ready(left_future_result.clone())
828 } else {
829 polled = true;
830 Poll::Pending
831 }
832 })
833 .fuse();
834
835 let second_future =
836 std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
837
838 let res = or_terminal_event(first_future, second_future).await;
839 match (res, expected) {
840 (Ok(()), Ok(())) => (),
841 (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
842 if res == expected => {}
843 (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
846 (res, expected) => panic!("expected {:?} got {:?}", expected, res),
847 }
848 }
849
850 #[test]
851 fn convert_proof_to_grant() {
852 let event = fidl::Event::create();
857 let grant = fnet_interfaces_admin::GrantForInterfaceAuthorization {
858 interface_id: Default::default(),
859 token: event,
860 };
861
862 let fnet_interfaces_admin::ProofOfInterfaceAuthorization { interface_id, token } =
863 proof_from_grant(&grant);
864 assert_eq!(interface_id, Default::default());
865 assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
866 }
867}