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;
12use zx_status as zx;
13
14#[derive(Error, Debug)]
16pub enum AddressStateProviderError {
17 #[error("address removed: {0:?}")]
19 AddressRemoved(fnet_interfaces_admin::AddressRemovalReason),
20 #[error("fidl error")]
22 Fidl(#[from] fidl::Error),
23 #[error("AddressStateProvider channel closed")]
25 ChannelClosed,
26}
27
28impl From<TerminalError<fnet_interfaces_admin::AddressRemovalReason>>
29 for AddressStateProviderError
30{
31 fn from(e: TerminalError<fnet_interfaces_admin::AddressRemovalReason>) -> Self {
32 match e {
33 TerminalError::Fidl(e) => AddressStateProviderError::Fidl(e),
34 TerminalError::Terminal(r) => AddressStateProviderError::AddressRemoved(r),
35 }
36 }
37}
38
39pub async fn wait_for_address_added_event(
43 event_stream: &mut fnet_interfaces_admin::AddressStateProviderEventStream,
44) -> Result<(), AddressStateProviderError> {
45 let event = event_stream
46 .next()
47 .await
48 .ok_or(AddressStateProviderError::ChannelClosed)?
49 .map_err(AddressStateProviderError::Fidl)?;
50 match event {
51 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => Ok(()),
52 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved { error } => {
53 Err(AddressStateProviderError::AddressRemoved(error))
54 }
55 }
56}
57
58pub fn assignment_state_stream(
71 address_state_provider: fnet_interfaces_admin::AddressStateProviderProxy,
72) -> impl Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
73{
74 let event_fut = address_state_provider
75 .take_event_stream()
76 .filter_map(|event| {
77 futures::future::ready(match event {
78 Ok(event) => match event {
79 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressAdded {} => None,
80 fnet_interfaces_admin::AddressStateProviderEvent::OnAddressRemoved {
81 error,
82 } => Some(AddressStateProviderError::AddressRemoved(error)),
83 },
84 Err(e) => Some(AddressStateProviderError::Fidl(e)),
85 })
86 })
87 .into_future()
88 .map(|(event, _stream)| event.unwrap_or(AddressStateProviderError::ChannelClosed));
89 futures::stream::try_unfold(
90 (address_state_provider, event_fut),
91 |(address_state_provider, event_fut)| {
92 futures::future::select(
97 address_state_provider.watch_address_assignment_state(),
98 event_fut,
99 )
100 .then(|s| match s {
101 futures::future::Either::Left((state_result, event_fut)) => match state_result {
102 Ok(state) => {
103 futures::future::ok(Some((state, (address_state_provider, event_fut))))
104 .left_future()
105 }
106 Err(e) if e.is_closed() => event_fut.map(Result::Err).right_future(),
107 Err(e) => {
108 futures::future::err(AddressStateProviderError::Fidl(e)).left_future()
109 }
110 },
111 futures::future::Either::Right((error, _state_fut)) => {
112 futures::future::err(error).left_future()
113 }
114 })
115 },
116 )
117}
118
119pub async fn wait_assignment_state<S>(
127 stream: S,
128 want: fnet_interfaces::AddressAssignmentState,
129) -> Result<(), AddressStateProviderError>
130where
131 S: Stream<Item = Result<fnet_interfaces::AddressAssignmentState, AddressStateProviderError>>
132 + Unpin,
133{
134 stream
135 .try_filter_map(|state| futures::future::ok((state == want).then_some(())))
136 .try_next()
137 .await
138 .and_then(|opt| opt.ok_or_else(|| AddressStateProviderError::ChannelClosed))
139}
140
141type ControlEventStreamFutureToReason =
142 fn(
143 (
144 Option<Result<fnet_interfaces_admin::ControlEvent, fidl::Error>>,
145 fnet_interfaces_admin::ControlEventStream,
146 ),
147 ) -> Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>;
148
149#[cfg(not(feature = "fdomain"))]
159pub fn proof_from_grant(
160 grant: &fnet_resources::GrantForInterfaceAuthorization,
161) -> fnet_resources::ProofOfInterfaceAuthorization {
162 let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } = grant;
163
164 fnet_resources::ProofOfInterfaceAuthorization {
169 interface_id: *interface_id,
170 token: token.duplicate_handle(fidl::Rights::TRANSFER).unwrap(),
171 }
172}
173
174#[derive(Clone)]
177pub struct Control {
178 proxy: fnet_interfaces_admin::ControlProxy,
179 terminal_event_fut: futures::future::Shared<
187 futures::future::Map<
188 futures::stream::StreamFuture<fnet_interfaces_admin::ControlEventStream>,
189 ControlEventStreamFutureToReason,
190 >,
191 >,
192}
193
194async fn or_terminal_event<QR, QF, TR>(
197 query_fut: QF,
198 terminal_event_fut: TR,
199) -> Result<QR, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>
200where
201 QR: Unpin,
202 QF: Unpin + Future<Output = Result<QR, fidl::Error>>,
203 TR: Unpin
204 + Future<Output = Result<Option<fnet_interfaces_admin::InterfaceRemovedReason>, fidl::Error>>,
205{
206 match futures::future::select(query_fut, terminal_event_fut).await {
207 futures::future::Either::Left((query_result, terminal_event_fut)) => match query_result {
208 Ok(ok) => Ok(ok),
209 Err(e) if e.is_closed() => match terminal_event_fut.await {
210 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
211 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
212 },
213 Err(e) => Err(TerminalError::Fidl(e)),
214 },
215 futures::future::Either::Right((event, query_fut)) => {
216 if let Some(query_result) = query_fut.now_or_never() {
232 match query_result {
233 Ok(ok) => Ok(ok),
234 Err(e) if e.is_closed() => match event {
235 Ok(Some(reason)) => Err(TerminalError::Terminal(reason)),
236 Ok(None) | Err(_) => Err(TerminalError::Fidl(e)),
237 },
238 Err(e) => Err(TerminalError::Fidl(e)),
239 }
240 } else {
241 match event.map_err(|e| TerminalError::Fidl(e))? {
242 Some(removal_reason) => Err(TerminalError::Terminal(removal_reason)),
243 None => Err(TerminalError::Fidl(fidl::Error::ClientChannelClosed {
244 status: zx::Status::PEER_CLOSED,
245 protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
246 #[cfg(not(target_os = "fuchsia"))]
247 reason: None,
248 epitaph: None,
249 })),
250 }
251 }
252 }
253 }
254}
255
256impl Control {
257 pub fn add_address(
259 &self,
260 address: &flex_fuchsia_net::Subnet,
261 parameters: &fnet_interfaces_admin::AddressParameters,
262 address_state_provider: flex_client::fidl::ServerEnd<
263 fnet_interfaces_admin::AddressStateProviderMarker,
264 >,
265 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
266 self.or_terminal_event_no_return(self.proxy.add_address(
267 address,
268 parameters,
269 address_state_provider,
270 ))
271 }
272
273 pub async fn get_id(
275 &self,
276 ) -> Result<u64, TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
277 self.or_terminal_event(self.proxy.get_id()).await
278 }
279
280 pub async fn remove_address(
282 &self,
283 address: &flex_fuchsia_net::Subnet,
284 ) -> Result<
285 fnet_interfaces_admin::ControlRemoveAddressResult,
286 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
287 > {
288 self.or_terminal_event(self.proxy.remove_address(address)).await
289 }
290
291 pub async fn set_configuration(
293 &self,
294 config: &fnet_interfaces_admin::Configuration,
295 ) -> Result<
296 fnet_interfaces_admin::ControlSetConfigurationResult,
297 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
298 > {
299 self.or_terminal_event(self.proxy.set_configuration(config)).await
300 }
301
302 pub async fn get_configuration(
304 &self,
305 ) -> Result<
306 fnet_interfaces_admin::ControlGetConfigurationResult,
307 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
308 > {
309 self.or_terminal_event(self.proxy.get_configuration()).await
310 }
311
312 pub async fn get_authorization_for_interface(
314 &self,
315 ) -> Result<
316 fnet_resources::GrantForInterfaceAuthorization,
317 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
318 > {
319 self.or_terminal_event(self.proxy.get_authorization_for_interface()).await
320 }
321
322 pub async fn enable(
324 &self,
325 ) -> Result<
326 fnet_interfaces_admin::ControlEnableResult,
327 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
328 > {
329 self.or_terminal_event(self.proxy.enable()).await
330 }
331
332 pub async fn remove(
334 &self,
335 ) -> Result<
336 fnet_interfaces_admin::ControlRemoveResult,
337 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
338 > {
339 self.or_terminal_event(self.proxy.remove()).await
340 }
341
342 pub async fn disable(
344 &self,
345 ) -> Result<
346 fnet_interfaces_admin::ControlDisableResult,
347 TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>,
348 > {
349 self.or_terminal_event(self.proxy.disable()).await
350 }
351
352 pub fn detach(
354 &self,
355 ) -> Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>> {
356 self.or_terminal_event_no_return(self.proxy.detach())
357 }
358
359 pub fn new(proxy: fnet_interfaces_admin::ControlProxy) -> Self {
361 let terminal_event_fut = proxy
362 .take_event_stream()
363 .into_future()
364 .map::<_, ControlEventStreamFutureToReason>(|(event, _stream)| {
365 event
366 .map(|r| {
367 r.map(
368 |fnet_interfaces_admin::ControlEvent::OnInterfaceRemoved { reason }| {
369 reason
370 },
371 )
372 })
373 .transpose()
374 })
375 .shared();
376 Self { proxy, terminal_event_fut }
377 }
378
379 pub async fn wait_termination(
381 self,
382 ) -> TerminalError<fnet_interfaces_admin::InterfaceRemovedReason> {
383 let Self { proxy: _, terminal_event_fut } = self;
384 match terminal_event_fut.await {
385 Ok(Some(event)) => TerminalError::Terminal(event),
386 Ok(None) => TerminalError::Fidl(fidl::Error::ClientChannelClosed {
387 status: zx::Status::PEER_CLOSED,
388 protocol_name: <fnet_interfaces_admin::ControlMarker as flex_client::fidl::ProtocolMarker>::DEBUG_NAME,
389 #[cfg(not(target_os = "fuchsia"))]
390 reason: None,
391 epitaph: None,
392 }),
393 Err(e) => TerminalError::Fidl(e),
394 }
395 }
396
397 #[cfg(not(feature = "fdomain"))]
399 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 flex_client::fidl::{ProtocolMarker, RequestStream};
531 use flex_fuchsia_net_interfaces as fnet_interfaces;
532 use flex_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
533 use fnet_interfaces_admin::InterfaceRemovedReason;
534 use futures::{FutureExt as _, StreamExt as _, TryStreamExt as _};
535 use test_case::test_case;
536 use zx_status as zx;
537
538 use super::*;
539
540 #[fuchsia_async::run_singlethreaded(test)]
542 async fn test_assignment_state_stream() {
543 let client = flex_local::local_client_empty();
544 let (address_state_provider, server_end) =
545 client.create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
546 let state_stream = assignment_state_stream(address_state_provider);
547 futures::pin_mut!(state_stream);
548
549 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
550 fnet_interfaces_admin::AddressRemovalReason::Invalid;
551 {
552 let (mut request_stream, control_handle) = server_end.into_stream_and_control_handle();
553
554 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
555 fnet_interfaces::AddressAssignmentState::Assigned;
556 let state_fut = state_stream.try_next().map(|r| {
557 assert_eq!(
558 r.expect("state stream error").expect("state stream ended"),
559 ASSIGNMENT_STATE_ASSIGNED
560 )
561 });
562 let handle_fut = request_stream.try_next().map(|r| match r.expect("request stream error").expect("request stream ended") {
563 fnet_interfaces_admin::AddressStateProviderRequest::WatchAddressAssignmentState { responder } => {
564 responder.send(ASSIGNMENT_STATE_ASSIGNED).expect("failed to send stubbed assignment state");
565 }
566 req => panic!("unexpected method called: {:?}", req),
567 });
568 let ((), ()) = futures::join!(state_fut, handle_fut);
569
570 control_handle
571 .send_on_address_removed(REMOVAL_REASON_INVALID)
572 .expect("failed to send fake INVALID address removal reason event");
573 }
574
575 assert_matches::assert_matches!(
576 state_stream.try_collect::<Vec<_>>().await,
577 Err(AddressStateProviderError::AddressRemoved(got)) if got == REMOVAL_REASON_INVALID
578 );
579 }
580
581 #[fuchsia_async::run_singlethreaded(test)]
584 async fn test_assignment_state_stream_single_error() {
585 let client = flex_local::local_client_empty();
586 let (address_state_provider, server_end) =
587 client.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 let states_fut = state_stream.collect::<Vec<_>>();
595
596 #[cfg(not(feature = "fdomain"))]
597 let states = states_fut.now_or_never().expect("state stream not immediately ready");
598
599 #[cfg(feature = "fdomain")]
602 let states = states_fut.await;
603
604 assert_matches::assert_matches!(
607 states.as_slice(),
608 [Err(AddressStateProviderError::Fidl(fidl::Error::ClientChannelClosed {
609 status: fidl::Status::INTERNAL,
610 #[cfg(not(target_os = "fuchsia"))]
611 reason: None,
612 ..
613 }))]
614 );
615 }
616
617 #[fuchsia_async::run_singlethreaded(test)]
620 async fn assignment_state_stream_state_before_event() {
621 let client = flex_local::local_client_empty();
622 let (address_state_provider, mut request_stream) =
623 client.create_proxy_and_stream::<fnet_interfaces_admin::AddressStateProviderMarker>();
624
625 const ASSIGNMENT_STATE_ASSIGNED: fnet_interfaces::AddressAssignmentState =
626 fnet_interfaces::AddressAssignmentState::Assigned;
627 const REMOVAL_REASON_INVALID: fnet_interfaces_admin::AddressRemovalReason =
628 fnet_interfaces_admin::AddressRemovalReason::Invalid;
629
630 let ((), ()) = futures::future::join(
631 async move {
632 request_stream
633 .try_next()
634 .await
635 .expect("request stream error")
636 .expect("request stream ended")
637 .into_watch_address_assignment_state()
638 .expect("unexpected request")
639 .send(ASSIGNMENT_STATE_ASSIGNED)
640 .expect("failed to send stubbed assignment state");
641 request_stream
642 .control_handle()
643 .send_on_address_removed(REMOVAL_REASON_INVALID)
644 .expect("failed to send fake INVALID address removal reason event");
645 },
646 async move {
647 let got = assignment_state_stream(address_state_provider).collect::<Vec<_>>().await;
648 assert_matches::assert_matches!(
649 got.as_slice(),
650 &[
651 Ok(got_state),
652 Err(AddressStateProviderError::AddressRemoved(got_reason)),
653 ] => {
654 assert_eq!(got_state, ASSIGNMENT_STATE_ASSIGNED);
655 assert_eq!(got_reason, REMOVAL_REASON_INVALID);
656 }
657 );
658 },
659 )
660 .await;
661 }
662
663 #[fuchsia_async::run_singlethreaded(test)]
665 async fn control_terminal_event() {
666 let client = flex_local::local_client_empty();
667 let (control, mut request_stream) =
668 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
669 let control = super::Control::new(control);
670 const EXPECTED_EVENT: fnet_interfaces_admin::InterfaceRemovedReason =
671 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
672 const ID: u64 = 15;
673 let ((), ()) = futures::future::join(
674 async move {
675 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
676 assert_matches::assert_matches!(
677 control.get_id().await,
678 Err(super::TerminalError::Terminal(got)) if got == EXPECTED_EVENT
679 );
680 },
681 async move {
682 let responder = request_stream
683 .try_next()
684 .await
685 .expect("operating request stream")
686 .expect("stream ended unexpectedly")
687 .into_get_id()
688 .expect("unexpected request");
689 responder.send(ID).expect("failed to send response");
690 request_stream
691 .control_handle()
692 .send_on_interface_removed(EXPECTED_EVENT)
693 .expect("sending terminal event");
694 },
695 )
696 .await;
697 }
698
699 #[fuchsia_async::run_singlethreaded(test)]
702 async fn control_missing_terminal_event() {
703 let client = flex_local::local_client_empty();
704 let (control, mut request_stream) =
705 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
706 let control = super::Control::new(control);
707 let ((), ()) = futures::future::join(
708 async move {
709 assert_matches::assert_matches!(
710 control.get_id().await,
711 Err(super::TerminalError::Fidl(fidl::Error::ClientChannelClosed {
712 status: zx::Status::PEER_CLOSED,
713 protocol_name: flex_fuchsia_net_interfaces_admin::ControlMarker::DEBUG_NAME,
714 #[cfg(not(target_os = "fuchsia"))]
715 reason: None,
716 ..
717 }))
718 );
719 },
720 async move {
721 match request_stream
722 .try_next()
723 .await
724 .expect("operating request stream")
725 .expect("stream ended unexpectedly")
726 {
727 fnet_interfaces_admin::ControlRequest::GetId { responder } => {
728 std::mem::drop(responder);
730 }
731 request => panic!("unexpected request {:?}", request),
732 }
733 },
734 )
735 .await;
736 }
737
738 #[fuchsia_async::run_singlethreaded(test)]
739 async fn control_pipelined_error() {
740 let client = flex_local::local_client_empty();
741 let (control, request_stream) =
742 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
743 let control = super::Control::new(control);
744 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
745 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
746 request_stream
747 .control_handle()
748 .send_on_interface_removed(CLOSE_REASON)
749 .expect("send terminal event");
750 std::mem::drop(request_stream);
751 #[cfg(feature = "fdomain")]
752 {
753 let control_clone = control.clone();
754 let _ = control_clone.wait_termination().await;
755 }
756 assert_matches::assert_matches!(control.or_terminal_event_no_return(Ok(())), Ok(()));
757 assert_matches::assert_matches!(
758 control.or_terminal_event_no_return(Err(fidl::Error::ClientWrite(
759 zx::Status::INTERNAL.into()
760 ))),
761 Err(super::TerminalError::Fidl(fidl::Error::ClientWrite(
762 fidl::TransportError::Status(zx::Status::INTERNAL)
763 )))
764 );
765 #[cfg(target_os = "fuchsia")]
766 assert_matches::assert_matches!(
767 control.or_terminal_event_no_return(Err(fidl::Error::ClientChannelClosed {
768 status: zx::Status::PEER_CLOSED,
769 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
770 epitaph: None,
771 })),
772 Err(super::TerminalError::Terminal(CLOSE_REASON))
773 );
774 }
775
776 #[fuchsia_async::run_singlethreaded(test)]
777 async fn control_wait_termination() {
778 let client = flex_local::local_client_empty();
779 let (control, request_stream) =
780 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
781 let control = super::Control::new(control);
782 const CLOSE_REASON: fnet_interfaces_admin::InterfaceRemovedReason =
783 fnet_interfaces_admin::InterfaceRemovedReason::BadPort;
784 request_stream
785 .control_handle()
786 .send_on_interface_removed(CLOSE_REASON)
787 .expect("send terminal event");
788 std::mem::drop(request_stream);
789 assert_matches::assert_matches!(
790 control.wait_termination().await,
791 super::TerminalError::Terminal(CLOSE_REASON)
792 );
793 }
794
795 #[fuchsia_async::run_singlethreaded(test)]
796 async fn control_respond_and_drop() {
797 const ID: u64 = 15;
798 let client = flex_local::local_client_empty();
799 let (control, mut request_stream) =
800 client.create_proxy_and_stream::<fnet_interfaces_admin::ControlMarker>();
801 let control = super::Control::new(control);
802 let ((), ()) = futures::future::join(
803 async move {
804 assert_matches::assert_matches!(control.get_id().await, Ok(ID));
805 },
806 async move {
807 let responder = request_stream
808 .try_next()
809 .await
810 .expect("operating request stream")
811 .expect("stream ended unexpectedly")
812 .into_get_id()
813 .expect("unexpected request");
814 responder.send(ID).expect("failed to send response");
815 },
816 )
817 .await;
818 }
819
820 #[test_case(Ok(()), Ok(Some(InterfaceRemovedReason::User)), Ok(()); "success")]
825 #[test_case(
826 Err(fidl::Error::InvalidHeader),
827 Ok(Some(InterfaceRemovedReason::User)),
828 Err(TerminalError::Fidl(fidl::Error::InvalidHeader));
829 "returns query error when not closed"
830 )]
831 #[test_case(
832 Err(fidl::Error::ClientChannelClosed {
833 status: zx::Status::PEER_CLOSED,
834 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
835 #[cfg(not(target_os = "fuchsia"))]
836 reason: None,
837 epitaph: None,
838 }),
839 Ok(Some(InterfaceRemovedReason::User)),
840 Err(TerminalError::Terminal(InterfaceRemovedReason::User));
841 "returns terminal error when channel closed"
842 )]
843 #[test_case(
844 Err(fidl::Error::ClientChannelClosed {
845 status: zx::Status::PEER_CLOSED,
846 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
847 #[cfg(not(target_os = "fuchsia"))]
848 reason: None,
849 epitaph: None,
850 }),
851 Ok(None),
852 Err(TerminalError::Fidl(
853 fidl::Error::ClientChannelClosed {
854 status: zx::Status::PEER_CLOSED,
855 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
856 #[cfg(not(target_os = "fuchsia"))]
857 reason: None,
858 epitaph: None,
859 }
860 ));
861 "returns query error when no terminal error"
862 )]
863 #[test_case(
864 Err(fidl::Error::ClientChannelClosed {
865 status: zx::Status::PEER_CLOSED,
866 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
867 #[cfg(not(target_os = "fuchsia"))]
868 reason: None,
869 epitaph: None,
870 }),
871 Err(fidl::Error::InvalidHeader),
872 Err(TerminalError::Fidl(
873 fidl::Error::ClientChannelClosed {
874 status: zx::Status::PEER_CLOSED,
875 protocol_name: fnet_interfaces_admin::ControlMarker::DEBUG_NAME,
876 #[cfg(not(target_os = "fuchsia"))]
877 reason: None,
878 epitaph: None,
879 }
880 ));
881 "returns query error when terminal event returns a fidl error"
882 )]
883 #[fuchsia_async::run_singlethreaded(test)]
884 async fn control_polling_race(
885 left_future_result: Result<(), fidl::Error>,
886 right_future_result: Result<
887 Option<fnet_interfaces_admin::InterfaceRemovedReason>,
888 fidl::Error,
889 >,
890 expected: Result<(), TerminalError<fnet_interfaces_admin::InterfaceRemovedReason>>,
891 ) {
892 let mut polled = false;
893 let first_future = std::future::poll_fn(|_cx| {
894 if polled {
895 Poll::Ready(left_future_result.clone())
896 } else {
897 polled = true;
898 Poll::Pending
899 }
900 })
901 .fuse();
902
903 let second_future =
904 std::future::poll_fn(|_cx| Poll::Ready(right_future_result.clone())).fuse();
905
906 let res = or_terminal_event(first_future, second_future).await;
907 match (res, expected) {
908 (Ok(()), Ok(())) => (),
909 (Err(TerminalError::Terminal(res)), Err(TerminalError::Terminal(expected)))
910 if res == expected => {}
911 (Err(TerminalError::Fidl(_)), Err(TerminalError::Fidl(_))) => (),
914 (res, expected) => panic!("expected {:?} got {:?}", expected, res),
915 }
916 }
917
918 #[cfg(not(feature = "fdomain"))]
919 #[test]
920 fn convert_proof_to_grant() {
921 use assert_matches::assert_matches;
922 #[allow(unused)]
926 use fidl::{AsHandleRef, Rights};
927 let event = fidl::Event::create();
932 let grant = fnet_resources::GrantForInterfaceAuthorization {
933 interface_id: Default::default(),
934 token: event,
935 };
936
937 let fnet_resources::ProofOfInterfaceAuthorization { interface_id, token } =
938 proof_from_grant(&grant);
939 assert_eq!(interface_id, Default::default());
940 assert_matches!(token.basic_info(), Ok(info) if info.rights == Rights::TRANSFER);
941 }
942}