1use fidl::endpoints::ProtocolMarker as _;
8use fidl::{HandleBased, Rights};
9use fidl_fuchsia_net_interfaces as fnet_interfaces;
10use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
11use fidl_fuchsia_net_resources as fnet_resources;
12use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
13use thiserror::Error;
14use zx_status as zx;
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_resources::GrantForInterfaceAuthorization,
162) -> fnet_resources::ProofOfInterfaceAuthorization {
163 let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } = grant;
164
165 fnet_resources::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_resources::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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
474pub enum NetstackManagedRoutesDesignation {
475 #[default]
477 Main,
478 InterfaceLocal,
484}
485
486#[derive(Error, Debug)]
488#[error("unknown designation for netsack managed routes: {0}")]
489pub struct UnknownNetstackManagedRoutesDesignation(pub u64);
490
491impl TryFrom<fnet_interfaces_admin::NetstackManagedRoutesDesignation>
492 for NetstackManagedRoutesDesignation
493{
494 type Error = UnknownNetstackManagedRoutesDesignation;
495
496 fn try_from(
497 value: fnet_interfaces_admin::NetstackManagedRoutesDesignation,
498 ) -> Result<Self, Self::Error> {
499 match value {
500 fnet_interfaces_admin::NetstackManagedRoutesDesignation::Main(
501 fnet_interfaces_admin::Empty,
502 ) => Ok(Self::Main),
503 fnet_interfaces_admin::NetstackManagedRoutesDesignation::InterfaceLocal(
504 fnet_interfaces_admin::Empty,
505 ) => Ok(Self::InterfaceLocal),
506 fnet_interfaces_admin::NetstackManagedRoutesDesignation::__SourceBreaking {
507 unknown_ordinal,
508 } => Err(UnknownNetstackManagedRoutesDesignation(unknown_ordinal)),
509 }
510 }
511}
512
513impl From<NetstackManagedRoutesDesignation>
514 for fnet_interfaces_admin::NetstackManagedRoutesDesignation
515{
516 fn from(value: NetstackManagedRoutesDesignation) -> Self {
517 match value {
518 NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
519 NetstackManagedRoutesDesignation::InterfaceLocal => {
520 Self::InterfaceLocal(fnet_interfaces_admin::Empty)
521 }
522 }
523 }
524}
525
526#[cfg(test)]
527mod test {
528 use std::task::Poll;
529
530 use assert_matches::assert_matches;
531 use fidl::Rights;
532 use fidl::prelude::*;
533 use fidl_fuchsia_net_interfaces as fnet_interfaces;
534 use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
535 use fnet_interfaces_admin::InterfaceRemovedReason;
536 use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
537 use test_case::test_case;
538 use zx_status as zx;
539
540 use super::*;
541
542 #[fuchsia_async::run_singlethreaded(test)]
544 async fn test_assignment_state_stream() {
545 let (address_state_provider, server_end) =
546 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
547 let state_stream = assignment_state_stream(address_state_provider);
548 futures::pin_mut!(state_stream);
549
550 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
551 fnet_interfaces_admin::AddressRemovalReason::Invalid;
552 {
553 let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
554
555 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
556 fnet_interfaces::AddressAssignmentState::Assigned;
557 let state_fut = state_stream.try_next().map(|r| {
558 assert_eq!(
559 r.expect("state stream error").expect("state stream ended"),
560 ASSIGNMENT_STATE_ASSIGNED
561 )
562 });
563 let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
564 fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
565 responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
566 }
567 req => panic!("unexpected method called: {:?}", req),
568 });
569 let ((), ()) = futures::join!(state_fut, handle_fut);
570
571 control_handle
572 .send_on_address_removed(REMOVAL_REASON_INVALID)
573 .expect("failed to send fake INVALID address removal reason event");
574 }
575
576 assert_matches::assert_matches!(
577 state_stream.try_collect::<Vec<_>>().await,
578 Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
579 );
580 }
581
582 #[fuchsia_async::run_singlethreaded(test)]
585 async fn test_assignment_state_stream_single_error() {
586 let (address_state_provider, server_end) =
587 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
588 let state_stream = assignment_state_stream(address_state_provider);
589
590 server_end
591 .close_with_epitaph(fidl::Status::INTERNAL)
592 .expect("failed to send INTERNAL epitaph");
593
594 assert_matches::assert_matches!(
597 state_stream
598 .collect::<Vec<_>>()
599 .now_or_never()
600 .expect("state stream not immediately ready")
601 .as_slice(),
602 [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
603 status: fidl::Status::INTERNAL,
604 #[cfg(not(target_os = "fuchsia"))]
605 reason: None,
606 ..
607 }))]
608 );
609 }
610
611 #[fuchsia_async::run_singlethreaded(test)]
614 async fn assignment_state_stream_state_before_event() {
615 let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
616 fnet_interfaces_admin::AddressStateProviderMarker,
617 >();
618
619 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
620 fnet_interfaces::AddressAssignmentState::Assigned;
621 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
622 fnet_interfaces_admin::AddressRemovalReason::Invalid;
623
624 let ((), ()) = futures::future::join(
625 async move {
626 request_stream
627 .try_next()
628 .await
629 .expect("request stream error")
630 .expect("request stream ended")
631 .into_watch_address_assignment_state()
632 .expect("unexpected request")
633 .send(ASSIGNMENT_STATE_ASSIGNED)
634 .expect("failed to send stubbed assignment state");
635 request_stream
636 .control_handle()
637 .send_on_address_removed(REMOVAL_REASON_INVALID)
638 .expect("failed to send fake INVALID address removal reason event");
639 },
640 async move {
641 let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
642 assert_matches::assert_matches!(
643 got.as_slice(),
644 &[
645 Ok(got_state),
646 Err(AddressStateProviderError::AddressRemoved(got_reason)),
647 ] => {
648 assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
649 assert_eq!(got_reason, REMOVAL_REASON_INVALID);
650 }
651 );
652 },
653 )
654 .await;
655 }
656
657 #[fuchsia_async::run_singlethreaded(test)]
659 async fn control_terminal_event() {
660 let (control, mut request_stream) =
661 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
662 let control = super::Control::new(control);
663 const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
664 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
665 const ID: u64 = 15;
666 let ((), ()) = futures::future::join(
667 async move {
668 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
669 assert_matches::assert_matches!(
670 control.get_id().await,
671 Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
672 );
673 },
674 async move {
675 let responder = request_stream
676 .try_next()
677 .await
678 .expect("operating request stream")
679 .expect("stream ended unexpectedly")
680 .into_get_id()
681 .expect("unexpected request");
682 responder.send(ID).expect("failed to send response");
683 request_stream
684 .control_handle()
685 .send_on_interface_removed(EXPECTED_EVENT)
686 .expect("sending terminal event");
687 },
688 )
689 .await;
690 }
691
692 #[fuchsia_async::run_singlethreaded(test)]
695 async fn control_missing_terminal_event() {
696 let (control, mut request_stream) =
697 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
698 let control = super::Control::new(control);
699 let ((), ()) = futures::future::join(
700 async move {
701 assert_matches::assert_matches!(
702 control.get_id().await,
703 Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
704 status: zx::Status::PEER_CLOSED,
705 protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
706 #[cfg(not(target_os = "fuchsia"))]
707 reason: None,
708 ..
709 }))
710 );
711 },
712 async move {
713 match request_stream
714 .try_next()
715 .await
716 .expect("operating request stream")
717 .expect("stream ended unexpectedly")
718 {
719 fnet_interfaces_admin::ControlRequest::GetId { responder } => {
720 std::mem::drop(responder);
722 }
723 request => panic!("unexpected request {:?}", request),
724 }
725 },
726 )
727 .await;
728 }
729
730 #[fuchsia_async::run_singlethreaded(test)]
731 async fn control_pipelined_error() {
732 let (control, request_stream) =
733 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
734 let control = super::Control::new(control);
735 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
736 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
737 request_stream
738 .control_handle()
739 .send_on_interface_removed(CLOSE_REASON)
740 .expect("send terminal event");
741 std::mem::drop(request_stream);
742 assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
743 assert_matches::assert_matches!(
744 control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
745 zx::Status::INTERNAL.into()
746 ))),
747 Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
748 fidl::TransportError::Status(zx::Status::INTERNAL)
749 )))
750 );
751 #[cfg(target_os = "fuchsia")]
752 assert_matches::assert_matches!(
753 control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
754 status: zx::Status::PEER_CLOSED,
755 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
756 epitaph: None,
757 })),
758 Err(super::TerminalError::Terminal(CLOSE_REASON))
759 );
760 }
761
762 #[fuchsia_async::run_singlethreaded(test)]
763 async fn control_wait_termination() {
764 let (control, request_stream) =
765 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
766 let control = super::Control::new(control);
767 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
768 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
769 request_stream
770 .control_handle()
771 .send_on_interface_removed(CLOSE_REASON)
772 .expect("send terminal event");
773 std::mem::drop(request_stream);
774 assert_matches::assert_matches!(
775 control.wait_termination().await,
776 super::TerminalError::Terminal(CLOSE_REASON)
777 );
778 }
779
780 #[fuchsia_async::run_singlethreaded(test)]
781 async fn control_respond_and_drop() {
782 const ID: u64 = 15;
783 let (control, mut request_stream) =
784 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
785 let control = super::Control::new(control);
786 let ((), ()) = futures::future::join(
787 async move {
788 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
789 },
790 async move {
791 let responder = request_stream
792 .try_next()
793 .await
794 .expect("operating request stream")
795 .expect("stream ended unexpectedly")
796 .into_get_id()
797 .expect("unexpected request");
798 responder.send(ID).expect("failed to send response");
799 },
800 )
801 .await;
802 }
803
804 #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
809 #[test_case(
810 Err(fidl::Error::InvalidHeader),
811 Ok(Some(InterfaceRemovedReason::User)),
812 Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
813 "returns query error when not closed"
814 )]
815 #[test_case(
816 Err(fidl::Error::ClientChannelClosed {
817 status: zx::Status::PEER_CLOSED,
818 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
819 #[cfg(not(target_os = "fuchsia"))]
820 reason: None,
821 epitaph: None,
822 }),
823 Ok(Some(InterfaceRemovedReason::User)),
824 Err(TerminalError::Terminal(InterfaceRemovedReason::User));
825 "returns terminal error when channel closed"
826 )]
827 #[test_case(
828 Err(fidl::Error::ClientChannelClosed {
829 status: zx::Status::PEER_CLOSED,
830 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
831 #[cfg(not(target_os = "fuchsia"))]
832 reason: None,
833 epitaph: None,
834 }),
835 Ok(None),
836 Err(TerminalError::Fidl(
837 fidl::Error::ClientChannelClosed {
838 status: zx::Status::PEER_CLOSED,
839 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
840 #[cfg(not(target_os = "fuchsia"))]
841 reason: None,
842 epitaph: None,
843 }
844 ));
845 "returns query error when no terminal error"
846 )]
847 #[test_case(
848 Err(fidl::Error::ClientChannelClosed {
849 status: zx::Status::PEER_CLOSED,
850 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
851 #[cfg(not(target_os = "fuchsia"))]
852 reason: None,
853 epitaph: None,
854 }),
855 Err(fidl::Error::InvalidHeader),
856 Err(TerminalError::Fidl(
857 fidl::Error::ClientChannelClosed {
858 status: zx::Status::PEER_CLOSED,
859 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
860 #[cfg(not(target_os = "fuchsia"))]
861 reason: None,
862 epitaph: None,
863 }
864 ));
865 "returns query error when terminal event returns a fidl error"
866 )]
867 #[fuchsia_async::run_singlethreaded(test)]
868 async fn control_polling_race(
869 left_future_result: Result<(), fidl::Error>,
870 right_future_result: Result<
871 Option<fnet_interfaces_admin::InterfaceRemovedReason>,
872 fidl::Error,
873 >,
874 expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
875 ) {
876 let mut polled = false;
877 let first_future = std::future::poll_fn(|_cx| {
878 if polled {
879 Poll::Ready(left_future_result.clone())
880 } else {
881 polled = true;
882 Poll::Pending
883 }
884 })
885 .fuse();
886
887 let second_future =
888 std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
889
890 let res = or_terminal_event(first_future, second_future).await;
891 match (res, expected) {
892 (Ok(()), Ok(())) => (),
893 (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
894 if res == expected => {}
895 (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
898 (res, expected) => panic!("expected {:?} got {:?}", expected, res),
899 }
900 }
901
902 #[test]
903 fn convert_proof_to_grant() {
904 let event = fidl::Event::create();
909 let grant = fnet_resources::GrantForInterfaceAuthorization {
910 interface_id: Default::default(),
911 token: event,
912 };
913
914 let fnet_resources::ProofOfInterfaceAuthorization { interface_id, token } =
915 proof_from_grant(&grant);
916 assert_eq!(interface_id, Default::default());
917 assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
918 }
919}