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