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#[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 super::{
531 assignment_state_stream, or_terminal_event, proof_from_grant, AddressStateProviderError,
532 TerminalError,
533 };
534 use assert_matches::assert_matches;
535 use fidl::prelude::*;
536 use fidl::Rights;
537 use fnet_interfaces_admin::InterfaceRemovedReason;
538 use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
539 use test_case::test_case;
540 use {
541 fidl_fuchsia_net_interfaces as fnet_interfaces,
542 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin, zx_status as zx,
543 };
544
545 #[fuchsia_async::run_singlethreaded(test)]
547 async fn test_assignment_state_stream() {
548 let (address_state_provider, server_end) =
549 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
550 let state_stream = assignment_state_stream(address_state_provider);
551 futures::pin_mut!(state_stream);
552
553 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
554 fnet_interfaces_admin::AddressRemovalReason::Invalid;
555 {
556 let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
557
558 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
559 fnet_interfaces::AddressAssignmentState::Assigned;
560 let state_fut = state_stream.try_next().map(|r| {
561 assert_eq!(
562 r.expect("state stream error").expect("state stream ended"),
563 ASSIGNMENT_STATE_ASSIGNED
564 )
565 });
566 let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
567 fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
568 let () = responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
569 }
570 req => panic!("unexpected method called: {:?}", req),
571 });
572 let ((), ()) = futures::join!(state_fut, handle_fut);
573
574 let () = control_handle
575 .send_on_address_removed(REMOVAL_REASON_INVALID)
576 .expect("failed to send fake INVALID address removal reason event");
577 }
578
579 assert_matches::assert_matches!(
580 state_stream.try_collect::<Vec<_>>().await,
581 Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
582 );
583 }
584
585 #[fuchsia_async::run_singlethreaded(test)]
588 async fn test_assignment_state_stream_single_error() {
589 let (address_state_provider, server_end) =
590 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
591 let state_stream = assignment_state_stream(address_state_provider);
592
593 let () = server_end
594 .close_with_epitaph(fidl::Status::INTERNAL)
595 .expect("failed to send INTERNAL epitaph");
596
597 assert_matches::assert_matches!(
600 state_stream
601 .collect::<Vec<_>>()
602 .now_or_never()
603 .expect("state stream not immediately ready")
604 .as_slice(),
605 [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
606 status: fidl::Status::INTERNAL,
607 #[cfg(not(target_os = "fuchsia"))]
608 reason: None,
609 ..
610 }))]
611 );
612 }
613
614 #[fuchsia_async::run_singlethreaded(test)]
617 async fn assignment_state_stream_state_before_event() {
618 let (address_state_provider, mut request_stream) = fidl::endpoints::create_proxy_and_stream::<
619 fnet_interfaces_admin::AddressStateProviderMarker,
620 >();
621
622 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
623 fnet_interfaces::AddressAssignmentState::Assigned;
624 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
625 fnet_interfaces_admin::AddressRemovalReason::Invalid;
626
627 let ((), ()) = futures::future::join(
628 async move {
629 let () = request_stream
630 .try_next()
631 .await
632 .expect("request stream error")
633 .expect("request stream ended")
634 .into_watch_address_assignment_state()
635 .expect("unexpected request")
636 .send(ASSIGNMENT_STATE_ASSIGNED)
637 .expect("failed to send stubbed assignment state");
638 let () = request_stream
639 .control_handle()
640 .send_on_address_removed(REMOVAL_REASON_INVALID)
641 .expect("failed to send fake INVALID address removal reason event");
642 },
643 async move {
644 let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
645 assert_matches::assert_matches!(
646 got.as_slice(),
647 &[
648 Ok(got_state),
649 Err(AddressStateProviderError::AddressRemoved(got_reason)),
650 ] => {
651 assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
652 assert_eq!(got_reason, REMOVAL_REASON_INVALID);
653 }
654 );
655 },
656 )
657 .await;
658 }
659
660 #[fuchsia_async::run_singlethreaded(test)]
662 async fn control_terminal_event() {
663 let (control, mut request_stream) =
664 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
665 let control = super::Control::new(control);
666 const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
667 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
668 const ID: u64 = 15;
669 let ((), ()) = futures::future::join(
670 async move {
671 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
672 assert_matches::assert_matches!(
673 control.get_id().await,
674 Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
675 );
676 },
677 async move {
678 let responder = request_stream
679 .try_next()
680 .await
681 .expect("operating request stream")
682 .expect("stream ended unexpectedly")
683 .into_get_id()
684 .expect("unexpected request");
685 let () = responder.send(ID).expect("failed to send response");
686 let () = request_stream
687 .control_handle()
688 .send_on_interface_removed(EXPECTED_EVENT)
689 .expect("sending terminal event");
690 },
691 )
692 .await;
693 }
694
695 #[fuchsia_async::run_singlethreaded(test)]
698 async fn control_missing_terminal_event() {
699 let (control, mut request_stream) =
700 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
701 let control = super::Control::new(control);
702 let ((), ()) = futures::future::join(
703 async move {
704 assert_matches::assert_matches!(
705 control.get_id().await,
706 Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
707 status: zx::Status::PEER_CLOSED,
708 protocol_name: fidl_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
709 #[cfg(not(target_os = "fuchsia"))]
710 reason: None,
711 ..
712 }))
713 );
714 },
715 async move {
716 match request_stream
717 .try_next()
718 .await
719 .expect("operating request stream")
720 .expect("stream ended unexpectedly")
721 {
722 fnet_interfaces_admin::ControlRequest::GetId { responder } => {
723 std::mem::drop(responder);
725 }
726 request => panic!("unexpected request {:?}", request),
727 }
728 },
729 )
730 .await;
731 }
732
733 #[fuchsia_async::run_singlethreaded(test)]
734 async fn control_pipelined_error() {
735 let (control, request_stream) =
736 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
737 let control = super::Control::new(control);
738 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
739 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
740 let () = request_stream
741 .control_handle()
742 .send_on_interface_removed(CLOSE_REASON)
743 .expect("send terminal event");
744 std::mem::drop(request_stream);
745 assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
746 assert_matches::assert_matches!(
747 control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
748 zx::Status::INTERNAL.into()
749 ))),
750 Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
751 fidl::TransportError::Status(zx::Status::INTERNAL)
752 )))
753 );
754 #[cfg(target_os = "fuchsia")]
755 assert_matches::assert_matches!(
756 control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
757 status: zx::Status::PEER_CLOSED,
758 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
759 epitaph: None,
760 })),
761 Err(super::TerminalError::Terminal(CLOSE_REASON))
762 );
763 }
764
765 #[fuchsia_async::run_singlethreaded(test)]
766 async fn control_wait_termination() {
767 let (control, request_stream) =
768 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
769 let control = super::Control::new(control);
770 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
771 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
772 let () = request_stream
773 .control_handle()
774 .send_on_interface_removed(CLOSE_REASON)
775 .expect("send terminal event");
776 std::mem::drop(request_stream);
777 assert_matches::assert_matches!(
778 control.wait_termination().await,
779 super::TerminalError::Terminal(CLOSE_REASON)
780 );
781 }
782
783 #[fuchsia_async::run_singlethreaded(test)]
784 async fn control_respond_and_drop() {
785 const ID: u64 = 15;
786 let (control, mut request_stream) =
787 fidl::endpoints::create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
788 let control = super::Control::new(control);
789 let ((), ()) = futures::future::join(
790 async move {
791 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
792 },
793 async move {
794 let responder = request_stream
795 .try_next()
796 .await
797 .expect("operating request stream")
798 .expect("stream ended unexpectedly")
799 .into_get_id()
800 .expect("unexpected request");
801 let () = responder.send(ID).expect("failed to send response");
802 },
803 )
804 .await;
805 }
806
807 #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
812 #[test_case(
813 Err(fidl::Error::InvalidHeader),
814 Ok(Some(InterfaceRemovedReason::User)),
815 Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
816 "returns query error when not closed"
817 )]
818 #[test_case(
819 Err(fidl::Error::ClientChannelClosed {
820 status: zx::Status::PEER_CLOSED,
821 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
822 #[cfg(not(target_os = "fuchsia"))]
823 reason: None,
824 epitaph: None,
825 }),
826 Ok(Some(InterfaceRemovedReason::User)),
827 Err(TerminalError::Terminal(InterfaceRemovedReason::User));
828 "returns terminal error when channel closed"
829 )]
830 #[test_case(
831 Err(fidl::Error::ClientChannelClosed {
832 status: zx::Status::PEER_CLOSED,
833 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
834 #[cfg(not(target_os = "fuchsia"))]
835 reason: None,
836 epitaph: None,
837 }),
838 Ok(None),
839 Err(TerminalError::Fidl(
840 fidl::Error::ClientChannelClosed {
841 status: zx::Status::PEER_CLOSED,
842 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
843 #[cfg(not(target_os = "fuchsia"))]
844 reason: None,
845 epitaph: None,
846 }
847 ));
848 "returns query error when no terminal error"
849 )]
850 #[test_case(
851 Err(fidl::Error::ClientChannelClosed {
852 status: zx::Status::PEER_CLOSED,
853 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
854 #[cfg(not(target_os = "fuchsia"))]
855 reason: None,
856 epitaph: None,
857 }),
858 Err(fidl::Error::InvalidHeader),
859 Err(TerminalError::Fidl(
860 fidl::Error::ClientChannelClosed {
861 status: zx::Status::PEER_CLOSED,
862 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
863 #[cfg(not(target_os = "fuchsia"))]
864 reason: None,
865 epitaph: None,
866 }
867 ));
868 "returns query error when terminal event returns a fidl error"
869 )]
870 #[fuchsia_async::run_singlethreaded(test)]
871 async fn control_polling_race(
872 left_future_result: Result<(), fidl::Error>,
873 right_future_result: Result<
874 Option<fnet_interfaces_admin::InterfaceRemovedReason>,
875 fidl::Error,
876 >,
877 expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
878 ) {
879 let mut polled = false;
880 let first_future = std::future::poll_fn(|_cx| {
881 if polled {
882 Poll::Ready(left_future_result.clone())
883 } else {
884 polled = true;
885 Poll::Pending
886 }
887 })
888 .fuse();
889
890 let second_future =
891 std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
892
893 let res = or_terminal_event(first_future, second_future).await;
894 match (res, expected) {
895 (Ok(()), Ok(())) => (),
896 (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
897 if res == expected => {}
898 (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
901 (res, expected) => panic!("expected {:?} got {:?}", expected, res),
902 }
903 }
904
905 #[test]
906 fn convert_proof_to_grant() {
907 let event = fidl::Event::create();
912 let grant = fnet_interfaces_admin::GrantForInterfaceAuthorization {
913 interface_id: Default::default(),
914 token: event,
915 };
916
917 let fnet_interfaces_admin::ProofOfInterfaceAuthorization { interface_id, token } =
918 proof_from_grant(&grant);
919 assert_eq!(interface_id, Default::default());
920 assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
921 }
922}