1use flex_fuchsia_net_interfaces as fnet_interfaces;
8use flex_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
9use flex_fuchsia_net_resources as fnet_resources;
10use futures::{Future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
11use thiserror::Error;
12
13#[derive(Error, Debug)]
15pub enum AddressStateProviderError {
16 #[error("address removed: {0:?}")]
18 AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
19 #[error("fidl error")]
21 Fidl(#[from] fidl::Error),
22 #[error("AddressStateProvider channel closed")]
24 ChannelClosed,
25}
26
27impl From<TerminalError<fnet_interfaces_admin::AddressRemovalReason>>
28 for AddressStateProviderError
29{
30 fn from(e: TerminalError<fnet_interfaces_admin::AddressRemovalReason>) -> Self {
31 match e {
32 TerminalError::Fidl(e) => AddressStateProviderError::Fidl(e),
33 TerminalError::Terminal(r) => AddressStateProviderError::AddressRemoved(r),
34 }
35 }
36}
37
38pub async fn wait_for_address_added_event(
42 event_stream: &mut fnet_interfaces_admin::AddressStateProviderEventStream,
43) -> Result<(), AddressStateProviderError> {
44 let event = event_stream
45 .next()
46 .await
47 .ok_or(AddressStateProviderError::ChannelClosed)?
48 .map_err(AddressStateProviderError::Fidl)?;
49 match event {
50 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(()),
51 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
52 Err(AddressStateProviderError::AddressRemoved(error))
53 }
54 }
55}
56
57pub fn assignment_state_stream(
70 address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
71) -> impl Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
72{
73 let event_fut = address_state_provider
74 .take_event_stream()
75 .filter_map(|event| {
76 futures::future::ready(match event {
77 Ok(event) => match event {
78 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => None,
79 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
80 error,
81 } => Some(AddressStateProviderError::AddressRemoved(error)),
82 },
83 Err(e) => Some(AddressStateProviderError::Fidl(e)),
84 })
85 })
86 .into_future()
87 .map(|(event, _stream)| event.unwrap_or(AddressStateProviderError::ChannelClosed));
88 futures::stream::try_unfold(
89 (address_state_provider, event_fut),
90 |(address_state_provider, event_fut)| {
91 futures::future::select(
96 address_state_provider.watch_address_assignment_state(),
97 event_fut,
98 )
99 .then(|s| match s {
100 futures::future::Either::Left((state_result, event_fut)) => match state_result {
101 Ok(state) => {
102 futures::future::ok(Some((state, (address_state_provider, event_fut))))
103 .left_future()
104 }
105 Err(e) if e.is_closed() => event_fut.map(Result::Err).right_future(),
106 Err(e) => {
107 futures::future::err(AddressStateProviderError::Fidl(e)).left_future()
108 }
109 },
110 futures::future::Either::Right((error, _state_fut)) => {
111 futures::future::err(error).left_future()
112 }
113 })
114 },
115 )
116}
117
118pub async fn wait_assignment_state<S>(
126 stream: S,
127 want: fnet_interfaces::AddressAssignmentState,
128) -> Result<(), AddressStateProviderError>
129where
130 S: Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
131 + Unpin,
132{
133 stream
134 .try_filter_map(|state| futures::future::ok((state == want).then_some(())))
135 .try_next()
136 .await
137 .and_then(|opt| opt.ok_or_else(|| AddressStateProviderError::ChannelClosed))
138}
139
140type ControlEventStreamFutureToReason =
141 fn(
142 (
143 Option<Result<fnet_interfaces_admin::ControlEvent, fidl::Error>>,
144 fnet_interfaces_admin::ControlEventStream,
145 ),
146 ) -> Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>;
147
148#[cfg(not(feature = "fdomain"))]
158pub fn proof_from_grant(
159 grant: &fnet_resources::GrantForInterfaceAuthorization,
160) -> fnet_resources::ProofOfInterfaceAuthorization {
161 let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } = grant;
162
163 fnet_resources::ProofOfInterfaceAuthorization {
168 interface_id: *interface_id,
169 token: token.duplicate_handle(fidl::Rights::TRANSFER).unwrap(),
170 }
171}
172
173#[derive(Clone)]
176pub struct Control {
177 proxy: fnet_interfaces_admin::ControlProxy,
178 terminal_event_fut: futures::future::Shared<
186 futures::future::Map<
187 futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
188 ControlEventStreamFutureToReason,
189 >,
190 >,
191}
192
193async fn or_terminal_event<QR, QF, TR>(
196 query_fut: QF,
197 terminal_event_fut: TR,
198) -> Result<QR, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>
199where
200 QR: Unpin,
201 QF: Unpin + Future<Output = Result<QR, fidl::Error>>,
202 TR: Unpin
203 + Future<Output = Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>>,
204{
205 match futures::future::select(query_fut, terminal_event_fut).await {
206 futures::future::Either::Left((query_result, terminal_event_fut)) => match query_result {
207 Ok(ok) => Ok(ok),
208 Err(e) if e.is_closed() => match terminal_event_fut.await {
209 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
210 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
211 },
212 Err(e) => Err(TerminalError::Fidl(e)),
213 },
214 futures::future::Either::Right((event, query_fut)) => {
215 if let Some(query_result) = query_fut.now_or_never() {
231 match query_result {
232 Ok(ok) => Ok(ok),
233 Err(e) if e.is_closed() => match event {
234 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
235 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
236 },
237 Err(e) => Err(TerminalError::Fidl(e)),
238 }
239 } else {
240 match event.map_err(|e| TerminalError::Fidl(e))? {
241 Some(removal_reason) => Err(TerminalError::Terminal(removal_reason)),
242 None => Err(TerminalError::Fidl(fidl::Error::ClientChannelClosed {
243 epitaph: fidl::Epitaph::PeerClosed,
244 protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
245 #[cfg(not(target_os = "fuchsia"))]
246 reason: None,
247 })),
248 }
249 }
250 }
251 }
252}
253
254impl Control {
255 pub fn add_address(
257 &self,
258 address: &flex_fuchsia_net::Subnet,
259 parameters: &fnet_interfaces_admin::AddressParameters,
260 address_state_provider: flex_client::fidl::ServerEnd<
261 fnet_interfaces_admin::AddressStateProviderMarker,
262 >,
263 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
264 self.or_terminal_event_no_return(self.proxy.add_address(
265 address,
266 parameters,
267 address_state_provider,
268 ))
269 }
270
271 pub async fn get_id(
273 &self,
274 ) -> Result<u64, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
275 self.or_terminal_event(self.proxy.get_id()).await
276 }
277
278 pub async fn remove_address(
280 &self,
281 address: &flex_fuchsia_net::Subnet,
282 ) -> Result<
283 fnet_interfaces_admin::ControlRemoveAddressResult,
284 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
285 > {
286 self.or_terminal_event(self.proxy.remove_address(address)).await
287 }
288
289 pub async fn set_configuration(
291 &self,
292 config: &fnet_interfaces_admin::Configuration,
293 ) -> Result<
294 fnet_interfaces_admin::ControlSetConfigurationResult,
295 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
296 > {
297 self.or_terminal_event(self.proxy.set_configuration(config)).await
298 }
299
300 pub async fn get_configuration(
302 &self,
303 ) -> Result<
304 fnet_interfaces_admin::ControlGetConfigurationResult,
305 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
306 > {
307 self.or_terminal_event(self.proxy.get_configuration()).await
308 }
309
310 pub async fn get_authorization_for_interface(
312 &self,
313 ) -> Result<
314 fnet_resources::GrantForInterfaceAuthorization,
315 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
316 > {
317 self.or_terminal_event(self.proxy.get_authorization_for_interface()).await
318 }
319
320 pub async fn enable(
322 &self,
323 ) -> Result<
324 fnet_interfaces_admin::ControlEnableResult,
325 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
326 > {
327 self.or_terminal_event(self.proxy.enable()).await
328 }
329
330 pub async fn remove(
332 &self,
333 ) -> Result<
334 fnet_interfaces_admin::ControlRemoveResult,
335 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
336 > {
337 self.or_terminal_event(self.proxy.remove()).await
338 }
339
340 pub async fn disable(
342 &self,
343 ) -> Result<
344 fnet_interfaces_admin::ControlDisableResult,
345 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
346 > {
347 self.or_terminal_event(self.proxy.disable()).await
348 }
349
350 pub fn detach(
352 &self,
353 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
354 self.or_terminal_event_no_return(self.proxy.detach())
355 }
356
357 pub fn new(proxy: fnet_interfaces_admin::ControlProxy) -> Self {
359 let terminal_event_fut = proxy
360 .take_event_stream()
361 .into_future()
362 .map::<_, ControlEventStreamFutureToReason>(|(event, _stream)| {
363 event
364 .map(|r| {
365 r.map(
366 |fnet_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
367 reason
368 },
369 )
370 })
371 .transpose()
372 })
373 .shared();
374 Self { proxy, terminal_event_fut }
375 }
376
377 pub async fn wait_termination(
379 self,
380 ) -> TerminalError<fnet_interfaces_admin::InterfaceRemovedReason> {
381 let Self { proxy: _, terminal_event_fut } = self;
382 match terminal_event_fut.await {
383 Ok(Some(event)) => TerminalError::Terminal(event),
384 Ok(None) => TerminalError::Fidl(fidl::Error::ClientChannelClosed {
385 epitaph: fidl::Epitaph::PeerClosed,
386 protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
387 #[cfg(not(target_os = "fuchsia"))]
388 reason: None,
389 }),
390 Err(e) => TerminalError::Fidl(e),
391 }
392 }
393
394 #[cfg(not(feature = "fdomain"))]
396 pub fn create_endpoints()
397 -> Result<(Self, fidl::endpoints::ServerEnd<fnet_interfaces_admin::ControlMarker>), fidl::Error>
398 {
399 let (proxy, server_end) = fidl::endpoints::create_proxy();
400 Ok((Self::new(proxy), server_end))
401 }
402
403 async fn or_terminal_event<R: Unpin, F: Unpin + Future<Output = Result<R, fidl::Error>>>(
404 &self,
405 fut: F,
406 ) -> Result<R, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
407 or_terminal_event(fut, self.terminal_event_fut.clone()).await
408 }
409
410 fn or_terminal_event_no_return(
411 &self,
412 r: Result<(), fidl::Error>,
413 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
414 r.map_err(|err| {
415 if !err.is_closed() {
416 return TerminalError::Fidl(err);
417 }
418 match self.terminal_event_fut.clone().now_or_never() {
426 Some(Ok(Some(terminal_event))) => TerminalError::Terminal(terminal_event),
427 Some(Err(e)) => {
428 let _: fidl::Error = e;
430 TerminalError::Fidl(err)
431 }
432 None | Some(Ok(None)) => TerminalError::Fidl(err),
433 }
434 })
435 }
436}
437
438impl std::fmt::Debug for Control {
439 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 let Self { proxy, terminal_event_fut: _ } = self;
441 fmt.debug_struct("Control").field("proxy", proxy).finish()
442 }
443}
444
445#[derive(Debug)]
447pub enum TerminalError<E> {
448 Terminal(E),
450 Fidl(fidl::Error),
452}
453
454impl<E> std::fmt::Display for TerminalError<E>
455where
456 E: std::fmt::Debug,
457{
458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459 match self {
460 TerminalError::Terminal(e) => write!(f, "terminal event: {:?}", e),
461 TerminalError::Fidl(e) => write!(f, "fidl error: {}", e),
462 }
463 }
464}
465
466impl<E: std::fmt::Debug> std::error::Error for TerminalError<E> {}
467
468#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
471pub enum NetstackManagedRoutesDesignation {
472 #[default]
474 Main,
475 InterfaceLocal,
481}
482
483#[derive(Error, Debug)]
485#[error("unknown designation for netsack managed routes: {0}")]
486pub struct UnknownNetstackManagedRoutesDesignation(pub u64);
487
488impl TryFrom<fnet_interfaces_admin::NetstackManagedRoutesDesignation>
489 for NetstackManagedRoutesDesignation
490{
491 type Error = UnknownNetstackManagedRoutesDesignation;
492
493 fn try_from(
494 value: fnet_interfaces_admin::NetstackManagedRoutesDesignation,
495 ) -> Result<Self, Self::Error> {
496 match value {
497 fnet_interfaces_admin::NetstackManagedRoutesDesignation::Main(
498 fnet_interfaces_admin::Empty,
499 ) => Ok(Self::Main),
500 fnet_interfaces_admin::NetstackManagedRoutesDesignation::InterfaceLocal(
501 fnet_interfaces_admin::Empty,
502 ) => Ok(Self::InterfaceLocal),
503 fnet_interfaces_admin::NetstackManagedRoutesDesignation::__SourceBreaking {
504 unknown_ordinal,
505 } => Err(UnknownNetstackManagedRoutesDesignation(unknown_ordinal)),
506 }
507 }
508}
509
510impl From<NetstackManagedRoutesDesignation>
511 for fnet_interfaces_admin::NetstackManagedRoutesDesignation
512{
513 fn from(value: NetstackManagedRoutesDesignation) -> Self {
514 match value {
515 NetstackManagedRoutesDesignation::Main => Self::Main(fnet_interfaces_admin::Empty),
516 NetstackManagedRoutesDesignation::InterfaceLocal => {
517 Self::InterfaceLocal(fnet_interfaces_admin::Empty)
518 }
519 }
520 }
521}
522
523#[cfg(test)]
524mod test {
525 use std::task::Poll;
526
527 use flex_client::fidl::{ProtocolMarker, RequestStream};
528 use flex_fuchsia_net_interfaces as fnet_interfaces;
529 use flex_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
530 use fnet_interfaces_admin::InterfaceRemovedReason;
531 use test_case::test_case;
532
533 use super::*;
534
535 #[fuchsia_async::run_singlethreaded(test)]
537 async fn test_assignment_state_stream() {
538 let client = flex_local::local_client_empty();
539 let (address_state_provider, server_end) =
540 client.create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
541 let state_stream = assignment_state_stream(address_state_provider);
542 futures::pin_mut!(state_stream);
543
544 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
545 fnet_interfaces_admin::AddressRemovalReason::Invalid;
546 {
547 let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
548
549 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
550 fnet_interfaces::AddressAssignmentState::Assigned;
551 let state_fut = state_stream.try_next().map(|r| {
552 assert_eq!(
553 r.expect("state stream error").expect("state stream ended"),
554 ASSIGNMENT_STATE_ASSIGNED
555 )
556 });
557 let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
558 fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
559 responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
560 }
561 req => panic!("unexpected method called: {:?}", req),
562 });
563 let ((), ()) = futures::join!(state_fut, handle_fut);
564
565 control_handle
566 .send_on_address_removed(REMOVAL_REASON_INVALID)
567 .expect("failed to send fake INVALID address removal reason event");
568 }
569
570 assert_matches::assert_matches!(
571 state_stream.try_collect::<Vec<_>>().await,
572 Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
573 );
574 }
575
576 #[fuchsia_async::run_singlethreaded(test)]
579 async fn test_assignment_state_stream_single_error() {
580 let client = flex_local::local_client_empty();
581 let (address_state_provider, server_end) =
582 client.create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
583 let state_stream = assignment_state_stream(address_state_provider);
584
585 server_end
586 .close_with_epitaph(fidl::Status::INTERNAL)
587 .expect("failed to send INTERNAL epitaph");
588
589 let states_fut = state_stream.collect::<Vec<_>>();
590
591 #[cfg(not(feature = "fdomain"))]
592 let states = states_fut.now_or_never().expect("state stream not immediately ready");
593
594 #[cfg(feature = "fdomain")]
597 let states = states_fut.await;
598
599 assert_matches::assert_matches!(
602 states.as_slice(),
603 [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
604 epitaph: fidl::Epitaph::Explicit(Err(fidl::Status::INTERNAL)),
605 #[cfg(not(target_os = "fuchsia"))]
606 reason: None,
607 ..
608 }))]
609 );
610 }
611
612 #[fuchsia_async::run_singlethreaded(test)]
615 async fn assignment_state_stream_state_before_event() {
616 let client = flex_local::local_client_empty();
617 let (address_state_provider, mut request_stream) =
618 client.create_proxy_and_stream::<fnet_interfaces_admin::AddressStateProviderMarker>();
619
620 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
621 fnet_interfaces::AddressAssignmentState::Assigned;
622 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
623 fnet_interfaces_admin::AddressRemovalReason::Invalid;
624
625 let ((), ()) = futures::future::join(
626 async move {
627 request_stream
628 .try_next()
629 .await
630 .expect("request stream error")
631 .expect("request stream ended")
632 .into_watch_address_assignment_state()
633 .expect("unexpected request")
634 .send(ASSIGNMENT_STATE_ASSIGNED)
635 .expect("failed to send stubbed assignment state");
636 request_stream
637 .control_handle()
638 .send_on_address_removed(REMOVAL_REASON_INVALID)
639 .expect("failed to send fake INVALID address removal reason event");
640 },
641 async move {
642 let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
643 assert_matches::assert_matches!(
644 got.as_slice(),
645 &[
646 Ok(got_state),
647 Err(AddressStateProviderError::AddressRemoved(got_reason)),
648 ] => {
649 assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
650 assert_eq!(got_reason, REMOVAL_REASON_INVALID);
651 }
652 );
653 },
654 )
655 .await;
656 }
657
658 #[fuchsia_async::run_singlethreaded(test)]
660 async fn control_terminal_event() {
661 let client = flex_local::local_client_empty();
662 let (control, mut request_stream) =
663 client.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 responder.send(ID).expect("failed to send response");
685 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 client = flex_local::local_client_empty();
699 let (control, mut request_stream) =
700 client.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 epitaph: fidl::Epitaph::PeerClosed,
708 protocol_name: flex_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 client = flex_local::local_client_empty();
736 let (control, request_stream) =
737 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
738 let control = super::Control::new(control);
739 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
740 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
741 request_stream
742 .control_handle()
743 .send_on_interface_removed(CLOSE_REASON)
744 .expect("send terminal event");
745 std::mem::drop(request_stream);
746 #[cfg(feature = "fdomain")]
747 {
748 let control_clone = control.clone();
749 let _ = control_clone.wait_termination().await;
750 }
751 assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
752 assert_matches::assert_matches!(
753 control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
754 zx_status::Status::INTERNAL.into()
755 ))),
756 Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
757 fidl::TransportError::Status(zx_status::Status::INTERNAL)
758 )))
759 );
760 #[cfg(target_os = "fuchsia")]
761 assert_matches::assert_matches!(
762 control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
763 epitaph: fidl::Epitaph::PeerClosed,
764 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
765 })),
766 Err(super::TerminalError::Terminal(CLOSE_REASON))
767 );
768 }
769
770 #[fuchsia_async::run_singlethreaded(test)]
771 async fn control_wait_termination() {
772 let client = flex_local::local_client_empty();
773 let (control, request_stream) =
774 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
775 let control = super::Control::new(control);
776 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
777 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
778 request_stream
779 .control_handle()
780 .send_on_interface_removed(CLOSE_REASON)
781 .expect("send terminal event");
782 std::mem::drop(request_stream);
783 assert_matches::assert_matches!(
784 control.wait_termination().await,
785 super::TerminalError::Terminal(CLOSE_REASON)
786 );
787 }
788
789 #[fuchsia_async::run_singlethreaded(test)]
790 async fn control_respond_and_drop() {
791 const ID: u64 = 15;
792 let client = flex_local::local_client_empty();
793 let (control, mut request_stream) =
794 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
795 let control = super::Control::new(control);
796 let ((), ()) = futures::future::join(
797 async move {
798 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
799 },
800 async move {
801 let responder = request_stream
802 .try_next()
803 .await
804 .expect("operating request stream")
805 .expect("stream ended unexpectedly")
806 .into_get_id()
807 .expect("unexpected request");
808 responder.send(ID).expect("failed to send response");
809 },
810 )
811 .await;
812 }
813
814 #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
819 #[test_case(
820 Err(fidl::Error::InvalidHeader),
821 Ok(Some(InterfaceRemovedReason::User)),
822 Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
823 "returns query error when not closed"
824 )]
825 #[test_case(
826 Err(fidl::Error::ClientChannelClosed {
827 epitaph: fidl::Epitaph::PeerClosed,
828 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
829 #[cfg(not(target_os = "fuchsia"))]
830 reason: None,
831 }),
832 Ok(Some(InterfaceRemovedReason::User)),
833 Err(TerminalError::Terminal(InterfaceRemovedReason::User));
834 "returns terminal error when channel closed"
835 )]
836 #[test_case(
837 Err(fidl::Error::ClientChannelClosed {
838 epitaph: fidl::Epitaph::PeerClosed,
839 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
840 #[cfg(not(target_os = "fuchsia"))]
841 reason: None,
842 }),
843 Ok(None),
844 Err(TerminalError::Fidl(
845 fidl::Error::ClientChannelClosed {
846 epitaph: fidl::Epitaph::PeerClosed,
847 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
848 #[cfg(not(target_os = "fuchsia"))]
849 reason: None,
850 }
851 ));
852 "returns query error when no terminal error"
853 )]
854 #[test_case(
855 Err(fidl::Error::ClientChannelClosed {
856 epitaph: fidl::Epitaph::PeerClosed,
857 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
858 #[cfg(not(target_os = "fuchsia"))]
859 reason: None,
860 }),
861 Err(fidl::Error::InvalidHeader),
862 Err(TerminalError::Fidl(
863 fidl::Error::ClientChannelClosed {
864 epitaph: fidl::Epitaph::PeerClosed,
865 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
866 #[cfg(not(target_os = "fuchsia"))]
867 reason: None,
868 }
869 ));
870 "returns query error when terminal event returns a fidl error"
871 )]
872 #[fuchsia_async::run_singlethreaded(test)]
873 async fn control_polling_race(
874 left_future_result: Result<(), fidl::Error>,
875 right_future_result: Result<
876 Option<fnet_interfaces_admin::InterfaceRemovedReason>,
877 fidl::Error,
878 >,
879 expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
880 ) {
881 let mut polled = false;
882 let first_future = std::future::poll_fn(|_cx| {
883 if polled {
884 Poll::Ready(left_future_result.clone())
885 } else {
886 polled = true;
887 Poll::Pending
888 }
889 })
890 .fuse();
891
892 let second_future =
893 std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
894
895 let res = or_terminal_event(first_future, second_future).await;
896 match (res, expected) {
897 (Ok(()), Ok(())) => (),
898 (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
899 if res == expected => {}
900 (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
903 (res, expected) => panic!("expected {:?} got {:?}", expected, res),
904 }
905 }
906
907 #[cfg(not(feature = "fdomain"))]
908 #[test]
909 fn convert_proof_to_grant() {
910 use assert_matches::assert_matches;
911 #[allow(unused)]
915 use fidl::{AsHandleRef, Rights};
916 let event = fidl::Event::create();
921 let grant = fnet_resources::GrantForInterfaceAuthorization {
922 interface_id: Default::default(),
923 token: event,
924 };
925
926 let fnet_resources::ProofOfInterfaceAuthorization { interface_id, token } =
927 proof_from_grant(&grant);
928 assert_eq!(interface_id, Default::default());
929 assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
930 }
931}